/** * @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_flags32_t added_flags; ecs_flags32_t removed_flags; } 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_flags32_t added_flags; ecs_flags32_t removed_flags; } 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; /* Added/removed components for 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); void flecs_table_edges_add_flags( ecs_world_t *world, ecs_table_t *table, ecs_id_t id, ecs_flags32_t flags); #endif #ifdef FLECS_SANITIZE #define ecs_vec_from_column(arg_column, table, arg_elem_size) {\ .array = (arg_column)->data,\ .count = table->data.count,\ .size = table->data.size,\ .elem_size = arg_elem_size\ } #define ecs_vec_from_column_ext(arg_column, arg_count, arg_size, arg_elem_size) {\ .array = (arg_column)->data,\ .count = arg_count,\ .size = arg_size,\ .elem_size = arg_elem_size\ } #define ecs_vec_from_entities(table) {\ .array = table->data.entities,\ .count = table->data.count,\ .size = table->data.size,\ .elem_size = ECS_SIZEOF(ecs_entity_t)\ } #else #define ecs_vec_from_column(arg_column, table, arg_elem_size) {\ .array = (arg_column)->data,\ .count = table->data.count,\ .size = table->data.size,\ } #define ecs_vec_from_column_ext(arg_column, arg_count, arg_size, arg_elem_size) {\ .array = (arg_column)->data,\ .count = arg_count,\ .size = arg_size,\ } #define ecs_vec_from_entities(table) {\ .array = table->data.entities,\ .count = table->data.count,\ .size = table->data.size,\ } #endif #define ecs_vec_from_column_t(arg_column, table, T)\ ecs_vec_from_column(arg_column, table, ECS_SIZEOF(T)) /* Table event type for notifying tables of world events */ typedef enum ecs_table_eventkind_t { EcsTableTriggersForId, EcsTableNoTriggersForId, } ecs_table_eventkind_t; typedef struct ecs_table_event_t { ecs_table_eventkind_t kind; /* 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 { void *data; /* Array with component data */ ecs_type_info_t *ti; /* Component type info */ } ecs_column_t; /** Table data */ struct ecs_data_t { ecs_entity_t *entities; /* Entity ids */ ecs_column_t *columns; /* Component data */ int32_t count; int32_t size; }; /** A table is the Flecs equivalent of an archetype. Tables store all entities * with a specific set of components. Tables are automatically created when an * entity has a set of components not previously observed before. When a new * table is created, it is automatically matched with existing queries */ struct ecs_table_t { uint64_t id; /* Table id in sparse set */ ecs_flags32_t flags; /* Flags for testing table properties */ int16_t column_count; /* Number of components (excluding tags) */ 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 */ int16_t *component_map; /* Get column for component id */ int16_t *column_map; /* Map type index <-> column * - 0..count(T): type index -> column * - count(T)..count(C): column -> type index */ ecs_table__t *_; /* Infrequently accessed table metadata */ }; /* 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); /* 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); /* 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, int32_t count, const ecs_entity_t *ids); /* Shrink table to contents */ bool flecs_table_shrink( ecs_world_t *world, ecs_table_t *table); /* Get dirty state for table columns */ int32_t* flecs_table_get_dirty_state( ecs_world_t *world, ecs_table_t *table); /* Initialize root table */ void flecs_init_root_table( ecs_world_t *world); /* 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_id_t id, 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); 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); ecs_id_t flecs_column_id( ecs_table_t *table, int32_t column_index); #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; /** 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_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 */ ecs_commands_t *cmd; ecs_commands_t cmd_stack[2]; /* Two so we can flush one & populate the other */ bool cmd_flushing; /* Ensures only one defer_end call flushes */ /* 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; #ifdef FLECS_SCRIPT /* Thread specific runtime for script execution */ ecs_script_runtime_t *runtime; #endif }; /* 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; /* Records cache */ ecs_vec_t records; /* Stack of ids being deleted during cleanup action. */ ecs_vec_t marked_ids; /* vector */ /* Components deleted during cleanup action. Used to delay cleaning up of * type info so it's guaranteed that this data is available while the * storage is cleaning up tables. */ ecs_vec_t deleted_components; /* vector */ } ecs_store_t; /* fini actions */ typedef struct ecs_action_elem_t { ecs_fini_action_t action; void *ctx; } ecs_action_elem_t; typedef struct ecs_pipeline_state_t ecs_pipeline_state_t; /** The world stores and manages all ECS data. An application can have more than * one world, but data is not shared between worlds. */ struct ecs_world_t { ecs_header_t hdr; /* -- Type metadata -- */ ecs_id_record_t **id_index_lo; ecs_map_t id_index_hi; /* map */ ecs_map_t type_info; /* map */ /* -- 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; /* 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 */ /* -- Component ids -- */ ecs_vec_t component_ids; /* World local component ids */ /* 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_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); #define flecs_table_cache_count(cache) (cache)->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 /* 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); /* Return flags for matching id records */ ecs_flags32_t flecs_id_flags_get( ecs_world_t *world, ecs_id_t id); #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; /* Placeholder values for queries with only $this variable */ extern ecs_query_var_t flecs_this_array; extern char *flecs_this_name_array; /* -- Instruction kinds -- */ typedef enum { EcsQueryAnd, /* And operator: find or match id against variable source */ 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) */ EcsQueryCache, /* Cached search */ EcsQueryIsCache, /* Cached search for queries that are entirely cached */ EcsQueryUp, /* Up traversal */ EcsQuerySelfUp, /* Self|up traversal */ EcsQueryWith, /* Match id against fixed or variable source */ 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 */ 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; ecs_table_record_t *tr; bool ready; } ecs_trav_up_t; typedef enum { EcsTravUp = 1, EcsTravDown = 2 } ecs_trav_direction_t; typedef struct { ecs_map_t src; /* 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; const ecs_table_record_t *tr; } 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 start_from; int32_t first_to_eval; } ecs_query_trivial_ctx_t; /* *From operator iterator context */ typedef struct { ecs_query_and_ctx_t and; ecs_entity_t type_id; ecs_type_t *type; int32_t first_id_index; int32_t cur_id_index; } ecs_query_xfrom_ctx_t; /* Member equality context */ typedef struct { ecs_query_each_ctx_t each; void *data; } ecs_query_membereq_ctx_t; /* Toggle context */ typedef struct { ecs_table_range_t range; int32_t cur; int32_t block_index; ecs_flags64_t block; ecs_termset_t prev_set_fields; bool optional_not; bool has_bitset; } ecs_query_toggle_ctx_t; 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; 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_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 */ /* Misc */ int16_t tokens_len; /* Length of tokens buffer */ char *tokens; /* Buffer with string tokens used by terms */ int32_t *monitor; /* Change monitor for fields with fixed src */ /* Query cache */ struct ecs_query_cache_t *cache; /* Cache, if query contains cached 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 */ const ecs_table_record_t **trs; /* Information about where to find field in table */ ecs_id_t *ids; /* Resolved (component) ids for current table */ ecs_entity_t *sources; /* Subjects (sources) of ids */ 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 trs; 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; /* Zero'd out sources array, used for results that only match on $this */ ecs_entity_t *sources; /* Map field indices from cache to query */ int8_t *field_map; /* 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_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 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 */ bool flecs_query_cache_search( const ecs_query_run_ctx_t *ctx); /* Cache search where entire query is cached */ bool flecs_query_is_cache_search( const ecs_query_run_ctx_t *ctx); /* Cache test */ bool flecs_query_cache_test( const ecs_query_run_ctx_t *ctx, bool redo); /* Cache test where entire query is cached */ bool flecs_query_is_cache_test( const ecs_query_run_ctx_t *ctx, bool redo); /** * @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 queries with trivial terms. */ bool flecs_query_trivial_search( const ecs_query_run_ctx_t *ctx, ecs_query_trivial_ctx_t *op_ctx, bool redo, ecs_flags64_t field_set); /* Iterator for queries with only trivial terms. */ bool flecs_query_is_trivial_search( const ecs_query_run_ctx_t *ctx, ecs_query_trivial_ctx_t *op_ctx, bool redo); /* Trivial test for constrained $this. */ bool flecs_query_trivial_test( const ecs_query_run_ctx_t *ctx, bool first, ecs_flags64_t field_set); /* 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_tr( ecs_iter_t *it, int32_t field_index, const ecs_table_record_t *tr); 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, const ecs_table_record_t *tr, ecs_entity_t trav, ecs_entity_t second, const ecs_query_run_ctx_t *ctx); bool flecs_query_table_filter( ecs_table_t *table, ecs_query_lbl_t other, ecs_flags32_t filter_mask); bool flecs_query_setids( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx); bool flecs_query_run_until( bool redo, ecs_query_run_ctx_t *ctx, const ecs_query_op_t *ops, ecs_query_lbl_t first, ecs_query_lbl_t cur, int32_t last); /* 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_id( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx, ecs_flags32_t table_filter); bool flecs_query_with( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx); bool flecs_query_with_id( 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); /* 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); /* Up traversal */ typedef enum ecs_query_up_select_trav_kind_t { FlecsQueryUpSelectUp, FlecsQueryUpSelectSelfUp } ecs_query_up_select_trav_kind_t; typedef enum ecs_query_up_select_kind_t { FlecsQueryUpSelectDefault, 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); /* 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) */ int8_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); 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); 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); void flecs_commands_init( ecs_stage_t *stage, ecs_commands_t *cmd); void flecs_commands_fini( ecs_stage_t *stage, ecs_commands_t *cmd); #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_delete_table( ecs_world_t *world, ecs_table_t *table); /* Suspend/resume readonly state. To fully support implicit registration of * components, it should be possible to register components while the world is * in readonly mode. It is not uncommon that a component is used first from * within a system, which are often ran while in readonly mode. * * Suspending readonly mode is only allowed when the world is not multithreaded. * When a world is multithreaded, it is not safe to (even temporarily) leave * readonly mode, so a multithreaded application should always explicitly * register components in advance. * * These operations also suspend deferred mode. */ typedef struct ecs_suspend_readonly_state_t { bool is_readonly; bool is_deferred; bool cmd_flushing; int32_t defer_count; ecs_entity_t scope; ecs_entity_t with; ecs_commands_t cmd_stack[2]; ecs_commands_t *cmd; ecs_stage_t *stage; } ecs_suspend_readonly_state_t; ecs_world_t* flecs_suspend_readonly( const ecs_world_t *world, ecs_suspend_readonly_state_t *state); void flecs_resume_readonly( ecs_world_t *world, ecs_suspend_readonly_state_t *state); /* Convenience macro's for world allocator */ #define flecs_walloc(world, size)\ flecs_alloc(&world->allocator, size) #define flecs_walloc_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_table_diff_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); typedef struct ecs_instantiate_ctx_t { ecs_entity_t root_prefab; ecs_entity_t root_instance; } ecs_instantiate_ctx_t; void flecs_instantiate( ecs_world_t *world, ecs_entity_t base, ecs_table_t *table, int32_t row, int32_t count, const ecs_instantiate_ctx_t *ctx); 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, const ecs_table_record_t *tr, int32_t count, int32_t row, const ecs_entity_t *entities, ecs_id_t id, const ecs_type_info_t *ti, ecs_entity_t event, ecs_iter_action_t hook); void flecs_add_ids( ecs_world_t *world, ecs_entity_t entity, ecs_id_t *ids, int32_t count); //////////////////////////////////////////////////////////////////////////////// //// 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) { #ifdef FLECS_DEBUG if (ecs_should_log(0)) { char *path = ecs_get_path(world, e); ecs_trace("unregistering component '%s'", path); ecs_os_free(path); } #endif if (!ecs_vec_count(&world->store.marked_ids)) { flecs_type_info_free(world, e); } else { ecs_vec_append_t(&world->allocator, &world->store.deleted_components, ecs_entity_t)[0] = e; } } } } static void flecs_ensure_module_tag(ecs_iter_t *it) { ecs_world_t *world = it->world; int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; ecs_entity_t parent = ecs_get_target(world, e, EcsChildOf, 0); if (parent) { ecs_add_id(world, parent, EcsModule); } } } static void flecs_disable_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 = 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 = 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 = 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); /* Preallocate enough memory for initial components */ ecs_allocator_t *a = &world->allocator; ecs_vec_t v_entities = ecs_vec_from_entities(result); ecs_vec_init_t(a, &v_entities, ecs_entity_t, EcsFirstUserComponentId); { ecs_column_t *column = &result->data.columns[0]; ecs_vec_t v = ecs_vec_from_column_t(column, result, EcsComponent); ecs_vec_init_t(a, &v, EcsComponent, EcsFirstUserComponentId); ecs_assert(v.count == v_entities.count, ECS_INTERNAL_ERROR, NULL); ecs_assert(v.size == v_entities.size, ECS_INTERNAL_ERROR, NULL); column->data = v.array; } { ecs_column_t *column = &result->data.columns[1]; ecs_vec_t v = ecs_vec_from_column_t(column, result, EcsIdentifier); ecs_vec_init_t(a, &v, EcsIdentifier, EcsFirstUserComponentId); ecs_assert(v.count == v_entities.count, ECS_INTERNAL_ERROR, NULL); ecs_assert(v.size == v_entities.size, ECS_INTERNAL_ERROR, NULL); column->data = v.array; } { ecs_column_t *column = &result->data.columns[2]; ecs_vec_t v = ecs_vec_from_column_t(column, result, EcsIdentifier); ecs_vec_init_t(a, &v, EcsIdentifier, EcsFirstUserComponentId); ecs_assert(v.count == v_entities.count, ECS_INTERNAL_ERROR, NULL); ecs_assert(v.size == v_entities.size, ECS_INTERNAL_ERROR, NULL); column->data = v.array; } result->data.entities = v_entities.array; result->data.count = 0; result->data.size = v_entities.size; 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, EcsFlecsInternals); 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 }, .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); /* 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 }}, .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, .events = {EcsOnAdd}, .callback = flecs_register_final }); ecs_observer(world, { .query.terms = { { .id = ecs_pair(EcsOnDelete, EcsWildcard) } }, .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, .events = {EcsOnAdd, EcsOnRemove}, .callback = flecs_register_on_delete }); ecs_observer(world, { .query.terms = { { .id = ecs_pair(EcsOnDeleteTarget, EcsWildcard) } }, .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, .events = {EcsOnAdd, EcsOnRemove}, .callback = flecs_register_on_delete_object }); ecs_observer(world, { .query.terms = { { .id = ecs_pair(EcsOnInstantiate, EcsWildcard) } }, .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, .events = {EcsOnAdd}, .callback = flecs_register_on_instantiate }); ecs_observer(world, { .query.terms = {{ .id = EcsSymmetric }}, .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 }}, .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 }}, .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 }}, .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) }, }, .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 }}, .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 }}, .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) } }, .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 } }, .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 }, { .id = EcsDisabled }, }, .events = {EcsOnAdd, EcsOnRemove}, .callback = flecs_disable_observer }); /* Observer that tracks whether modules are disabled */ ecs_observer(world, { .query.terms = { { .id = EcsModule }, { .id = EcsDisabled }, }, .events = {EcsOnAdd, EcsOnRemove}, .callback = flecs_disable_module }); /* Set scope back to flecs core */ ecs_set_scope(world, EcsFlecsCore); /* 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); /* Unqueryable entities */ ecs_add_id(world, EcsThis, EcsNotQueryable); ecs_add_id(world, EcsWildcard, EcsNotQueryable); ecs_add_id(world, EcsAny, EcsNotQueryable); ecs_add_id(world, EcsVariable, EcsNotQueryable); /* Tag relationships (relationships that should never have data) */ ecs_add_id(world, EcsIsA, EcsPairIsTag); ecs_add_id(world, EcsChildOf, EcsPairIsTag); ecs_add_id(world, EcsSlotOf, EcsPairIsTag); ecs_add_id(world, EcsDependsOn, EcsPairIsTag); ecs_add_id(world, EcsFlag, EcsPairIsTag); ecs_add_id(world, EcsWith, EcsPairIsTag); /* 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); /* 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); /* 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); 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; each_iter->trs = NULL; 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; const ecs_table_record_t *next = flecs_table_cache_next( &each_iter->it, ecs_table_record_t); it->flags |= EcsIterIsValid; if (next) { each_iter->trs = next; ecs_table_t *table = next->hdr.table; it->table = table; it->count = ecs_table_count(table); it->entities = ecs_table_entities(table); it->ids = &table->type.array[next->index]; it->trs = &each_iter->trs; it->sources = &each_iter->sources; it->sizes = &each_iter->sizes; it->set_fields = 1; 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; ecs_expr_node_t *expr; /* Only set if script is just an expression */ char *token_buffer; char *token_remaining; /* Remaining space in token buffer */ const char *next_token; /* First character after expression */ int32_t token_buffer_size; int32_t refcount; } 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 = ']', EcsTokMember = '.', EcsTokComma = ',', EcsTokSemiColon = ';', EcsTokColon = ':', EcsTokAssign = '=', EcsTokAdd = '+', EcsTokSub = '-', EcsTokMul = '*', EcsTokDiv = '/', EcsTokMod = '%', EcsTokBitwiseOr = '|', EcsTokBitwiseAnd = '&', EcsTokNot = '!', EcsTokOptional = '?', EcsTokAnnotation = '@', EcsTokNewline = '\n', EcsTokEq = 100, EcsTokNeq = 101, EcsTokGt = 102, EcsTokGtEq = 103, EcsTokLt = 104, EcsTokLtEq = 105, EcsTokAnd = 106, EcsTokOr = 107, EcsTokMatch = 108, EcsTokRange = 109, EcsTokShiftLeft = 110, EcsTokShiftRight = 111, EcsTokIdentifier = 112, EcsTokString = 113, EcsTokNumber = 114, EcsTokKeywordModule = 115, EcsTokKeywordUsing = 116, EcsTokKeywordWith = 117, EcsTokKeywordIf = 118, EcsTokKeywordFor = 119, EcsTokKeywordIn = 120, EcsTokKeywordElse = 121, EcsTokKeywordTemplate = 122, EcsTokKeywordProp = 130, EcsTokKeywordConst = 131, EcsTokKeywordMatch = 132, EcsTokAddAssign = 133, EcsTokMulAssign = 134, } 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; typedef struct ecs_script_tokenizer_t { ecs_script_tokens_t stack; ecs_script_token_t *tokens; } ecs_script_tokenizer_t; 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_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); const char* flecs_script_identifier( ecs_script_parser_t *parser, const char *pos, ecs_script_token_t *out); #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; bool merge_variable_members; /* For term parser */ ecs_term_t *term; ecs_oper_kind_t extra_oper; ecs_term_ref_t *extra_args; }; typedef struct ecs_function_calldata_t { ecs_entity_t function; ecs_function_callback_t callback; void *ctx; } ecs_function_calldata_t; /** * @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, EcsAstFor } 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; /* Array with component ids that are added in scope. Used to limit * archetype moves. */ ecs_vec_t components; /* vec */ }; typedef struct ecs_script_id_t { const char *first; const char *second; ecs_id_t flag; ecs_id_t eval; /* If first or second refer to a variable, these are the cached variable * stack pointers so we don't have to lookup variables by name. */ int32_t first_sp; int32_t second_sp; /* If true, the lookup result for this id cannot be cached. This is the case * for entities that are defined inside of templates, which have different * values for each instantiation. */ bool dynamic; } ecs_script_id_t; typedef struct ecs_script_tag_t { ecs_script_node_t node; ecs_script_id_t id; } ecs_script_tag_t; typedef struct ecs_script_component_t { ecs_script_node_t node; ecs_script_id_t id; ecs_expr_node_t *expr; ecs_value_t eval; bool is_collection; } ecs_script_component_t; typedef struct ecs_script_default_component_t { ecs_script_node_t node; ecs_expr_node_t *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; int32_t sp; } 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; ecs_expr_node_t *name_expr; /* 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; ecs_expr_node_t *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; ecs_expr_node_t *expr; } ecs_script_if_t; typedef struct ecs_script_for_range_t { ecs_script_node_t node; const char *loop_var; ecs_expr_node_t *from; ecs_expr_node_t *to; ecs_script_scope_t *scope; } ecs_script_for_range_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, bool name_is_expr); 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); ecs_script_for_range_t* flecs_script_insert_for_range( ecs_script_parser_t *parser); #endif /** * @file addons/script/expr/expr.h * @brief Script expression support. */ #ifndef FLECS_EXPR_SCRIPT_H #define FLECS_EXPR_SCRIPT_H /** * @file addons/script/expr/stack.h * @brief Script expression AST. */ #ifndef FLECS_SCRIPT_EXPR_STACK_H #define FLECS_SCRIPT_EXPR_STACK_H #define FLECS_EXPR_STACK_MAX (256) #define FLECS_EXPR_SMALL_DATA_SIZE (24) typedef union ecs_expr_small_value_t { bool bool_; char char_; ecs_byte_t byte_; int8_t i8; int16_t i16; int32_t i32; int64_t i64; intptr_t iptr; uint8_t u8; uint16_t u16; uint32_t u32; uint64_t u64; uintptr_t uptr; double f32; double f64; char *string; ecs_entity_t entity; ecs_id_t id; /* Avoid allocations for small trivial types */ char small_data[FLECS_EXPR_SMALL_DATA_SIZE]; } ecs_expr_small_value_t; typedef struct ecs_expr_value_t { ecs_value_t value; const ecs_type_info_t *type_info; bool owned; /* Is value owned by the runtime */ } ecs_expr_value_t; typedef struct ecs_expr_stack_frame_t { ecs_stack_cursor_t *cur; int32_t sp; } ecs_expr_stack_frame_t; typedef struct ecs_expr_stack_t { ecs_expr_value_t values[FLECS_EXPR_STACK_MAX]; ecs_expr_stack_frame_t frames[FLECS_EXPR_STACK_MAX]; ecs_stack_t stack; int32_t frame; } ecs_expr_stack_t; void flecs_expr_stack_init( ecs_expr_stack_t *stack); void flecs_expr_stack_fini( ecs_expr_stack_t *stack); ecs_expr_value_t* flecs_expr_stack_alloc( ecs_expr_stack_t *stack, const ecs_type_info_t *ti); ecs_expr_value_t* flecs_expr_stack_result( ecs_expr_stack_t *stack, ecs_expr_node_t *node); void flecs_expr_stack_push( ecs_expr_stack_t *stack); void flecs_expr_stack_pop( ecs_expr_stack_t *stack); #endif /** * @file addons/script/expr_ast.h * @brief Script expression AST. */ #ifndef FLECS_SCRIPT_EXPR_AST_H #define FLECS_SCRIPT_EXPR_AST_H #define FLECS_EXPR_SMALL_DATA_SIZE (24) typedef enum ecs_expr_node_kind_t { EcsExprValue, EcsExprInterpolatedString, EcsExprInitializer, EcsExprEmptyInitializer, EcsExprUnary, EcsExprBinary, EcsExprIdentifier, EcsExprVariable, EcsExprGlobalVariable, EcsExprFunction, EcsExprMethod, EcsExprMember, EcsExprElement, EcsExprComponent, EcsExprCast, EcsExprCastNumber, EcsExprMatch } ecs_expr_node_kind_t; struct ecs_expr_node_t { ecs_expr_node_kind_t kind; ecs_entity_t type; const ecs_type_info_t *type_info; const char *pos; }; typedef struct ecs_expr_value_node_t { ecs_expr_node_t node; void *ptr; ecs_expr_small_value_t storage; } ecs_expr_value_node_t; typedef struct ecs_expr_interpolated_string_t { ecs_expr_node_t node; char *value; /* modified by parser */ char *buffer; /* for storing expr tokens */ ecs_size_t buffer_size; ecs_vec_t fragments; /* vec */ ecs_vec_t expressions; /* vec */ } ecs_expr_interpolated_string_t; typedef struct ecs_expr_initializer_element_t { const char *member; ecs_expr_node_t *value; uintptr_t offset; ecs_script_token_kind_t operator; } ecs_expr_initializer_element_t; typedef struct ecs_expr_initializer_t { ecs_expr_node_t node; ecs_vec_t elements; const ecs_type_info_t *type_info; bool is_collection; bool is_dynamic; } ecs_expr_initializer_t; typedef struct ecs_expr_variable_t { ecs_expr_node_t node; const char *name; ecs_value_t global_value; /* Only set for global variables */ int32_t sp; /* For fast variable lookups */ } ecs_expr_variable_t; typedef struct ecs_expr_identifier_t { ecs_expr_node_t node; const char *value; ecs_expr_node_t *expr; } ecs_expr_identifier_t; typedef struct ecs_expr_unary_t { ecs_expr_node_t node; ecs_expr_node_t *expr; ecs_script_token_kind_t operator; } ecs_expr_unary_t; typedef struct ecs_expr_binary_t { ecs_expr_node_t node; ecs_expr_node_t *left; ecs_expr_node_t *right; ecs_script_token_kind_t operator; } ecs_expr_binary_t; typedef struct ecs_expr_member_t { ecs_expr_node_t node; ecs_expr_node_t *left; const char *member_name; uintptr_t offset; } ecs_expr_member_t; typedef struct ecs_expr_function_t { ecs_expr_node_t node; ecs_expr_node_t *left; ecs_expr_initializer_t *args; const char *function_name; ecs_function_calldata_t calldata; } ecs_expr_function_t; typedef struct ecs_expr_element_t { ecs_expr_node_t node; ecs_expr_node_t *left; ecs_expr_node_t *index; ecs_size_t elem_size; } ecs_expr_element_t; typedef struct ecs_expr_component_t { ecs_expr_node_t node; ecs_expr_node_t *expr; ecs_id_t component; } ecs_expr_component_t; typedef struct ecs_expr_cast_t { ecs_expr_node_t node; ecs_expr_node_t *expr; } ecs_expr_cast_t; typedef struct ecs_expr_match_element_t { ecs_expr_node_t *compare; ecs_expr_node_t *expr; } ecs_expr_match_element_t; typedef struct ecs_expr_match_t { ecs_expr_node_t node; ecs_expr_node_t *expr; ecs_vec_t elements; ecs_expr_match_element_t any; } ecs_expr_match_t; ecs_expr_value_node_t* flecs_expr_value_from( ecs_script_t *script, ecs_expr_node_t *node, ecs_entity_t type); ecs_expr_variable_t* flecs_expr_variable_from( ecs_script_t *script, ecs_expr_node_t *node, const char *name); ecs_expr_value_node_t* flecs_expr_bool( ecs_script_parser_t *parser, bool value); ecs_expr_value_node_t* flecs_expr_int( ecs_script_parser_t *parser, int64_t value); ecs_expr_value_node_t* flecs_expr_uint( ecs_script_parser_t *parser, uint64_t value); ecs_expr_value_node_t* flecs_expr_float( ecs_script_parser_t *parser, double value); ecs_expr_value_node_t* flecs_expr_string( ecs_script_parser_t *parser, const char *value); ecs_expr_interpolated_string_t* flecs_expr_interpolated_string( ecs_script_parser_t *parser, const char *value); ecs_expr_value_node_t* flecs_expr_entity( ecs_script_parser_t *parser, ecs_entity_t value); ecs_expr_initializer_t* flecs_expr_initializer( ecs_script_parser_t *parser); ecs_expr_identifier_t* flecs_expr_identifier( ecs_script_parser_t *parser, const char *value); ecs_expr_variable_t* flecs_expr_variable( ecs_script_parser_t *parser, const char *value); ecs_expr_unary_t* flecs_expr_unary( ecs_script_parser_t *parser); ecs_expr_binary_t* flecs_expr_binary( ecs_script_parser_t *parser); ecs_expr_member_t* flecs_expr_member( ecs_script_parser_t *parser); ecs_expr_function_t* flecs_expr_function( ecs_script_parser_t *parser); ecs_expr_element_t* flecs_expr_element( ecs_script_parser_t *parser); ecs_expr_match_t* flecs_expr_match( ecs_script_parser_t *parser); ecs_expr_cast_t* flecs_expr_cast( ecs_script_t *script, ecs_expr_node_t *node, ecs_entity_t type); #endif /** * @file addons/script/exor_visit.h * @brief Script AST visitor utilities. */ #ifndef FLECS_EXPR_SCRIPT_VISIT_H #define FLECS_EXPR_SCRIPT_VISIT_H #define flecs_expr_visit_error(script, node, ...) \ ecs_parser_error( \ script->name, script->code, \ ((const ecs_expr_node_t*)node)->pos - script->code, \ __VA_ARGS__); int flecs_expr_visit_type( ecs_script_t *script, ecs_expr_node_t *node, const ecs_expr_eval_desc_t *desc); int flecs_expr_visit_fold( ecs_script_t *script, ecs_expr_node_t **node, const ecs_expr_eval_desc_t *desc); int flecs_expr_visit_eval( const ecs_script_t *script, ecs_expr_node_t *node, const ecs_expr_eval_desc_t *desc, ecs_value_t *out); void flecs_expr_visit_free( ecs_script_t *script, ecs_expr_node_t *node); ecs_script_var_t flecs_expr_find_var( ecs_script_t *script, const char *name); #endif int flecs_value_copy_to( ecs_world_t *world, ecs_value_t *dst, const ecs_expr_value_t *src); int flecs_value_move_to( ecs_world_t *world, ecs_value_t *dst, ecs_value_t *src); int flecs_value_binary( const ecs_script_t *script, const ecs_value_t *left, const ecs_value_t *right, ecs_value_t *out, ecs_script_token_kind_t operator); int flecs_value_unary( const ecs_script_t *script, const ecs_value_t *expr, ecs_value_t *out, ecs_script_token_kind_t operator); const char* flecs_script_parse_expr( ecs_script_parser_t *parser, const char *pos, ecs_script_token_kind_t left_oper, ecs_expr_node_t **out); const char* flecs_script_parse_initializer( ecs_script_parser_t *parser, const char *pos, char until, ecs_expr_initializer_t **node_out); void flecs_expr_to_str_buf( const ecs_world_t *world, const ecs_expr_node_t *expr, ecs_strbuf_t *buf, bool colors); bool flecs_string_is_interpolated( const char *str); char* flecs_string_escape( char *str); bool flecs_value_is_0( const ecs_value_t *value); bool flecs_expr_is_type_integer( ecs_entity_t type); bool flecs_expr_is_type_number( ecs_entity_t type); #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_script_runtime_t *r; ecs_script_template_t *template; /* Set when creating template */ ecs_entity_t template_entity; /* Set when creating template instance */ ecs_entity_t module; ecs_entity_t parent; ecs_script_entity_t *entity; ecs_entity_t with_relationship; int32_t with_relationship_sp; bool is_with_scope; bool dynamic_variable_binding; ecs_script_vars_t *vars; } 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__) int flecs_script_find_entity( ecs_script_eval_visitor_t *v, ecs_entity_t from, const char *path, int32_t *frame_offset, ecs_entity_t *out); ecs_script_var_t* flecs_script_find_var( const ecs_script_vars_t *vars, const char *name, int32_t *frame_offset); 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, ecs_expr_node_t **expr_ptr, ecs_value_t *value); void flecs_script_eval_visit_init( const ecs_script_impl_t *script, ecs_script_eval_visitor_t *v, const ecs_script_eval_desc_t *desc); void flecs_script_eval_visit_fini( ecs_script_eval_visitor_t *v, const ecs_script_eval_desc_t *desc); int flecs_script_eval_node( ecs_script_eval_visitor_t *v, ecs_script_node_t *node); int flecs_script_check_node( ecs_script_eval_visitor_t *v, ecs_script_node_t *node); int flecs_script_check_scope( ecs_script_eval_visitor_t *v, ecs_script_scope_t *node); /* Functions shared between check and eval visitor */ int flecs_script_eval_scope( ecs_script_eval_visitor_t *v, ecs_script_scope_t *node); int flecs_script_eval_id( ecs_script_eval_visitor_t *v, void *node, ecs_script_id_t *id); int flecs_script_eval_using( ecs_script_eval_visitor_t *v, ecs_script_using_t *node); int flecs_script_eval_const( ecs_script_eval_visitor_t *v, ecs_script_var_node_t *node); ecs_entity_t flecs_script_find_entity_action( const ecs_world_t *world, const char *path, void *ctx); #endif /** * @file addons/script/template.h * @brief Script template implementation. */ #ifndef FLECS_SCRIPT_TEMPLATE_H #define FLECS_SCRIPT_TEMPLATE_H extern ECS_COMPONENT_DECLARE(EcsScriptTemplateSetEvent); 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; }; #define ECS_TEMPLATE_SMALL_SIZE (36) /* Event used for deferring template instantiation */ typedef struct EcsScriptTemplateSetEvent { ecs_entity_t template_entity; ecs_entity_t *entities; void *data; int32_t count; /* Storage for small template types */ int64_t _align; /* Align data storage to 8 bytes */ char data_storage[ECS_TEMPLATE_SMALL_SIZE]; ecs_entity_t entity_storage; } EcsScriptTemplateSetEvent; 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_template_import( ecs_world_t *world); #endif struct ecs_script_runtime_t { ecs_allocator_t allocator; ecs_expr_stack_t expr_stack; ecs_stack_t stack; ecs_vec_t using; ecs_vec_t with; ecs_vec_t with_type_info; ecs_vec_t annot; }; 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); ecs_script_runtime_t* flecs_script_runtime_get( ecs_world_t *world); void flecs_script_register_builtin_functions( ecs_world_t *world); void flecs_function_import( ecs_world_t *world); int flecs_script_check( const ecs_script_t *script, const ecs_script_eval_desc_t *desc); #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_ELEM(column->data, column->ti->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 = ecs_table_entities(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, const ecs_instantiate_ctx_t *ctx) { 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; } /* If this is a pure override, make sure we have a concrete version of the * component. This relies on the fact that overrides always come after * concrete components in the table type so we can check the components * that have already been added to the child table type. */ if (ECS_HAS_ID_FLAG(id, AUTO_OVERRIDE)) { ecs_id_t concreteId = id & ~ECS_AUTO_OVERRIDE; flecs_child_type_insert(&diff.added, component_data, concreteId); continue; } int32_t storage_index = ecs_table_type_to_column_index(child_table, i); if (storage_index != -1) { component_data[diff.added.count] = child_data->columns[storage_index].data; } else { component_data[diff.added.count] = NULL; } diff.added.array[diff.added.count] = id; diff.added.count ++; diff.added_flags |= flecs_id_flags_get(world, id); } /* Table must contain children of base */ ecs_assert(childof_base_index != -1, ECS_INTERNAL_ERROR, NULL); /* If children are added to a prefab, make sure they are prefabs too */ if (table->flags & EcsTableIsPrefab) { if (flecs_child_type_insert( &diff.added, component_data, EcsPrefab) != -1) { childof_base_index ++; } } /* Instantiate the prefab child table for each new instance */ const ecs_entity_t *instances = ecs_table_entities(table); int32_t child_count = ecs_table_count(child_table); ecs_entity_t *child_ids = flecs_walloc_n(world, ecs_entity_t, child_count); 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. */ const ecs_entity_t *children = ecs_table_entities(child_table); #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 /* Attempt to reserve ids for children that have the same offset from * the instance as from the base prefab. This ensures stable ids for * instance children, even across networked applications. */ ecs_instantiate_ctx_t ctx_cur = {base, instance}; if (ctx) { ctx_cur = *ctx; } for (j = 0; j < child_count; j ++) { if ((uint32_t)children[j] < (uint32_t)ctx_cur.root_prefab) { /* Child id is smaller than root prefab id, can't use offset */ child_ids[j] = ecs_new(world); continue; } /* Get prefab offset, ignore lifecycle generation count */ ecs_entity_t prefab_offset = (uint32_t)children[j] - (uint32_t)ctx_cur.root_prefab; ecs_assert(prefab_offset != 0, ECS_INTERNAL_ERROR, NULL); /* First check if any entity with the desired id exists */ ecs_entity_t instance_child = (uint32_t)ctx_cur.root_instance + prefab_offset; ecs_entity_t alive_id = flecs_entities_get_alive(world, instance_child); if (alive_id && flecs_entities_is_alive(world, alive_id)) { /* Alive entity with requested id exists, can't use offset id */ child_ids[j] = ecs_new(world); continue; } /* Id is not in use. Make it alive & match the generation of the instance. */ instance_child = ctx_cur.root_instance + prefab_offset; flecs_entities_make_alive(world, instance_child); flecs_entities_ensure(world, instance_child); ecs_assert(ecs_is_alive(world, instance_child), ECS_INTERNAL_ERROR, NULL); child_ids[j] = instance_child; } /* Create children */ int32_t child_row; const ecs_entity_t *i_children = flecs_bulk_new(world, i_table, child_ids, &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, &ctx_cur); } } flecs_wfree_n(world, ecs_entity_t, child_count, child_ids); error: return; } void flecs_instantiate( ecs_world_t *world, ecs_entity_t base, ecs_table_t *table, int32_t row, int32_t count, const ecs_instantiate_ctx_t *ctx) { ecs_record_t *record = flecs_entities_get_any(world, base); ecs_table_t *base_table = record->table; if (!base_table) { return; } /* If prefab has union relationships, also set them on instance */ if (base_table->flags & EcsTableHasUnion) { const ecs_entity_t *entities = ecs_table_entities(table); ecs_id_record_t *union_idr = flecs_id_record_get(world, ecs_pair(EcsWildcard, EcsUnion)); ecs_assert(union_idr != NULL, ECS_INTERNAL_ERROR, NULL); const ecs_table_record_t *tr = flecs_id_record_get_table( union_idr, base_table); ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); int32_t i = 0, j, union_count = 0; do { ecs_id_t id = base_table->type.array[i]; if (ECS_PAIR_SECOND(id) == EcsUnion) { ecs_entity_t rel = ECS_PAIR_FIRST(id); ecs_entity_t tgt = ecs_get_target(world, base, rel, 0); ecs_assert(tgt != 0, ECS_INTERNAL_ERROR, NULL); for (j = row; j < (row + count); j ++) { ecs_add_pair(world, entities[j], rel, tgt); } union_count ++; } i ++; } while (union_count < tr->count); } if (!(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)) { ecs_os_perf_trace_push("flecs.instantiate"); 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, ctx); } ecs_os_perf_trace_pop("flecs.instantiate"); } } 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; const ecs_entity_t *entities = ecs_table_entities(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) { const ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); flecs_invoke_hook(world, table, tr, count, row, &entities[row + j],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; const ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); ecs_xtor_t dtor = ti->hooks.dtor; ecs_iter_action_t on_remove = ti->hooks.on_remove; const ecs_entity_t *entities = ecs_table_entities(table); for (j = 0; j < count; j ++) { ecs_entity_t e = entities[row + j]; if (on_remove) { flecs_invoke_hook(world, table, tr, count, row, &entities[row + j], id, ti, EcsOnRemove, on_remove); } void *ptr = flecs_sparse_remove_fast(idr->sparse, 0, e); ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); 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) { const ecs_entity_t *entities = ecs_table_entities(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) { const ecs_entity_t *entities = ecs_table_entities(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_table_diff_t *diff, ecs_flags32_t flags, ecs_flags64_t set_mask, bool construct, bool sparse) { ecs_assert(diff != NULL, ECS_INTERNAL_ERROR, NULL); const ecs_type_t *added = &diff->added; if (added->count) { ecs_flags32_t diff_flags = diff->added_flags|(table->flags & EcsTableHasTraversable); if (!diff_flags) { return; } if (sparse && (diff_flags & EcsTableHasSparse)) { flecs_sparse_on_add(world, table, row, count, added, construct); } if (diff_flags & EcsTableHasUnion) { flecs_union_on_add(world, table, row, count, added); } if (diff_flags & (EcsTableHasOnAdd|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_table_diff_t *diff) { ecs_assert(diff != NULL, ECS_INTERNAL_ERROR, NULL); const ecs_type_t *removed = &diff->removed; ecs_assert(count != 0, ECS_INTERNAL_ERROR, NULL); if (removed->count) { ecs_flags32_t diff_flags = diff->removed_flags|(table->flags & EcsTableHasTraversable); if (!diff_flags) { return; } if (diff_flags & EcsTableHasUnion) { flecs_union_on_remove(world, table, row, count, removed); } if (diff_flags & (EcsTableHasOnRemove|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 }); } if (diff_flags & EcsTableHasSparse) { flecs_sparse_on_remove(world, table, row, count, removed); } } } 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; const ecs_entity_t *entities = &ecs_table_entities(dst)[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_table_count(table) > row, ECS_INTERNAL_ERROR, NULL); flecs_notify_on_add( world, table, NULL, row, 1, diff, evt_flags, 0, ctor, true); ecs_assert(table == record->table, ECS_INTERNAL_ERROR, NULL); return record; } static int commit_indent = 0; 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_table_count(src_table) > 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); ecs_assert(record->table == src_table, 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); /* Copy entity & components from src_table to dst_table */ flecs_table_move(world, entity, entity, dst_table, dst_row, src_table, src_row, ctor); ecs_assert(record->table == src_table, ECS_INTERNAL_ERROR, NULL); /* 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, evt_flags, 0, ctor, true); flecs_update_name_index(world, src_table, dst_table, dst_row, 1); ecs_assert(record->table == dst_table, ECS_INTERNAL_ERROR, NULL); 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); 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 && src_table->flags & EcsTableHasUnion) { diff->added_flags |= EcsIdIsUnion; flecs_notify_on_add(world, src_table, src_table, ECS_RECORD_TO_ROW(record->row), 1, diff, evt_flags, 0, construct, true); } flecs_journal_end(); return; } commit_indent += 2; ecs_os_perf_trace_push("flecs.commit"); 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); } commit_indent -=2 ; ecs_os_perf_trace_pop("flecs.commit"); 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; } int32_t row = flecs_table_appendn(world, table, 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, (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 = ti->size; ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); void *ptr = ECS_ELEM(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_id_t id = flecs_column_id(table, j); ecs_type_t set_type = { .array = &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); } void flecs_add_ids( ecs_world_t *world, ecs_entity_t entity, ecs_id_t *ids, int32_t count) { ecs_record_t *r = flecs_entities_get(world, entity); ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT; flecs_table_diff_builder_init(world, &diff); ecs_table_t *table = ecs_get_table(world, entity); int32_t i; for (i = 0; i < count; i ++) { ecs_id_t id = ids[i]; table = flecs_find_table_add(world, table, id, &diff); } ecs_table_diff_t table_diff; flecs_table_diff_build_noalloc(&diff, &table_diff); flecs_commit(world, entity, r, table, &table_diff, true, 0); flecs_table_diff_builder_fini(world, &diff); } 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 = NULL; ecs_table_t *table; if ((table = r->table)) { if (id < FLECS_HI_COMPONENT_ID) { int16_t column_index = table->component_map[id]; if (column_index > 0) { ecs_column_t *column = &table->data.columns[column_index - 1]; ecs_type_info_t *ti = column->ti; dst.ptr = ECS_ELEM(column->data, ti->size, ECS_RECORD_TO_ROW(r->row)); dst.ti = ti; return dst; } else if (column_index < 0) { column_index = flecs_ito(int16_t, -column_index - 1); const ecs_table_record_t *tr = &table->_->records[column_index]; idr = (ecs_id_record_t*)tr->hdr.cache; if (idr->flags & EcsIdIsSparse) { dst.ptr = flecs_sparse_get_any(idr->sparse, 0, entity); dst.ti = idr->type_info; return dst; } } } else { idr = flecs_id_record_get(world, id); dst = flecs_get_component_ptr(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, const ecs_table_record_t *tr, int32_t count, int32_t row, const ecs_entity_t *entities, ecs_id_t id, const ecs_type_info_t *ti, ecs_entity_t event, ecs_iter_action_t hook) { 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.trs[0] = tr; it.row_fields = !!(((ecs_id_record_t*)tr->hdr.cache)->flags & EcsIdIsSparse); it.ref_fields = it.row_fields; 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); const ecs_entity_t *entities = &ecs_table_entities(table)[row]; ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert((row + count) <= ecs_table_count(table), ECS_INTERNAL_ERROR, NULL); 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; } const ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); if (idr->flags & EcsIdIsSparse) { int32_t j; for (j = 0; j < count; j ++) { flecs_invoke_hook(world, table, tr, 1, row, &entities[j], id, ti, EcsOnSet, on_set); } } else { ecs_assert(tr->column != -1, ECS_INTERNAL_ERROR, NULL); ecs_assert(tr->count == 1, ECS_INTERNAL_ERROR, NULL); if (on_set) { flecs_invoke_hook(world, table, tr, count, row, entities, 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; diff.added_flags = table->flags & EcsTableAddEdgeFlags; } if (removed) { diff.removed = *removed; if (src_table) { diff.removed_flags = src_table->flags & EcsTableRemoveEdgeFlags; } } 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); ecs_check(table != 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_flags32_t flags = table->flags & EcsTableAddEdgeFlags; if (table->flags & EcsTableHasIsA) { flags |= EcsTableHasOnAdd; } ecs_table_diff_t table_diff = { .added = table->type, .added_flags = flags }; flecs_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; } if (!ecs_id_is_valid(world, id)) { char *idstr = ecs_id_str(world, id); ecs_parser_error(name, expr, (ptr - expr), "id %s is invalid for add expression", idstr); ecs_os_free(idstr); goto error; } 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, "%s (provided) vs. %s (existing)", desc->symbol, sym); } 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) { if (!ecs_add_path_w_sep(world, result, scope, name, sep, root_sep)) { if (name[0] == '#') { /* Numerical ids should always return, unless it's invalid */ goto error; } } } 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: flecs_table_diff_builder_fini(world, &diff); 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; ecs_check(ecs_is_valid(world, desc->parent), ECS_INVALID_PARAMETER, "ecs_entity_desc_t::parent is not valid"); } /* 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 (ecs_is_deferred(world)) { 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, .removed_flags = table->flags & EcsTableRemoveEdgeFlags }; 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; const ecs_entity_t *entities = ecs_table_entities(table); int32_t i, count = ecs_table_count(table); 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); 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 i, count = ecs_vec_count(&world->store.marked_ids); /* Collect all ids that need to be deleted */ flecs_on_delete_mark(world, id, action, delete_id); /* 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); /* 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); 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); /* If any components got deleted, cleanup type info. Delaying this * ensures that type info remains available during cleanup. */ count = ecs_vec_count(&world->store.deleted_components); ecs_entity_t *comps = ecs_vec_first(&world->store.deleted_components); for (i = 0; i < count; i ++) { flecs_type_info_free(world, comps[i]); } ecs_vec_clear(&world->store.deleted_components); ecs_log_pop_2(); } } void 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_os_perf_trace_push("flecs.delete"); 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, .removed_flags = table->flags & EcsTableRemoveEdgeFlags }; 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: ecs_os_perf_trace_pop("flecs.delete"); 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, .added_flags = dst_table->flags & EcsTableAddEdgeFlags }; ecs_record_t *dst_r = flecs_entities_get(world, dst); /* Note 'ctor' parameter below is set to 'false' if the value will be copied, since flecs_table_move will call a copy constructor */ flecs_new_entity(world, dst, dst_r, dst_table, &diff, !copy_value, 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_id_t id = flecs_column_id(dst_table, i); ecs_type_t type = { .array = &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; } #define ecs_get_low_id(table, r, id)\ ecs_assert(table->component_map != NULL, ECS_INTERNAL_ERROR, NULL);\ int16_t column_index = table->component_map[id];\ if (column_index > 0) {\ ecs_column_t *column = &table->data.columns[column_index - 1];\ return ECS_ELEM(column->data, column->ti->size, \ ECS_RECORD_TO_ROW(r->row));\ } else if (column_index < 0) {\ column_index = flecs_ito(int16_t, -column_index - 1);\ const ecs_table_record_t *tr = &table->_->records[column_index];\ ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache;\ if (idr->flags & EcsIdIsSparse) {\ return flecs_sparse_get_any(idr->sparse, 0, entity);\ }\ } 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; } if (id < FLECS_HI_COMPONENT_ID) { ecs_get_low_id(table, r, id); if (!(table->flags & EcsTableHasIsA)) { 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; } if (id < FLECS_HI_COMPONENT_ID) { ecs_get_low_id(table, r, id); 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 table->data.entities[ECS_RECORD_TO_ROW(record->row)]; error: return 0; } const void* ecs_record_get_id( const ecs_world_t *stage, const ecs_record_t *r, ecs_id_t 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 (!table || !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"); ecs_entity_t bs_id = id | ECS_TOGGLE; ecs_add_id(world, entity, bs_id); if (flecs_defer_enable(stage, entity, id, enable)) { return; } ecs_record_t *r = flecs_entities_get(world, entity); ecs_table_t *table = r->table; int32_t index = ecs_table_get_type_index(world, table, bs_id); ecs_assert(index != -1, ECS_INTERNAL_ERROR, NULL); 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; } if (id < FLECS_HI_COMPONENT_ID) { if (table->component_map[id] != 0) { return true; } } else { ecs_id_record_t *idr = flecs_id_record_get(world, id); 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; int32_t 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) { 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; } if (id < FLECS_HI_COMPONENT_ID) { return table->component_map[id] != 0; } else { ecs_id_record_t *idr = flecs_id_record_get(world, id); if (idr) { ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); if (tr) { return true; } } } error: return false; } ecs_entity_t ecs_get_target( const ecs_world_t *world, ecs_entity_t entity, ecs_entity_t rel, int32_t index) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_check(rel != 0, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); ecs_record_t *r = flecs_entities_get(world, entity); ecs_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); if (flecs_entities_is_alive(world, entity_with_generation)) { ecs_record_t *r = flecs_entities_get(world, entity_with_generation); if (r && r->table) { int32_t row = ECS_RECORD_TO_ROW(r->row); ecs_entity_t *entities = r->table->data.entities; entities[row] = entity_with_generation; } } } 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, false); ecs_strbuf_appendch(buf, ','); ecs_get_path_w_sep_buf(world, 0, obj, NULL, NULL, buf, false); ecs_strbuf_appendch(buf, ')'); } else { ecs_entity_t e = id & ECS_COMPONENT_MASK; ecs_get_path_w_sep_buf(world, 0, e, NULL, NULL, buf, false); } error: return; } char* ecs_id_str( const ecs_world_t *world, ecs_id_t id) { ecs_strbuf_t buf = ECS_STRBUF_INIT; ecs_id_str_buf(world, id, &buf); return ecs_strbuf_get(&buf); } 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, false); ecs_strbuf_appendlit(&buf, " ["); const ecs_type_t *type = ecs_get_type(world, entity); if (type) { ecs_type_str_buf(world, type, &buf); } ecs_strbuf_appendch(&buf, ']'); return ecs_strbuf_get(&buf); error: return NULL; } 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); const ecs_table_record_t *tr = flecs_id_record_get_table(idr, start_table); if (tr) { const ecs_type_info_t *ti = idr->type_info; 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, tr, 1, row, &entity,cmd->id, 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); ecs_table_t *next = flecs_find_table_add(world, table, id, diff); if (next != table) { table = next; 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); diff->removed_flags |= table->flags & EcsTableRemoveEdgeFlags; } 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); } } else { ecs_os_memcpy(ptr.ptr, cmd->is._1.value, ti->size); } flecs_stack_free(cmd->is._1.value, cmd->is._1.size); cmd->is._1.value = NULL; if (cmd->kind == EcsCmdSet) { /* A set operation is add + copy + modified. We just did * the add and copy, so the only thing that's left is a * modified command, which will call the OnSet * observers. */ cmd->kind = EcsCmdModified; } else { /* If this was an ensure, nothing's left to be done */ cmd->kind = EcsCmdSkip; } } else { /* The entity no longer has the component which means that * there was a remove command for the component in the * command queue. In that case skip the command. */ cmd->kind = EcsCmdSkip; } break; } case 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 && r->table) { ecs_table_diff_t add_diff = ECS_TABLE_DIFF_INIT; add_diff.added = added; add_diff.added_flags = diff->added_flags; flecs_defer_begin(world, world->stages[0]); flecs_notify_on_add(world, r->table, start_table, ECS_RECORD_TO_ROW(r->row), 1, &add_diff, 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 && !stage->cmd_flushing) { ecs_os_perf_trace_push("flecs.commands.merge"); /* 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; } do { ecs_stage_t *dst_stage = flecs_stage_from_world(&world); ecs_commands_t *commands = stage->cmd; ecs_vec_t *queue = &commands->queue; if (stage->cmd == &stage->cmd_stack[0]) { stage->cmd = &stage->cmd_stack[1]; } else { stage->cmd = &stage->cmd_stack[0]; } if (!ecs_vec_count(queue)) { break; } stage->cmd_flushing = true; /* 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); 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); } } stage->cmd_flushing = false; flecs_stack_reset(&commands->stack); ecs_vec_clear(queue); 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); } } while (true); ecs_os_perf_trace_pop("flecs.commands.merge"); 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, bool escape) { 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, escape); if (!sep[1]) { ecs_strbuf_appendch(buf, sep[0]); } else { ecs_strbuf_appendstr(buf, sep); } } } else if (prefix && prefix[0]) { if (!prefix[1]) { ecs_strbuf_appendch(buf, prefix[0]); } else { ecs_strbuf_appendstr(buf, prefix); } } const EcsIdentifier *id = ecs_get_pair( world, child, EcsIdentifier, EcsName); if (id) { name = id->value; name_len = id->length; } } } if (name) { /* Check if we need to escape separator character */ const char *sep_in_name = NULL; if (!sep[1]) { sep_in_name = strchr(name, sep[0]); } if (sep_in_name || escape) { const char *name_ptr; char ch; for (name_ptr = name; (ch = name_ptr[0]); name_ptr ++) { char esc[3]; if (ch != sep[0]) { if (escape) { flecs_chresc(esc, ch, '\"'); ecs_strbuf_appendch(buf, esc[0]); if (esc[1]) { ecs_strbuf_appendch(buf, esc[1]); } } else { ecs_strbuf_appendch(buf, ch); } } else { if (!escape) { ecs_strbuf_appendch(buf, '\\'); ecs_strbuf_appendch(buf, sep[0]); } else { ecs_strbuf_appendlit(buf, "\\\\"); flecs_chresc(esc, ch, '\"'); ecs_strbuf_appendch(buf, esc[0]); if (esc[1]) { ecs_strbuf_appendch(buf, esc[1]); } } } } } else { ecs_strbuf_appendstrn(buf, name, name_len); } } else { ecs_strbuf_appendch(buf, '#'); ecs_strbuf_appendint(buf, flecs_uto(int64_t, (uint32_t)child)); } return cur != 0; } bool flecs_name_is_id( const char *name) { ecs_assert(name != NULL, ECS_INTERNAL_ERROR, NULL); if (name[0] == '#') { /* If name is not just digits it's not an id */ const char *ptr; char ch; for (ptr = name + 1; (ch = ptr[0]); ptr ++) { if (!isdigit(ch)) { return false; } } return true; } return false; } ecs_entity_t flecs_name_to_id( const char *name) { ecs_assert(name != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(name[0] == '#', ECS_INVALID_PARAMETER, NULL); ecs_entity_t res = flecs_ito(uint64_t, atoll(name + 1)); if (res >= UINT32_MAX) { return 0; /* Invalid id */ } return res; } static ecs_entity_t flecs_get_builtin( const char *name) { if (name[0] == '.' && name[1] == '\0') { return EcsThis; } else if (name[0] == '*' && name[1] == '\0') { return EcsWildcard; } else if (name[0] == '_' && name[1] == '\0') { return EcsAny; } else if (name[0] == '$' && name[1] == '\0') { return EcsVariable; } return 0; } static 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 || ptr[0]) { 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 *error) { ecs_assert(error != NULL, ECS_INTERNAL_ERROR, NULL); bool start_from_root = false; const char *path = *path_ptr; if (flecs_is_root_path(path, prefix)) { path += ecs_os_strlen(prefix); parent = 0; start_from_root = true; } if (path[0] == '#') { parent = flecs_name_to_id(path); if (!parent && ecs_os_strncmp(path, "#0", 2)) { *error = true; return 0; } path ++; while (path[0] && isdigit(path[0])) { path ++; /* Skip id part of path */ } /* Skip next separator so that the returned path points to the next * name element. */ ecs_size_t sep_len = ecs_os_strlen(sep); if (!ecs_os_strncmp(path, sep, ecs_os_strlen(sep))) { path += sep_len; } start_from_root = true; } if (!start_from_root && !parent && new_entity) { parent = ecs_get_scope(world); } *path_ptr = path; return parent; } 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) }, .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, bool escape) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(buf != NULL, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); if (child == EcsWildcard) { ecs_strbuf_appendch(buf, '*'); return; } if (child == EcsAny) { ecs_strbuf_appendch(buf, '_'); return; } if (!sep) { sep = "."; } if (!child || parent != child) { flecs_path_append(world, parent, child, sep, prefix, buf, escape); } else { ecs_strbuf_appendstrn(buf, "", 0); } error: return; } char* ecs_get_path_w_sep( const ecs_world_t *world, ecs_entity_t parent, ecs_entity_t child, const char *sep, const char *prefix) { ecs_strbuf_t buf = ECS_STRBUF_INIT; ecs_get_path_w_sep_buf(world, parent, child, sep, prefix, &buf, false); return ecs_strbuf_get(&buf); } ecs_entity_t ecs_lookup_child( const ecs_world_t *world, ecs_entity_t parent, const char *name) { ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); world = ecs_get_world(world); 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); ecs_check(!parent || ecs_is_valid(world, parent), ECS_INVALID_PARAMETER, NULL); const ecs_world_t *stage = world; world = ecs_get_world(world); ecs_entity_t e = flecs_get_builtin(path); if (e) { return e; } e = flecs_name_index_find(&world->aliases, path, 0, 0); if (e) { return e; } char buff[ECS_NAME_BUFFER_LENGTH], *elem = buff; const char *ptr; int32_t size = ECS_NAME_BUFFER_LENGTH; ecs_entity_t cur; bool lookup_path_search = false; const ecs_entity_t *lookup_path = 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 = "."; } bool error = false; parent = flecs_get_parent_from_path( stage, parent, &path, sep, prefix, true, &error); if (error) { return 0; } if (parent && !(parent = ecs_get_alive(world, parent))) { return 0; } if (!path[0]) { return parent; } if (!sep[0]) { return ecs_lookup_child(world, parent, path); } 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); const ecs_world_t *real_world = world; if (flecs_poly_is(world, ecs_stage_t)) { real_world = ecs_get_world(world); } if (!sep) { sep = "."; } if (!path) { if (!entity) { entity = ecs_new(world); } if (parent) { ecs_add_pair(world, entity, EcsChildOf, entity); } return entity; } bool root_path = flecs_is_root_path(path, prefix); bool error = false; parent = flecs_get_parent_from_path( world, parent, &path, sep, prefix, !entity, &error); if (error) { /* Invalid id */ ecs_err("invalid identifier: '%s'", path); return 0; } char buff[ECS_NAME_BUFFER_LENGTH]; const char *ptr = path; char *elem = buff; int32_t size = ECS_NAME_BUFFER_LENGTH; /* If we're in deferred/readonly mode suspend it, so that the name index is * immediately updated. Without this, we could create multiple entities for * the same name in a single command queue. */ bool suspend_defer = ecs_is_deferred(world) && !(real_world->flags & EcsWorldMultiThreaded); 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. */ #ifdef FLECS_SCRIPT #endif bool ecs_id_match( ecs_id_t id, ecs_id_t pattern) { if (id == pattern) { return true; } if (ECS_HAS_ID_FLAG(pattern, PAIR)) { if (!ECS_HAS_ID_FLAG(id, PAIR)) { return false; } ecs_entity_t id_first = ECS_PAIR_FIRST(id); ecs_entity_t id_second = ECS_PAIR_SECOND(id); ecs_entity_t pattern_first = ECS_PAIR_FIRST(pattern); ecs_entity_t pattern_second = ECS_PAIR_SECOND(pattern); ecs_check(id_first != 0, ECS_INVALID_PARAMETER, "first element of pair cannot be 0"); ecs_check(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; } } ecs_id_t ecs_id_from_str( const ecs_world_t *world, const char *expr) { #ifdef FLECS_SCRIPT ecs_id_t result; /* Temporarily disable parser logging */ int prev_level = ecs_log_set_level(-3); if (!flecs_id_parse(world, NULL, expr, &result)) { /* Invalid expression */ ecs_log_set_level(prev_level); return 0; } ecs_log_set_level(prev_level); return result; #else (void)world; (void)expr; ecs_abort(ECS_UNSUPPORTED, "ecs_id_from_str requires FLECS_SCRIPT addon"); #endif } /** * @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, trs, ecs_table_record_t*, it->field_count); INIT_CACHE(it, stack, fields, variables, ecs_var_t, it->variable_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, trs, ecs_table_record_t*, it->field_count); FINI_CACHE(it, variables, ecs_var_t, it->variable_count); ecs_stage_t *stage = flecs_stage_from_world(&world); flecs_stack_restore_cursor(&stage->allocators.iter_stack, it->priv_.cache.stack_cursor); } /* --- Public API --- */ void* ecs_field_w_size( const ecs_iter_t *it, size_t size, int8_t index) { ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, "operation invalid before calling next()"); ecs_check(index >= 0, ECS_INVALID_PARAMETER, "invalid field index %d", index); ecs_check(index < it->field_count, ECS_INVALID_PARAMETER, "field index %d out of bounds", index); ecs_check(!size || ecs_field_size(it, index) == size || !ecs_field_size(it, index), ECS_INVALID_PARAMETER, "mismatching size for field %d", index); (void)size; const ecs_table_record_t *tr = it->trs[index]; if (!tr) { ecs_assert(!ecs_field_is_set(it, index), ECS_INTERNAL_ERROR, NULL); return NULL; } ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; ecs_assert(!(idr->flags & EcsIdIsSparse), ECS_INVALID_OPERATION, "use ecs_field_at to access fields for sparse components"); (void)idr; ecs_entity_t src = it->sources[index]; ecs_table_t *table; int32_t row; if (!src) { table = it->table; row = it->offset; } else { ecs_record_t *r = flecs_entities_get(it->real_world, src); table = r->table; row = ECS_RECORD_TO_ROW(r->row); } ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(tr->hdr.table == table, ECS_INTERNAL_ERROR, NULL); int32_t column_index = tr->column; ecs_assert(column_index != -1, ECS_NOT_A_COMPONENT, "only components can be fetched with fields"); ecs_assert(column_index >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(column_index < table->column_count, ECS_INTERNAL_ERROR, NULL); ecs_column_t *column = &table->data.columns[column_index]; ecs_assert((row < table->data.count) || (it->query && (it->query->flags & EcsQueryMatchEmptyTables)), ECS_INTERNAL_ERROR, NULL); if (!size) { size = (size_t)column->ti->size; } return ECS_ELEM(column->data, (ecs_size_t)size, row); error: return NULL; } void* ecs_field_at_w_size( const ecs_iter_t *it, size_t size, int8_t index, int32_t row) { ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, "operation invalid before calling next()"); ecs_check(index >= 0, ECS_INVALID_PARAMETER, "invalid field index %d", index); ecs_check(index < it->field_count, ECS_INVALID_PARAMETER, "field index %d out of bounds", index); ecs_check(!size || ecs_field_size(it, index) == size || !ecs_field_size(it, index), ECS_INVALID_PARAMETER, "mismatching size for field %d", index); const ecs_table_record_t *tr = it->trs[index]; if (!tr) { ecs_assert(!ecs_field_is_set(it, index), ECS_INTERNAL_ERROR, NULL); return NULL; } ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; ecs_assert((idr->flags & EcsIdIsSparse), ECS_INVALID_OPERATION, "use ecs_field to access fields for non-sparse components"); ecs_assert(it->row_fields & (1ull << index), ECS_INTERNAL_ERROR, NULL); ecs_entity_t src = it->sources[index]; if (!src) { src = ecs_table_entities(it->table)[row + it->offset]; } return flecs_sparse_get_any(idr->sparse, flecs_uto(int32_t, size), src); error: return NULL; } bool ecs_field_is_readonly( const ecs_iter_t *it, int8_t index) { ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, "operation invalid before calling next()"); 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, int8_t index) { ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, "operation invalid before calling next()"); ecs_check(it->query != NULL, ECS_INVALID_PARAMETER, "operation only valid for query iterators"); ecs_check(it->query->terms != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(index >= 0, ECS_INVALID_PARAMETER, "invalid field index %d", index); ecs_check(index < it->field_count, ECS_INVALID_PARAMETER, "field index %d out of bounds", index); const ecs_term_t *term = &it->query->terms[index]; return term->inout == EcsOut; error: return false; } bool ecs_field_is_set( const ecs_iter_t *it, int8_t index) { ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, "operation invalid before calling next()"); ecs_check(index >= 0, ECS_INVALID_PARAMETER, "invalid field index %d", index); ecs_check(index < it->field_count, ECS_INVALID_PARAMETER, "field index %d out of bounds", index); return it->set_fields & (1llu << (index)); error: return false; } bool ecs_field_is_self( const ecs_iter_t *it, int8_t index) { ecs_check(index >= 0, ECS_INVALID_PARAMETER, "invalid field index %d", index); ecs_check(index < it->field_count, ECS_INVALID_PARAMETER, "field index %d out of bounds", index); return it->sources == NULL || it->sources[index] == 0; error: return false; } ecs_id_t ecs_field_id( const ecs_iter_t *it, int8_t index) { ecs_check(index >= 0, ECS_INVALID_PARAMETER, "invalid field index %d", index); ecs_check(index < it->field_count, ECS_INVALID_PARAMETER, "field index %d out of bounds", index); return it->ids[index]; error: return 0; } int32_t ecs_field_column( const ecs_iter_t *it, int8_t index) { ecs_check(index >= 0, ECS_INVALID_PARAMETER, "invalid field index %d", index); ecs_check(index < it->field_count, ECS_INVALID_PARAMETER, "field index %d out of bounds", index); const ecs_table_record_t *tr = it->trs[index]; if (tr) { return tr->index; } else { return -1; } error: return 0; } ecs_entity_t ecs_field_src( const ecs_iter_t *it, int8_t index) { ecs_check(index >= 0, ECS_INVALID_PARAMETER, "invalid field index %d", index); ecs_check(index < it->field_count, ECS_INVALID_PARAMETER, "field index %d out of bounds", index); if (it->sources) { return it->sources[index]; } else { return 0; } error: return 0; } size_t ecs_field_size( const ecs_iter_t *it, int8_t index) { ecs_check(index >= 0, ECS_INVALID_PARAMETER, "invalid field index %d", index); ecs_check(index < it->field_count, ECS_INVALID_PARAMETER, "field index %d out of bounds", index); return (size_t)it->sizes[index]; error: return 0; } 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; int8_t 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); int32_t count = 0; while (ecs_iter_next(it)) { count += it->count; } return count; error: return 0; } ecs_entity_t ecs_iter_first( ecs_iter_t *it) { ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); ECS_BIT_SET(it->flags, EcsIterNoData); ecs_entity_t result = 0; if (ecs_iter_next(it)) { result = it->entities[0]; ecs_iter_fini(it); } return result; error: return 0; } bool ecs_iter_is_true( ecs_iter_t *it) { ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); ECS_BIT_SET(it->flags, EcsIterNoData); bool result = ecs_iter_next(it); if (result) { ecs_iter_fini(it); } return result; error: return false; } 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_table_entities(table)[var->range.offset]; } } } 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_table_entities(table)[range->offset]; } 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 }; } bool ecs_page_next( ecs_iter_t *it) { ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(it->chain_it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(it->next == ecs_page_next, ECS_INVALID_PARAMETER, NULL); ecs_iter_t *chain_it = it->chain_it; do { if (!ecs_iter_next(chain_it)) { goto depleted; } ecs_page_iter_t *iter = &it->priv_.iter.page; /* Copy everything up to the private iterator data */ ecs_os_memcpy(it, chain_it, offsetof(ecs_iter_t, priv_)); if (!chain_it->table) { goto yield; /* Task query */ } int32_t offset = iter->offset; int32_t limit = iter->limit; if (!(offset || limit)) { if (it->count) { goto yield; } else { goto depleted; } } int32_t count = it->count; int32_t remaining = iter->remaining; if (offset) { if (offset > count) { /* No entities to iterate in current table */ iter->offset -= count; it->count = 0; continue; } else { iter->offset = 0; it->offset = offset; count = it->count -= offset; it->entities = &(ecs_table_entities(it->table)[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: return true; done: /* Cleanup iterator resources if it wasn't yet depleted */ ecs_iter_fini(chain_it); depleted: error: return false; } ecs_iter_t ecs_worker_iter( const ecs_iter_t *it, int32_t index, int32_t count) { ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(it->next != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(count > 0, ECS_INVALID_PARAMETER, NULL); ecs_check(index >= 0, ECS_INVALID_PARAMETER, "invalid 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 }; } bool ecs_worker_next( ecs_iter_t *it) { ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(it->chain_it != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(it->next == ecs_worker_next, ECS_INVALID_PARAMETER, NULL); ecs_iter_t *chain_it = it->chain_it; ecs_worker_iter_t *iter = &it->priv_.iter.worker; int32_t res_count = iter->count, res_index = iter->index; int32_t per_worker, first; do { if (!ecs_iter_next(chain_it)) { return false; } /* Copy everything up to the private iterator data */ ecs_os_memcpy(it, chain_it, offsetof(ecs_iter_t, priv_)); int32_t count = it->count; per_worker = count / res_count; first = per_worker * res_index; count -= per_worker * res_count; if (count) { if (res_index < count) { per_worker ++; first += res_index; } else { first += count; } } if (!per_worker && it->table == NULL) { if (res_index == 0) { return true; } else { // chained iterator was not yet cleaned up // since it returned true from ecs_iter_next, so clean it up here. ecs_iter_fini(chain_it); return false; } } } while (!per_worker); it->frame_offset += first; it->count = per_worker; it->offset += first; it->entities = &(ecs_table_entities(it->table)[it->offset]); return true; 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: if (file) { fclose(file); } 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); if (id_fwc != id) { iders[count] = flecs_event_id_record_get_if(er, id_fwc); count += iders[count] != 0; } if (id_swc != id) { iders[count] = flecs_event_id_record_get_if(er, id_swc); count += iders[count] != 0; } if (id_pwc != id) { iders[count] = flecs_event_id_record_get_if(er, id_pwc); count += iders[count] != 0; } } else if (id != EcsWildcard) { iders[count] = flecs_event_id_record_get_if(er, EcsWildcard); count += iders[count] != 0; } } return count; } 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; it->up_fields = 1; if (entity_count) { it->entities = ecs_table_entities(table); } 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; } const ecs_entity_t *entities = ecs_table_entities(table); 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; it->up_fields = 0; } 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); const ecs_entity_t *entities = ecs_table_entities(table); 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) { const ecs_entity_t *entities = &ecs_table_entities(table)[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, const ecs_entity_t *entities, int32_t count, ecs_entity_t src, ecs_event_id_record_t **iders, int32_t ider_count) { if (!count) { return; } ecs_entity_t old_src = it->sources[0]; ecs_table_t *old_table = it->table; ecs_table_t *old_other_table = it->other_table; const ecs_entity_t *old_entities = it->entities; int32_t old_count = it->count; int32_t old_offset = it->offset; int32_t i; for (i = 0; i < count; i ++) { ecs_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_table_record_t *tr, 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) { const ecs_entity_t *entities = &ecs_table_entities(table)[offset]; flecs_invoke_hook(world, table, tr, count, offset, entities, 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_ELEM(column->data, size, it->offset); } } return NULL; } static void flecs_emit_forward_up( ecs_world_t *world, const ecs_event_record_t *er, const ecs_event_record_t *er_onset, const ecs_type_t *emit_ids, ecs_iter_t *it, ecs_table_t *table, ecs_id_record_t *idr, ecs_vec_t *stack, ecs_vec_t *reachable_ids, int32_t depth); 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, 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; ECS_CONST_CAST(int32_t*, it->sizes)[0] = 0; /* safe, owned by observer */ it->up_fields = 1; int32_t storage_i = ecs_table_type_to_column_index(tgt_table, column); 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->trs[0] = &tgt_table->_->records[column]; 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. */ ecs_table_record_t *base_tr = ECS_CONST_CAST( ecs_table_record_t*, it->trs[0]); void *ptr = flecs_override(it, emit_ids, id, table, idr); if (ptr) { override = true; } 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; it->trs[0] = tr; 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->trs[0] = base_tr; } } it->up_fields = 0; } 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, 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, 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; } 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, 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, int32_t depth) { 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 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; } if (idr == tgt_idr) { char *idstr = ecs_id_str(world, idr->id); ecs_assert(idr != tgt_idr, ECS_CYCLE_DETECTED, idstr); ecs_os_free(idstr); return; } /* 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, depth); 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, 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, int32_t depth) { if (depth >= FLECS_DAG_DEPTH_MAX) { char *idstr = ecs_id_str(world, idr->id); ecs_assert(depth < FLECS_DAG_DEPTH_MAX, ECS_CYCLE_DETECTED, idstr); ecs_os_free(idstr); return; } 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, depth + 1); } 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, 0); 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); flecs_emit_forward_id(world, er, er_onset, emit_ids, it, table, rc_idr, elem->src, r->table, tr->index, trav); } } /* Propagate events for new reachable ids downwards */ if (table->_->traversable_count) { int32_t i; const ecs_entity_t *entities = ecs_table_entities(table); entities = ECS_ELEM_T(entities, ecs_entity_t, it->offset); for (i = 0; i < it->count; i ++) { ecs_record_t *r = flecs_entities_get(world, entities[i]); if (r->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; /* If entities already have the component, don't propagate */ if (flecs_id_record_get_table(rc_idr, it->table)) { continue; } 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_os_perf_trace_push("flecs.emit"); ecs_time_t t = {0}; bool measure_time = world->flags & EcsWorldMeasureSystemTime; if (measure_time) { ecs_time_measure(&t); } const ecs_type_t *ids = desc->ids; ecs_entity_t event = desc->event; ecs_table_t *table = desc->table, *other_table = desc->other_table; int32_t offset = desc->offset; int32_t i, count = desc->count; ecs_flags32_t table_flags = table->flags; /* Deferring cannot be suspended for observers */ 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; ecs_size_t sizes_cache = 0; const ecs_table_record_t* trs_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, .sizes = &sizes_cache, .trs = (const ecs_table_record_t**)&trs_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_table_entities(table)[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]; ecs_assert(id == EcsAny || !ecs_id_is_wildcard(id), ECS_INVALID_PARAMETER, "cannot emit wildcard ids"); int32_t ider_i, ider_count = 0; bool is_pair = ECS_IS_PAIR(id); void *override_ptr = NULL; bool override_base_added = false; ecs_table_record_t *base_tr = 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, NULL); 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) { 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); override_ptr = base_table->data.columns[base_column].data; override_ptr = ECS_ELEM(override_ptr, ti->size, base_row); } /* For ids with override policy, check if base was added * in same operation. This will determine later on * whether we need to emit an OnSet event. */ if (!(idr->flags & (EcsIdOnInstantiateInherit|EcsIdOnInstantiateDontInherit))) { int32_t base_i; for (base_i = 0; base_i < id_count; base_i ++) { ecs_id_t base_id = id_array[base_i]; if (!ECS_IS_PAIR(base_id)) { continue; } if (ECS_PAIR_FIRST(base_id) != EcsIsA) { continue; } if (ECS_PAIR_SECOND(base_id) == (uint32_t)base) { override_base_added = true; } } } } } } } 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 storage_i; it.trs[0] = tr; 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 (!ecs_id_is_wildcard(id) && (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 = ecs_table_entities(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_ELEM(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, tr, ti, ptr, override_ptr, offset, count); /* If the base for this component got added in the same * operation, generate an OnSet event as this is the * first time this value is observed for the entity. */ if (override_base_added) { 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); for (ider_set_i = 0; ider_set_i < ider_set_count; ider_set_i ++) { ecs_event_id_record_t *ider = iders_set[ider_set_i]; flecs_observers_invoke( world, &ider->self, &it, table, 0); ecs_assert(it.event_cur == evtx, ECS_INTERNAL_ERROR, NULL); flecs_observers_invoke( world, &ider->self_up, &it, table, 0); ecs_assert(it.event_cur == evtx, ECS_INTERNAL_ERROR, NULL); } } } else if (er_onset && it.other_table) { /* 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.trs[0] = base_tr; it.up_fields = 1; 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.trs[0] = tr; } } } } } /* 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; ecs_os_perf_trace_pop("flecs.emit"); if (measure_time) { world->info.emit_time_total += (ecs_ftime_t)ecs_time_measure(&t); } return; } void ecs_emit( ecs_world_t *stage, ecs_event_desc_t *desc) { ecs_world_t *world = ECS_CONST_CAST(ecs_world_t*, ecs_get_world(stage)); ecs_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 == 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 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) { 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); } ecs_termset_t row_fields = it->row_fields; it->row_fields = query->row_fields; bool match_this = query->flags & EcsQueryMatchThis; if (match_this) { callback(it); ecs_os_inc(&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; } const 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); ecs_os_inc(&query->eval_count); } else if (observer_src == e) { ecs_entity_t dummy = 0; it->entities = &dummy; if (!src) { it->sources[0] = e; } callback(it); ecs_os_inc(&query->eval_count); it->sources[0] = src; break; } } it->entities = entities; it->count = count; } it->row_fields = row_fields; 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); 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) { 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 = query; it->ref_fields = query->fixed_fields | query->row_fields; it->row_fields = query->row_fields; 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); } 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); 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); } 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; } ecs_table_t *table = it->table; ecs_table_t *prev_table = it->other_table; int8_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; } } impl->last_event_id[0] = it->event_cur; /* 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. */ int8_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.trs[pivot_field] = it->trs[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); } else { /* While the observer query was strictly speaking evaluated, it's more * useful to measure how often the observer was actually invoked. */ o->query->eval_count --; } done: return; } static void flecs_multi_observer_invoke_no_query( ecs_iter_t *it) { ecs_observer_t *o = it->ctx; flecs_poly_assert(o, ecs_observer_t); ecs_world_t *world = it->real_world; ecs_table_t *table = it->table; ecs_iter_t user_it = *it; user_it.ctx = o->ctx; user_it.callback_ctx = o->callback_ctx; user_it.run_ctx = o->run_ctx; user_it.param = it->param; user_it.callback = o->callback; user_it.system = o->entity; user_it.event = it->event; ecs_entity_t old_system = flecs_stage_set_system( world->stages[0], o->entity); ecs_table_lock(it->world, table); if (o->run) { user_it.next = flecs_default_next_callback; o->run(&user_it); } else { user_it.callback(&user_it); } ecs_table_unlock(it->world, table); flecs_stage_set_system(world->stages[0], old_system); } /* 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, bool yield_on_remove) { ecs_run_action_t run = o->run; if (!run) { run = flecs_multi_observer_invoke_no_query; } 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_entity_t event = o->events[i]; /* We only yield for OnRemove events if the observer is deleted. */ if (event == EcsOnRemove) { if (!yield_on_remove) { continue; } } else { if (yield_on_remove) { continue; } } ecs_iter_t it = 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(int8_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_assert(child_desc->query.flags & EcsQueryNested, ECS_INTERNAL_ERROR, NULL); ecs_observer_t *child_observer = flecs_observer_init( world, 0, child_desc); if (!child_observer) { return -1; } ecs_observer_impl_t *impl = flecs_observer_impl(o); ecs_vec_append_t(&world->allocator, &impl->children, ecs_observer_t*)[0] = child_observer; child_observer->entity = o->entity; return 0; } static int flecs_multi_observer_init( ecs_world_t *world, ecs_observer_t *o, const ecs_observer_desc_t *desc) { ecs_observer_impl_t *impl = flecs_observer_impl(o); /* Create last event id for filtering out the same event that arrives from * more than one term */ impl->last_event_id = ecs_os_calloc_t(int32_t); /* Mark observer as multi observer */ impl->flags |= EcsObserverIsMulti; /* Vector that stores a single-component observer for each query term */ ecs_vec_init_t(&world->allocator, &impl->children, ecs_observer_t*, 2); /* Create a child observer for each term in the query */ ecs_query_t *query = o->query; ecs_observer_desc_t child_desc = *desc; child_desc.last_event_id = impl->last_event_id; child_desc.run = NULL; child_desc.callback = flecs_multi_observer_builtin_run; child_desc.ctx = o; child_desc.ctx_free = NULL; child_desc.query.expr = NULL; child_desc.callback_ctx = NULL; child_desc.callback_ctx_free = NULL; child_desc.run_ctx = NULL; child_desc.run_ctx_free = NULL; child_desc.yield_existing = false; child_desc.flags_ &= ~(EcsObserverYieldOnCreate|EcsObserverYieldOnDelete); ecs_os_zeromem(&child_desc.entity); ecs_os_zeromem(&child_desc.query.terms); ecs_os_zeromem(&child_desc.query); ecs_os_memcpy_n(child_desc.events, o->events, ecs_entity_t, o->event_count); child_desc.query.flags |= EcsQueryNested; int i, term_count = query->term_count; bool optional_only = query->flags & EcsQueryMatchThis; bool has_not = false; for (i = 0; i < term_count; i ++) { if (query->terms[i].oper != EcsOptional) { if (ecs_term_match_this(&query->terms[i])) { optional_only = false; } } if ((query->terms[i].oper == EcsNot) && (query->terms[i].inout != EcsInOutFilter)) { has_not = true; } } /* If an observer is only interested in table events, we only need to * observe a single component, as each table event will be emitted for all * components of the source table. */ bool only_table_events = true; for (i = 0; i < o->event_count; i ++) { ecs_entity_t e = o->events[i]; if (e != EcsOnTableCreate && e != EcsOnTableDelete) { only_table_events = false; break; } } if (query->flags & EcsQueryMatchPrefab) { child_desc.query.flags |= EcsQueryMatchPrefab; } if (query->flags & EcsQueryMatchDisabled) { child_desc.query.flags |= EcsQueryMatchDisabled; } bool self_term_handled = false; 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; if (only_table_events) { /* For table event observers, only observe a single $this|self * term. Make sure to create observers for non-self terms, as those * require event propagation. */ if (ecs_term_match_this(term) && (term->src.id & EcsTraverseFlags) == EcsSelf) { if (oper == EcsAnd) { if (!self_term_handled) { self_term_handled = true; } else { continue; } } } } /* AndFrom & OrFrom terms insert multiple observers */ if (oper == EcsAndFrom || oper == EcsOrFrom) { const ecs_type_t *type = ecs_get_type(world, id); if (!type) { continue; } int32_t ti, ti_count = type->count; ecs_id_t *ti_ids = type->array; /* Correct operator will be applied when an event occurs, and * the observer is evaluated on the observer source */ term->oper = EcsAnd; for (ti = 0; ti < ti_count; ti ++) { ecs_id_t ti_id = ti_ids[ti]; ecs_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) { if (only_table_events || desc->events[0] == EcsMonitor) { /* For table events & monitors optional terms aren't necessary */ continue; } } if (flecs_observer_add_child(world, o, &child_desc)) { goto error; } if (optional_only) { break; } } /* If observer has Not terms, we need to create a query that replaces Not * with Optional which we can use to populate the observer data for the * table that the entity moved away from (or to, if it's an OnRemove * observer). */ if (has_not) { ecs_query_desc_t not_desc = desc->query; not_desc.expr = NULL; ecs_os_memcpy_n(not_desc.terms, o->query->terms, ecs_term_t, term_count); /* cast suppresses warning */ for (i = 0; i < term_count; i ++) { if (not_desc.terms[i].oper == EcsNot) { not_desc.terms[i].oper = EcsOptional; } } flecs_observer_impl(o)->not_query = ecs_query_init(world, ¬_desc); } return 0; error: return -1; } static void flecs_observer_poly_fini(void *ptr) { flecs_observer_fini(ptr); } ecs_observer_t* flecs_observer_init( ecs_world_t *world, ecs_entity_t entity, const ecs_observer_desc_t *desc) { ecs_assert(flecs_poly_is(world, ecs_world_t), ECS_INTERNAL_ERROR, NULL); ecs_check(desc->callback != NULL || desc->run != NULL, ECS_INVALID_OPERATION, "cannot create observer: must at least specify callback or run"); ecs_observer_impl_t *impl = flecs_calloc_t( &world->allocator, ecs_observer_impl_t); ecs_assert(impl != NULL, ECS_INTERNAL_ERROR, NULL); impl->id = ++ world->observable.last_observer_id; flecs_poly_init(impl, ecs_observer_t); ecs_observer_t *o = &impl->pub; 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_; ecs_check(!(desc->yield_existing && (desc->flags_ & (EcsObserverYieldOnCreate|EcsObserverYieldOnDelete))), ECS_INVALID_PARAMETER, "cannot set yield_existing and YieldOn* flags at the same time"); /* Check if observer is monitor. Monitors are created as multi observers * since they require pre/post checking of the 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; if (desc->yield_existing) { impl->flags |= EcsObserverYieldOnCreate; impl->flags |= EcsObserverYieldOnDelete; } } else { o->events[i] = event; if (desc->yield_existing) { if (event == EcsOnRemove) { impl->flags |= EcsObserverYieldOnDelete; } else { impl->flags |= EcsObserverYieldOnCreate; } } } o->event_count ++; } /* Observer must have at least one event */ ecs_check(o->event_count != 0, ECS_INVALID_PARAMETER, "observer must have at least one event"); bool multi = false; if (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 (impl->flags & EcsObserverYieldOnCreate) { flecs_observer_yield_existing(world, o, false); } return o; error: return NULL; } ecs_entity_t ecs_observer_init( ecs_world_t *world, const ecs_observer_desc_t *desc) { ecs_entity_t entity = 0; flecs_poly_assert(world, ecs_world_t); ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, "ecs_observer_desc_t was not initialized to zero"); ecs_check(!(world->flags & EcsWorldFini), ECS_INVALID_OPERATION, "cannot create observer while world is being deleted"); 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 & EcsObserverYieldOnDelete) { flecs_observer_yield_existing(world, o, true); } if (impl->flags & EcsObserverIsMulti) { ecs_observer_t **children = ecs_vec_first(&impl->children); int32_t i, children_count = ecs_vec_count(&impl->children); for (i = 0; i < children_count; i ++) { flecs_observer_fini(children[i]); } ecs_os_free(impl->last_event_id); } else { 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_free_t(&world->allocator, ecs_observer_impl_t, o); } void flecs_observer_set_disable_bit( ecs_world_t *world, ecs_entity_t e, ecs_flags32_t bit, bool cond) { const EcsPoly *poly = ecs_get_pair(world, e, EcsPoly, EcsObserver); if (!poly || !poly->poly) { return; } ecs_observer_t *o = poly->poly; ecs_observer_impl_t *impl = flecs_observer_impl(o); if (impl->flags & EcsObserverIsMulti) { ecs_observer_t **children = ecs_vec_first(&impl->children); int32_t i, children_count = ecs_vec_count(&impl->children); if (children_count) { for (i = 0; i < children_count; i ++) { ECS_BIT_COND(flecs_observer_impl(children[i])->flags, bit, cond); } } } else { flecs_poly_assert(o, ecs_observer_t); ECS_BIT_COND(impl->flags, bit, cond); } } /** * @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); } void ecs_os_perf_trace_push_( const char *file, size_t line, const char *name) { if (ecs_os_api.perf_trace_push_) { ecs_os_api.perf_trace_push_(file, line, name); } } void ecs_os_perf_trace_pop_( const char *file, size_t line, const char *name) { if (ecs_os_api.perf_trace_pop_) { ecs_os_api.perf_trace_pop_(file, line, name); } } /* Replace dots with underscores */ static char *module_file_base(const char *module, char sep) { char *base = ecs_os_strdup(module); ecs_size_t i, len = ecs_os_strlen(base); for (i = 0; i < len; i ++) { if (base[i] == '.') { base[i] = sep; } } return base; } static char* ecs_os_api_module_to_dl(const char *module) { ecs_strbuf_t lib = ECS_STRBUF_INIT; /* Best guess, use module name with underscores + OS library extension */ char *file_base = module_file_base(module, '_'); # if defined(ECS_TARGET_LINUX) || defined(ECS_TARGET_FREEBSD) ecs_strbuf_appendlit(&lib, "lib"); ecs_strbuf_appendstr(&lib, file_base); ecs_strbuf_appendlit(&lib, ".so"); # elif defined(ECS_TARGET_DARWIN) ecs_strbuf_appendlit(&lib, "lib"); ecs_strbuf_appendstr(&lib, file_base); ecs_strbuf_appendlit(&lib, ".dylib"); # elif defined(ECS_TARGET_WINDOWS) ecs_strbuf_appendstr(&lib, file_base); ecs_strbuf_appendlit(&lib, ".dll"); # endif ecs_os_free(file_base); return ecs_strbuf_get(&lib); } static char* ecs_os_api_module_to_etc(const char *module) { ecs_strbuf_t lib = ECS_STRBUF_INIT; /* Best guess, use module name with dashes + /etc */ char *file_base = module_file_base(module, '-'); ecs_strbuf_appendstr(&lib, file_base); ecs_strbuf_appendlit(&lib, "/etc"); ecs_os_free(file_base); return ecs_strbuf_get(&lib); } void ecs_os_set_api_defaults(void) { /* Don't overwrite if already initialized */ if (ecs_os_api_initialized != 0) { return; } if (ecs_os_api_initializing != 0) { return; } ecs_os_api_initializing = true; /* Memory management */ ecs_os_api.malloc_ = ecs_os_api_malloc; ecs_os_api.free_ = ecs_os_api_free; ecs_os_api.realloc_ = ecs_os_api_realloc; ecs_os_api.calloc_ = ecs_os_api_calloc; /* Strings */ ecs_os_api.strdup_ = ecs_os_api_strdup; /* 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->id = 0; cmd->next_for_entity = 0; cmd->entry = NULL; cmd->system = stage->system; return cmd; } static ecs_cmd_t* flecs_cmd_new_batched( ecs_stage_t *stage, ecs_entity_t e) { ecs_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(ecs_world_get_flags(world), EcsWorldMeasureFrameTime); ecs_time_t t_start = {0}; if (measure_frame_time) { ecs_os_get_time(&t_start); } ecs_dbg_3("#[magenta]merge"); ecs_log_push_3(); if (is_stage) { /* 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 */ existing = table->data.columns[tr->column].data; existing = ECS_ELEM(existing, 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); } 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); } void flecs_commands_fini( ecs_stage_t *stage, ecs_commands_t *cmd) { /* Make sure stage has no unmerged data */ ecs_assert(ecs_vec_count(&cmd->queue) == 0, ECS_INTERNAL_ERROR, NULL); flecs_stack_fini(&cmd->stack); ecs_vec_fini_t(&stage->allocator, &cmd->queue, ecs_cmd_t); flecs_sparse_fini(&cmd->entries); } 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 < 2; 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 < 2; i ++) { flecs_commands_fini(stage, &stage->cmd_stack[i]); } #ifdef FLECS_SCRIPT if (stage->runtime) { ecs_script_runtime_free(stage->runtime); } #endif 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); 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_w_dbg_info( &world->allocator, ti->size, ti->name); 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; /* 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; const ecs_entity_t EcsDocUuid = FLECS_HI_COMPONENT_ID + 118; #endif /* REST module components */ #ifdef FLECS_REST const ecs_entity_t ecs_id(EcsRest) = FLECS_HI_COMPONENT_ID + 119; #endif /* Max static id: * #define EcsFirstUserEntityId (FLECS_HI_COMPONENT_ID + 128) */ /* Default lookup path */ static ecs_entity_t ecs_default_lookup_path[2] = { 0, 0 }; /* Declarations for addons. Located in world.c to avoid issues during linking of * static library */ #ifdef FLECS_ALERTS ECS_COMPONENT_DECLARE(EcsAlert); ECS_COMPONENT_DECLARE(EcsAlertInstance); ECS_COMPONENT_DECLARE(EcsAlertsActive); ECS_TAG_DECLARE(EcsAlertInfo); ECS_TAG_DECLARE(EcsAlertWarning); ECS_TAG_DECLARE(EcsAlertError); ECS_TAG_DECLARE(EcsAlertCritical); #endif #ifdef FLECS_UNITS ecs_entity_t EcsUnitPrefixes; ecs_entity_t EcsYocto; ecs_entity_t EcsZepto; ecs_entity_t EcsAtto; ecs_entity_t EcsFemto; ecs_entity_t EcsPico; ecs_entity_t EcsNano; ecs_entity_t EcsMicro; ecs_entity_t EcsMilli; ecs_entity_t EcsCenti; ecs_entity_t EcsDeci; ecs_entity_t EcsDeca; ecs_entity_t EcsHecto; ecs_entity_t EcsKilo; ecs_entity_t EcsMega; ecs_entity_t EcsGiga; ecs_entity_t EcsTera; ecs_entity_t EcsPeta; ecs_entity_t EcsExa; ecs_entity_t EcsZetta; ecs_entity_t EcsYotta; ecs_entity_t EcsKibi; ecs_entity_t EcsMebi; ecs_entity_t EcsGibi; ecs_entity_t EcsTebi; ecs_entity_t EcsPebi; ecs_entity_t EcsExbi; ecs_entity_t EcsZebi; ecs_entity_t EcsYobi; ecs_entity_t EcsDuration; ecs_entity_t EcsPicoSeconds; ecs_entity_t EcsNanoSeconds; ecs_entity_t EcsMicroSeconds; ecs_entity_t EcsMilliSeconds; ecs_entity_t EcsSeconds; ecs_entity_t EcsMinutes; ecs_entity_t EcsHours; ecs_entity_t EcsDays; ecs_entity_t EcsTime; ecs_entity_t EcsDate; ecs_entity_t EcsMass; ecs_entity_t EcsGrams; ecs_entity_t EcsKiloGrams; ecs_entity_t EcsElectricCurrent; ecs_entity_t EcsAmpere; ecs_entity_t EcsAmount; ecs_entity_t EcsMole; ecs_entity_t EcsLuminousIntensity; ecs_entity_t EcsCandela; ecs_entity_t EcsForce; ecs_entity_t EcsNewton; ecs_entity_t EcsLength; ecs_entity_t EcsMeters; ecs_entity_t EcsPicoMeters; ecs_entity_t EcsNanoMeters; ecs_entity_t EcsMicroMeters; ecs_entity_t EcsMilliMeters; ecs_entity_t EcsCentiMeters; ecs_entity_t EcsKiloMeters; ecs_entity_t EcsMiles; ecs_entity_t EcsPixels; ecs_entity_t EcsPressure; ecs_entity_t EcsPascal; ecs_entity_t EcsBar; ecs_entity_t EcsSpeed; ecs_entity_t EcsMetersPerSecond; ecs_entity_t EcsKiloMetersPerSecond; ecs_entity_t EcsKiloMetersPerHour; ecs_entity_t EcsMilesPerHour; ecs_entity_t EcsAcceleration; ecs_entity_t EcsTemperature; ecs_entity_t EcsKelvin; ecs_entity_t EcsCelsius; ecs_entity_t EcsFahrenheit; ecs_entity_t EcsData; ecs_entity_t EcsBits; ecs_entity_t EcsKiloBits; ecs_entity_t EcsMegaBits; ecs_entity_t EcsGigaBits; ecs_entity_t EcsBytes; ecs_entity_t EcsKiloBytes; ecs_entity_t EcsMegaBytes; ecs_entity_t EcsGigaBytes; ecs_entity_t EcsKibiBytes; ecs_entity_t EcsGibiBytes; ecs_entity_t EcsMebiBytes; ecs_entity_t EcsDataRate; ecs_entity_t EcsBitsPerSecond; ecs_entity_t EcsKiloBitsPerSecond; ecs_entity_t EcsMegaBitsPerSecond; ecs_entity_t EcsGigaBitsPerSecond; ecs_entity_t EcsBytesPerSecond; ecs_entity_t EcsKiloBytesPerSecond; ecs_entity_t EcsMegaBytesPerSecond; ecs_entity_t EcsGigaBytesPerSecond; ecs_entity_t EcsPercentage; ecs_entity_t EcsAngle; ecs_entity_t EcsRadians; ecs_entity_t EcsDegrees; ecs_entity_t EcsColor; ecs_entity_t EcsColorRgb; ecs_entity_t EcsColorHsl; ecs_entity_t EcsColorCss; ecs_entity_t EcsBel; ecs_entity_t EcsDeciBel; ecs_entity_t EcsFrequency; ecs_entity_t EcsHertz; ecs_entity_t EcsKiloHertz; ecs_entity_t EcsMegaHertz; ecs_entity_t EcsGigaHertz; ecs_entity_t EcsUri; ecs_entity_t EcsUriHyperlink; ecs_entity_t EcsUriImage; ecs_entity_t EcsUriFile; #endif /* -- 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) || !(world->flags & EcsWorldMultiThreaded), ECS_INVALID_WHILE_READONLY, NULL); state->is_readonly = is_readonly; state->is_deferred = stage->defer != 0; state->cmd_flushing = stage->cmd_flushing; /* Silence readonly checks */ world->flags &= ~EcsWorldReadonly; stage->cmd_flushing = false; /* Hack around safety checks (this ought to look ugly) */ state->defer_count = stage->defer; state->cmd_stack[0] = stage->cmd_stack[0]; state->cmd_stack[1] = stage->cmd_stack[1]; state->cmd = stage->cmd; flecs_commands_init(stage, &stage->cmd_stack[0]); flecs_commands_init(stage, &stage->cmd_stack[1]); stage->cmd = &stage->cmd_stack[0]; state->scope = stage->scope; state->with = stage->with; stage->defer = 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; stage->cmd_flushing = state->cmd_flushing; flecs_commands_fini(stage, &stage->cmd_stack[0]); flecs_commands_fini(stage, &stage->cmd_stack[1]); stage->cmd_stack[0] = state->cmd_stack[0]; stage->cmd_stack[1] = state->cmd_stack[1]; stage->cmd = state->cmd; 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->info.eval_comp_monitors_total ++; ecs_os_perf_trace_push("flecs.component_monitor.eval"); 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 }); } } ecs_os_perf_trace_pop("flecs.component_monitor.eval"); } 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.deleted_components, ecs_entity_t, 0); /* Initialize entity index */ flecs_entities_init(world); /* Initialize table sparse set */ flecs_sparse_init_t(&world->store.tables, a, &world->allocators.sparse_chunk, ecs_table_t); /* Initialize table map */ flecs_table_hashmap_init(world, &world->store.table_map); /* Initialize root table */ flecs_init_root_table(world); } static void flecs_clean_tables( ecs_world_t *world) { int32_t i, count = flecs_sparse_count(&world->store.tables); /* Ensure that first table in sparse set has id 0. This is a dummy table * that only exists so that there is no table with id 0 */ ecs_table_t *first = flecs_sparse_get_dense_t(&world->store.tables, ecs_table_t, 0); (void)first; for (i = 1; i < count; i ++) { ecs_table_t *t = flecs_sparse_get_dense_t(&world->store.tables, ecs_table_t, i); flecs_table_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_stage_t *stage0 = world->stages[0]; bool finished = false; const ecs_size_t MAX_DEFERRED_DELETE_QUEUE_SIZE = 4096; while (!finished) { ecs_table_cache_iter_t it; ecs_size_t queue_size = 0; finished = true; bool has_roots = flecs_table_cache_iter(&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 = ecs_table_count(table); const ecs_entity_t *entities = ecs_table_entities(table); if (fini_targets) { /* Only delete entities that are used as pair target. Iterate * backwards to minimize moving entities around in table. */ for (i = count - 1; i >= 0; i --) { ecs_record_t *r = flecs_entities_get(world, entities[i]); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(r->table == table, ECS_INTERNAL_ERROR, NULL); if (ECS_RECORD_TO_ROW_FLAGS(r->row) & EcsEntityIsTarget) { ecs_delete(world, entities[i]); queue_size++; /* Flush the queue before it grows too big: */ if(queue_size >= MAX_DEFERRED_DELETE_QUEUE_SIZE) { finished = false; break; /* restart iteration */ } } } } else { /* Delete remaining entities that are not in use (added to another * entity). This limits table moves during cleanup and delays * cleanup of tags. */ for (i = count - 1; i >= 0; i --) { ecs_record_t *r = flecs_entities_get(world, entities[i]); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(r->table == table, ECS_INTERNAL_ERROR, NULL); if (!ECS_RECORD_TO_ROW_FLAGS(r->row)) { ecs_delete(world, entities[i]); queue_size++; /* Flush the queue before it grows too big: */ if(queue_size >= MAX_DEFERRED_DELETE_QUEUE_SIZE) { finished = false; break; /* restart iteration */ } } } } if(!finished) { /* flush queue and restart iteration */ flecs_defer_end(world, stage0); flecs_defer_begin(world, stage0); break; } } } } static void flecs_fini_roots( ecs_world_t *world) { ecs_id_record_t *idr = flecs_id_record_get(world, ecs_pair(EcsChildOf, 0)); /* Delete root entities that are not modules. This prioritizes deleting * regular entities first, which reduces the chance of components getting * destructed in random order because 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(ecs_vec_count(&world->store.marked_ids) == 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(ecs_vec_count(&world->store.deleted_components) == 0, ECS_INTERNAL_ERROR, NULL); ecs_allocator_t *a = &world->allocator; ecs_vec_fini_t(a, &world->store.records, ecs_table_record_t); ecs_vec_fini_t(a, &world->store.marked_ids, ecs_marked_id_t); ecs_vec_fini_t(a, &world->store.deleted_components, ecs_entity_t); } static void flecs_world_allocators_init( ecs_world_t *world) { ecs_world_allocators_t *a = &world->allocators; flecs_allocator_init(&world->allocator); 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; ecs_map_init(&world->type_info, a); 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); 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); ecs_vec_init_t(a, &world->component_ids, ecs_id_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, id, 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, id, 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); } ECS_NORETURN static void flecs_ctor_illegal( void * dst, int32_t count, const ecs_type_info_t *ti) { (void)dst; /* silence unused warning */ (void)count; ecs_abort(ECS_INVALID_OPERATION, "invalid constructor for %s", ti->name); } ECS_NORETURN static void flecs_dtor_illegal( void *dst, int32_t count, const ecs_type_info_t *ti) { (void)dst; /* silence unused warning */ (void)count; ecs_abort(ECS_INVALID_OPERATION, "invalid destructor for %s", ti->name); } ECS_NORETURN static void flecs_copy_illegal( void *dst, const void *src, int32_t count, const ecs_type_info_t *ti) { (void)dst; /* silence unused warning */ (void)src; (void)count; ecs_abort(ECS_INVALID_OPERATION, "invalid copy assignment for %s", ti->name); } ECS_NORETURN static void flecs_move_illegal( void * dst, void * src, int32_t count, const ecs_type_info_t *ti) { (void)dst; /* silence unused warning */ (void)src; (void)count; ecs_abort(ECS_INVALID_OPERATION, "invalid move assignment for %s", ti->name); } ECS_NORETURN static void flecs_copy_ctor_illegal( void *dst, const void *src, int32_t count, const ecs_type_info_t *ti) { (void)dst; /* silence unused warning */ (void)src; (void)count; ecs_abort(ECS_INVALID_OPERATION, "invalid copy construct for %s", ti->name); } ECS_NORETURN static void flecs_move_ctor_illegal( void *dst, void *src, int32_t count, const ecs_type_info_t *ti) { (void)dst; /* silence unused warning */ (void)src; (void)count; ecs_abort(ECS_INVALID_OPERATION, "invalid move construct for %s", ti->name); } 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); /* TODO: Refactor to enforce flags consistency: */ ecs_flags32_t flags = h->flags; flags &= ~((ecs_flags32_t)ECS_TYPE_HOOKS); /* TODO: enable asserts once RTT API is updated */ /* ecs_check(!(h->flags & ECS_TYPE_HOOK_CTOR_ILLEGAL) || !h->ctor, ECS_INVALID_PARAMETER, "cannot specify both hook and illegal flag"); ecs_check(!(h->flags & ECS_TYPE_HOOK_DTOR_ILLEGAL) || !h->dtor, ECS_INVALID_PARAMETER, "cannot specify both hook and illegal flag"); ecs_check(!(h->flags & ECS_TYPE_HOOK_COPY_ILLEGAL) || !h->copy, ECS_INVALID_PARAMETER, "cannot specify both hook and illegal flag"); ecs_check(!(h->flags & ECS_TYPE_HOOK_MOVE_ILLEGAL) || !h->move, ECS_INVALID_PARAMETER, "cannot specify both hook and illegal flag"); ecs_check(!(h->flags & ECS_TYPE_HOOK_COPY_CTOR_ILLEGAL) || !h->copy_ctor, ECS_INVALID_PARAMETER, "cannot specify both hook and illegal flag"); ecs_check(!(h->flags & ECS_TYPE_HOOK_MOVE_CTOR_ILLEGAL) || !h->move_ctor, ECS_INVALID_PARAMETER, "cannot specify both hook and illegal flag"); ecs_check(!(h->flags & ECS_TYPE_HOOK_CTOR_MOVE_DTOR_ILLEGAL) || !h->ctor_move_dtor, ECS_INVALID_PARAMETER, "cannot specify both hook and illegal flag"); ecs_check(!(h->flags & ECS_TYPE_HOOK_MOVE_DTOR_ILLEGAL) || !h->move_dtor, ECS_INVALID_PARAMETER, "cannot specify both hook and illegal flag"); */ flecs_stage_from_world(&world); /* Ensure that no tables have yet been created for the component */ ecs_check( ecs_id_in_use(world, component) == false, ECS_ALREADY_IN_USE, ecs_get_name(world, component)); ecs_check( 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->lifecycle_ctx) ti->hooks.lifecycle_ctx = h->lifecycle_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 (h->lifecycle_ctx_free) ti->hooks.lifecycle_ctx_free = h->lifecycle_ctx_free; /* If no constructor is set, invoking any of the other lifecycle actions * is not safe as they will potentially access uninitialized memory. For * ease of use, if no constructor is specified, set a default one that * initializes the component to 0. */ if (!h->ctor && (h->dtor || h->copy || h->move)) { ti->hooks.ctor = flecs_default_ctor; } /* Set default copy ctor, move ctor and merge */ if (!h->copy_ctor) { if(flags & ECS_TYPE_HOOK_COPY_ILLEGAL || flags & ECS_TYPE_HOOK_CTOR_ILLEGAL) { flags |= ECS_TYPE_HOOK_COPY_CTOR_ILLEGAL; } else if(h->copy) { ti->hooks.copy_ctor = flecs_default_copy_ctor; } } if (!h->move_ctor) { if(flags & ECS_TYPE_HOOK_MOVE_ILLEGAL || flags & ECS_TYPE_HOOK_CTOR_ILLEGAL) { flags |= ECS_TYPE_HOOK_MOVE_CTOR_ILLEGAL; } else if (h->move) { ti->hooks.move_ctor = flecs_default_move_ctor; } } if (!h->ctor_move_dtor) { ecs_flags32_t illegal_check = 0; if (h->move) { illegal_check |= ECS_TYPE_HOOK_MOVE_ILLEGAL; if (h->dtor) { illegal_check |= ECS_TYPE_HOOK_DTOR_ILLEGAL; if (h->move_ctor) { illegal_check |= ECS_TYPE_HOOK_MOVE_CTOR_ILLEGAL; /* If an explicit move ctor has been set, use callback * that uses the move ctor vs. using a ctor+move */ ti->hooks.ctor_move_dtor = flecs_default_move_ctor_w_dtor; } else { illegal_check |= ECS_TYPE_HOOK_CTOR_ILLEGAL; /* If no explicit move_ctor has been set, use * combination of ctor + move + dtor */ ti->hooks.ctor_move_dtor = flecs_default_ctor_w_move_w_dtor; } } else { illegal_check |= ECS_TYPE_HOOK_MOVE_CTOR_ILLEGAL; /* If no dtor has been set, this is just a move ctor */ 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) { illegal_check |= ECS_TYPE_HOOK_MOVE_CTOR_ILLEGAL; if (h->dtor) { illegal_check |= ECS_TYPE_HOOK_DTOR_ILLEGAL; ti->hooks.ctor_move_dtor = flecs_default_move_ctor_w_dtor; } else { ti->hooks.ctor_move_dtor = ti->hooks.move_ctor; } } } if(flags & illegal_check) { flags |= ECS_TYPE_HOOK_CTOR_MOVE_DTOR_ILLEGAL; } } if (!h->move_dtor) { ecs_flags32_t illegal_check = 0; if (h->move) { illegal_check |= ECS_TYPE_HOOK_MOVE_ILLEGAL; if (h->dtor) { illegal_check |= ECS_TYPE_HOOK_DTOR_ILLEGAL; ti->hooks.move_dtor = flecs_default_move_w_dtor; } else { ti->hooks.move_dtor = flecs_default_move; } } else { if (h->dtor) { illegal_check |= ECS_TYPE_HOOK_DTOR_ILLEGAL; ti->hooks.move_dtor = flecs_default_dtor; } } if(flags & illegal_check) { flags |= ECS_TYPE_HOOK_MOVE_DTOR_ILLEGAL; } } ti->hooks.flags = flags; if (ti->hooks.ctor) ti->hooks.flags |= ECS_TYPE_HOOK_CTOR; if (ti->hooks.dtor) ti->hooks.flags |= ECS_TYPE_HOOK_DTOR; if (ti->hooks.move) ti->hooks.flags |= ECS_TYPE_HOOK_MOVE; if (ti->hooks.move_ctor) ti->hooks.flags |= ECS_TYPE_HOOK_MOVE_CTOR; if (ti->hooks.ctor_move_dtor) ti->hooks.flags |= ECS_TYPE_HOOK_CTOR_MOVE_DTOR; if (ti->hooks.move_dtor) ti->hooks.flags |= ECS_TYPE_HOOK_MOVE_DTOR; if (ti->hooks.copy) ti->hooks.flags |= ECS_TYPE_HOOK_COPY; if (ti->hooks.copy_ctor) ti->hooks.flags |= ECS_TYPE_HOOK_COPY_CTOR; if(flags & ECS_TYPE_HOOK_CTOR_ILLEGAL) ti->hooks.ctor = flecs_ctor_illegal; if(flags & ECS_TYPE_HOOK_DTOR_ILLEGAL) ti->hooks.dtor = flecs_dtor_illegal; if(flags & ECS_TYPE_HOOK_COPY_ILLEGAL) ti->hooks.copy = flecs_copy_illegal; if(flags & ECS_TYPE_HOOK_MOVE_ILLEGAL) ti->hooks.move = flecs_move_illegal; if(flags & ECS_TYPE_HOOK_COPY_CTOR_ILLEGAL) { ti->hooks.copy_ctor = flecs_copy_ctor_illegal; } if(ti->hooks.flags & ECS_TYPE_HOOK_MOVE_CTOR_ILLEGAL) { ti->hooks.move_ctor = flecs_move_ctor_illegal; } if(ti->hooks.flags & ECS_TYPE_HOOK_CTOR_MOVE_DTOR_ILLEGAL) { ti->hooks.ctor_move_dtor = flecs_move_ctor_illegal; } if(ti->hooks.flags & ECS_TYPE_HOOK_MOVE_DTOR_ILLEGAL) { ti->hooks.ctor_move_dtor = flecs_move_ctor_illegal; } 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_check((world->flags & EcsWorldFrameInProgress), ECS_INVALID_OPERATION, "cannot register post frame action while frame is not in progress"); ecs_action_elem_t *elem = ecs_vec_append_t(&stage->allocator, &stage->post_frame_actions, ecs_action_elem_t); ecs_assert(elem != NULL, ECS_INTERNAL_ERROR, NULL); elem->action = action; elem->ctx = ctx; error: return; } /* 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) { ecs_map_iter_t it = ecs_map_iter(&world->type_info); while (ecs_map_next(&it)) { ecs_type_info_t *ti = ecs_map_ptr(&it); flecs_type_info_fini(ti); ecs_os_free(ti); } ecs_map_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_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_vec_fini_t(&world->allocator, &world->component_ids, ecs_id_t); 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_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); 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); if (id_start == 0) { id_start = flecs_entities_max_id(world) + 1; } uint32_t start = (uint32_t)id_start; uint32_t end = (uint32_t)id_end; 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 ecs_map_get_deref(&world->type_info, ecs_type_info_t, component); } ecs_type_info_t* flecs_type_info_ensure( ecs_world_t *world, ecs_entity_t component) { flecs_poly_assert(world, ecs_world_t); ecs_assert(component != 0, ECS_INTERNAL_ERROR, NULL); const ecs_type_info_t *ti = flecs_type_info_get(world, component); ecs_type_info_t *ti_mut = NULL; if (!ti) { ti_mut = ecs_map_ensure_alloc_t( &world->type_info, ecs_type_info_t, component); ecs_assert(ti_mut != NULL, ECS_INTERNAL_ERROR, NULL); ti_mut->component = component; } else { ti_mut = ECS_CONST_CAST(ecs_type_info_t*, ti); } if (!ti_mut->name) { const char *sym = ecs_get_symbol(world, component); if (sym) { ti_mut->name = ecs_os_strdup(sym); } else { const char *name = ecs_get_name(world, component); if (name) { ti_mut->name = ecs_os_strdup(name); } } } return ti_mut; } bool flecs_type_info_init_id( ecs_world_t *world, ecs_entity_t component, ecs_size_t size, ecs_size_t alignment, const ecs_type_hooks_t *li) { bool changed = false; flecs_entities_ensure(world, component); ecs_type_info_t *ti = NULL; if (!size || !alignment) { ecs_assert(size == 0 && alignment == 0, ECS_INVALID_COMPONENT_SIZE, NULL); ecs_assert(li == NULL, ECS_INCONSISTENT_COMPONENT_ACTION, NULL); ecs_map_remove_free(&world->type_info, component); } else { ti = flecs_type_info_ensure(world, component); ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); changed |= ti->size != size; changed |= ti->alignment != alignment; ti->size = size; ti->alignment = alignment; if (li) { ecs_set_hooks_id(world, component, li); } } /* Set type info for 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->hooks.lifecycle_ctx_free) { ti->hooks.lifecycle_ctx_free(ti->hooks.lifecycle_ctx); } if (ti->name) { /* Safe to cast away const, world has ownership over string */ ecs_os_free(ECS_CONST_CAST(char*, ti->name)); ti->name = NULL; } ti->size = 0; ti->alignment = 0; } void flecs_type_info_free( ecs_world_t *world, ecs_entity_t component) { flecs_poly_assert(world, ecs_world_t); if (world->flags & EcsWorldQuit) { /* If world is in the final teardown stages, cleanup policies are no * longer applied and it can't be guaranteed that a component is not * deleted before entities that use it. The remaining type info elements * will be deleted after the store is finalized. */ return; } ecs_type_info_t *ti = ecs_map_get_deref( &world->type_info, ecs_type_info_t, component); if (ti) { flecs_type_info_fini(ti); ecs_map_remove_free(&world->type_info, 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_os_perf_trace_push("flecs.insert_sleep"); ecs_ftime_t target_delta_time = ((ecs_ftime_t)1.0 / (ecs_ftime_t)world->info.target_fps); /* Calculate the time we need to sleep by taking the measured delta from the * previous frame, and subtracting it from target_delta_time. */ ecs_ftime_t sleep = target_delta_time - delta_time; /* Pick a sleep interval that is 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)); ecs_os_perf_trace_pop("flecs.insert_sleep"); *stop = now; return delta_time; } static ecs_ftime_t flecs_start_measure_frame( ecs_world_t *world, ecs_ftime_t user_delta_time) { flecs_poly_assert(world, ecs_world_t); ecs_ftime_t delta_time = 0; if ((world->flags & EcsWorldMeasureFrameTime) || (ECS_EQZERO(user_delta_time))) { ecs_time_t t = world->frame_start_time; do { if (world->frame_start_time.nanosec || world->frame_start_time.sec){ delta_time = flecs_insert_sleep(world, &t); } else { ecs_time_measure(&t); if (ECS_NEQZERO(world->info.target_fps)) { delta_time = (ecs_ftime_t)1.0 / world->info.target_fps; } else { /* Best guess */ delta_time = (ecs_ftime_t)1.0 / (ecs_ftime_t)60.0; if (ECS_EQZERO(delta_time)) { delta_time = user_delta_time; break; } } } /* Keep trying while delta_time is zero */ } while (ECS_EQZERO(delta_time)); world->frame_start_time = t; /* Keep track of total time passed in world */ world->info.world_time_total_raw += (double)delta_time; } return (ecs_ftime_t)delta_time; } static void flecs_stop_measure_frame( ecs_world_t* world) { flecs_poly_assert(world, ecs_world_t); if (world->flags & EcsWorldMeasureFrameTime) { ecs_time_t t = world->frame_start_time; world->info.frame_time_total += (ecs_ftime_t)ecs_time_measure(&t); } } ecs_ftime_t ecs_frame_begin( ecs_world_t *world, ecs_ftime_t user_delta_time) { flecs_poly_assert(world, ecs_world_t); ecs_check(!(world->flags & EcsWorldReadonly), ECS_INVALID_OPERATION, "cannot begin frame while world is in readonly mode"); ecs_check(!(world->flags & EcsWorldFrameInProgress), ECS_INVALID_OPERATION, "cannot begin frame while frame is already in progress"); ecs_check(ECS_NEQZERO(user_delta_time) || ecs_os_has_time(), ECS_MISSING_OS_API, "get_time"); /* Start measuring total frame time */ ecs_ftime_t delta_time = flecs_start_measure_frame(world, user_delta_time); if (ECS_EQZERO(user_delta_time)) { user_delta_time = delta_time; } world->info.delta_time_raw = user_delta_time; world->info.delta_time = user_delta_time * world->info.time_scale; /* Keep track of total scaled time passed in world */ world->info.world_time_total += (double)world->info.delta_time; /* Command buffer capturing */ world->on_commands_active = world->on_commands; world->on_commands = NULL; world->on_commands_ctx_active = world->on_commands_ctx; world->on_commands_ctx = NULL; ecs_run_aperiodic(world, 0); world->flags |= EcsWorldFrameInProgress; return world->info.delta_time; error: return (ecs_ftime_t)0; } void ecs_frame_end( ecs_world_t *world) { flecs_poly_assert(world, ecs_world_t); ecs_check(!(world->flags & EcsWorldReadonly), ECS_INVALID_OPERATION, "cannot end frame while world is in readonly mode"); ecs_check((world->flags & EcsWorldFrameInProgress), ECS_INVALID_OPERATION, "cannot end frame while frame is not in progress"); world->info.frame_count_total ++; int32_t i, count = world->stage_count; for (i = 0; i < count; i ++) { flecs_stage_merge_post_frame(world, world->stages[i]); } flecs_stop_measure_frame(world); /* Reset command handler each frame */ world->on_commands_active = NULL; world->on_commands_ctx_active = NULL; world->flags &= ~EcsWorldFrameInProgress; error: return; } void 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; } /* 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; const ecs_entity_t *entities = ecs_table_entities(table); if (!ecs_query_is_true(query)) { ecs_add_id(world, entities[i], EcsEmpty); } } } } flecs_defer_end(world, world->stages[0]); } 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); } void ecs_run_aperiodic( ecs_world_t *world, ecs_flags32_t flags) { flecs_poly_assert(world, ecs_world_t); if ((flags & EcsAperiodicEmptyQueries)) { flecs_process_empty_queries(world); } if (!flags || (flags & EcsAperiodicComponentMonitors)) { flecs_eval_component_monitors(world); } } int32_t ecs_delete_empty_tables( ecs_world_t *world, const ecs_delete_empty_tables_desc_t *desc) { flecs_poly_assert(world, ecs_world_t); ecs_os_perf_trace_push("flecs.delete_empty_tables"); ecs_time_t start = {0}, cur = {0}; int32_t delete_count = 0; bool time_budget = false; ecs_id_t id = desc->id; uint16_t clear_generation = desc->clear_generation; uint16_t delete_generation = desc->delete_generation; int32_t min_id_count = desc->min_id_count; double time_budget_seconds = desc->time_budget_seconds; 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)) { flecs_table_shrink(world, table); } } } done: ecs_os_perf_trace_pop("flecs.delete_empty_tables"); 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; } ecs_flags32_t ecs_world_get_flags( const ecs_world_t *world) { if (flecs_poly_is(world, ecs_world_t)) { return world->flags; } else { flecs_poly_assert(world, ecs_stage_t); const ecs_stage_t *stage = (const ecs_stage_t*)world; return stage->world->flags; } } static int32_t flecs_component_ids_last_index = 0; int32_t flecs_component_ids_index_get(void) { if (ecs_os_api.ainc_) { return ecs_os_ainc(&flecs_component_ids_last_index); } else { return ++ flecs_component_ids_last_index; } } ecs_entity_t flecs_component_ids_get( const ecs_world_t *stage_world, int32_t index) { ecs_world_t *world = ECS_CONST_CAST(ecs_world_t*, ecs_get_world(stage_world)); flecs_poly_assert(world, ecs_world_t); if (index >= ecs_vec_count(&world->component_ids)) { return 0; } return ecs_vec_get_t( &world->component_ids, ecs_entity_t, index)[0]; } ecs_entity_t flecs_component_ids_get_alive( const ecs_world_t *stage_world, int32_t index) { ecs_world_t *world = ECS_CONST_CAST(ecs_world_t*, ecs_get_world(stage_world)); flecs_poly_assert(world, ecs_world_t); if (index >= ecs_vec_count(&world->component_ids)) { return 0; } ecs_entity_t result = ecs_vec_get_t( &world->component_ids, ecs_entity_t, index)[0]; if (!flecs_entities_is_alive(world, result)) { return 0; } return result; } void flecs_component_ids_set( ecs_world_t *stage_world, int32_t index, ecs_entity_t component) { ecs_world_t *world = ECS_CONST_CAST(ecs_world_t*, ecs_get_world(stage_world)); flecs_poly_assert(world, ecs_world_t); ecs_vec_set_min_count_zeromem_t( &world->allocator, &world->component_ids, ecs_entity_t, index + 1); ecs_vec_get_t(&world->component_ids, ecs_entity_t, index)[0] = component; } /** * @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: default: return 0; } bool has_error = ECS_NEQ(ranges->error.min, ranges->error.max); bool has_warning = ECS_NEQ(ranges->warning.min, ranges->warning.max); if (has_error && (value < ranges->error.min || value > ranges->error.max)) { return EcsAlertError; } else if (has_warning && (value < ranges->warning.min || value > ranges->warning.max)) { return EcsAlertWarning; } else { return 0; } } static void MonitorAlerts(ecs_iter_t *it) { ecs_world_t *world = it->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; 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; 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 = NULL; EMSCRIPTEN_KEEPALIVE char* flecs_explorer_request(const char *method, char *request, char *body) { ecs_assert(flecs_wasm_rest_server != NULL, ECS_INVALID_OPERATION, "wasm REST server is not initialized yet"); ecs_http_reply_t reply = ECS_HTTP_REPLY_INIT; ecs_http_server_request( flecs_wasm_rest_server, method, request, body, &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; #ifndef ECS_TARGET_EM if (ECS_NEQZERO(ecs_app_desc.target_fps)) { ecs_set_target_fps(world, ecs_app_desc.target_fps); } if (ecs_app_desc.threads) { ecs_set_threads(world, ecs_app_desc.threads); } #endif /* REST server enables connecting to app with explorer */ if (desc->enable_rest) { #ifdef FLECS_REST #ifdef ECS_TARGET_EM flecs_wasm_rest_server = ecs_rest_server_init(world, NULL); ecs_assert(flecs_wasm_rest_server != NULL, ECS_INTERNAL_ERROR, "failed to create wasm REST server (unexpected error)"); #else ECS_IMPORT(world, FlecsRest); ecs_set(world, EcsWorld, EcsRest, {.port = desc->port }); #endif #else ecs_warn("cannot enable remote API, REST addon not available"); #endif } /* Monitoring periodically collects statistics */ if (desc->enable_stats) { #ifdef FLECS_STATS ECS_IMPORT(world, FlecsStats); #else ecs_warn("cannot enable monitoring, MONITOR addon not available"); #endif } return run_action(world, &ecs_app_desc); } int ecs_app_run_frame( ecs_world_t *world, const ecs_app_desc_t *desc) { return frame_action(world, desc); } int ecs_app_set_run_action( ecs_app_run_action_t callback) { if (run_action != flecs_default_run_action && run_action != callback) { ecs_err("run action already set"); return -1; } run_action = callback; return 0; } int ecs_app_set_frame_action( ecs_app_frame_action_t callback) { if (frame_action != flecs_default_frame_action && frame_action != callback) { ecs_err("frame action already set"); return -1; } frame_action = callback; return 0; } #endif /** * @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_uuid( ecs_world_t *world, ecs_entity_t entity, const char *name) { flecs_doc_set(world, entity, EcsDocUuid, name); } void ecs_doc_set_name( ecs_world_t *world, ecs_entity_t entity, const char *name) { flecs_doc_set(world, entity, EcsName, name); } void ecs_doc_set_brief( ecs_world_t *world, ecs_entity_t entity, const char *brief) { flecs_doc_set(world, entity, EcsDocBrief, brief); } void ecs_doc_set_detail( ecs_world_t *world, ecs_entity_t entity, const char *detail) { flecs_doc_set(world, entity, EcsDocDetail, detail); } void ecs_doc_set_link( ecs_world_t *world, ecs_entity_t entity, const char *link) { flecs_doc_set(world, entity, EcsDocLink, link); } void ecs_doc_set_color( ecs_world_t *world, ecs_entity_t entity, const char *color) { flecs_doc_set(world, entity, EcsDocColor, color); } const char* ecs_doc_get_uuid( const ecs_world_t *world, ecs_entity_t entity) { const EcsDocDescription *ptr = ecs_get_pair( world, entity, EcsDocDescription, EcsDocUuid); if (ptr) { return ptr->value; } else { return NULL; } } const char* ecs_doc_get_name( const ecs_world_t *world, ecs_entity_t entity) { const EcsDocDescription *ptr = ecs_get_pair( world, entity, EcsDocDescription, EcsName); if (ptr) { return ptr->value; } else { return ecs_get_name(world, entity); } } const char* ecs_doc_get_brief( const ecs_world_t *world, ecs_entity_t entity) { const EcsDocDescription *ptr = ecs_get_pair( world, entity, EcsDocDescription, EcsDocBrief); if (ptr) { return ptr->value; } else { return NULL; } } const char* ecs_doc_get_detail( const ecs_world_t *world, ecs_entity_t entity) { const EcsDocDescription *ptr = ecs_get_pair( world, entity, EcsDocDescription, EcsDocDetail); if (ptr) { return ptr->value; } else { return NULL; } } const char* ecs_doc_get_link( const ecs_world_t *world, ecs_entity_t entity) { const EcsDocDescription *ptr = ecs_get_pair( world, entity, EcsDocDescription, EcsDocLink); if (ptr) { return ptr->value; } else { return NULL; } } const char* ecs_doc_get_color( const ecs_world_t *world, ecs_entity_t entity) { const EcsDocDescription *ptr = ecs_get_pair( world, entity, EcsDocDescription, EcsDocColor); if (ptr) { return ptr->value; } else { return NULL; } } /* 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, 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, EcsDocUuid); flecs_bootstrap_tag(world, EcsDocBrief); flecs_bootstrap_tag(world, EcsDocDetail); flecs_bootstrap_tag(world, EcsDocLink); flecs_bootstrap_tag(world, EcsDocColor); ecs_set_hooks(world, EcsDocDescription, { .ctor = flecs_default_ctor, .move = ecs_move(EcsDocDescription), .copy = ecs_copy(EcsDocDescription), .dtor = ecs_dtor(EcsDocDescription) }); 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) { const char *ptr; size_t i; for (i = 0, ptr = type_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; } ecs_entity_t ecs_cpp_component_find( ecs_world_t *world, ecs_entity_t id, const char *name, const char *symbol, size_t size, size_t alignment, bool implicit_name, bool *existing_out) { (void)size; (void)alignment; ecs_assert(existing_out != NULL, ECS_INTERNAL_ERROR, NULL); /* If the component is not yet registered, ensure no other component * or entity has been registered with this name. Ensure component is * looked up from root. */ ecs_entity_t prev_scope = ecs_set_scope(world, 0); ecs_entity_t ent; if (id) { ent = id; } else { ent = ecs_lookup_path_w_sep(world, 0, name, "::", "::", false); *existing_out = 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)) { 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); } if (flecs_itosize(component->size) != size || flecs_itosize(component->alignment) != alignment) { ecs_err( "component with name '%s' is already registered with"\ " mismatching size/alignment)", name); ecs_abort(ECS_INVALID_COMPONENT_SIZE, NULL); } ecs_os_free(type_path); } else if (!sym) { ecs_set_symbol(world, 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); } return ent; } ecs_entity_t ecs_cpp_component_register( 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; ecs_assert(existing_out != NULL, ECS_INTERNAL_ERROR, NULL); // If an explicit id is provided, it is possible that the symbol and // name differ from the actual type, as the application may alias // one type to another. if (!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; *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, ecs_entity_t underlying_type) { (void)world; (void)id; (void)underlying_type; #ifdef FLECS_META ecs_suspend_readonly_state_t readonly_state; world = flecs_suspend_readonly(world, &readonly_state); ecs_set(world, id, EcsEnum, { .underlying_type = underlying_type }); 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, void *value, ecs_entity_t value_type, size_t value_size) { #ifdef FLECS_META 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"); #endif ecs_set_id(world, id, ecs_pair(EcsConstant, value_type), value_size, value); flecs_resume_readonly(world, &readonly_state); if (ecs_should_log(0)) { ecs_value_t v = { .type = value_type, .ptr = value }; char *str = NULL; ecs_meta_cursor_t cur = ecs_meta_cursor(world, ecs_id(ecs_string_t), &str); ecs_meta_set_value(&cur, &v); ecs_trace("#[green]constant#[reset] %s.%s created with value %s", ecs_get_name(world, parent), name, str); ecs_os_free(str); } return id; #else (void)world; (void)parent; (void)id; (void)name; (void)value; (void)value_type; (void)value_size; ecs_err("enum reflection not supported without FLECS_META addon"); return 0; #endif } #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 (64 * 1024) /* Max length of request (path + query + headers + body) */ #define ECS_HTTP_REQUEST_LEN_MAX (10 * 1024 * 1024) /* Total number of outstanding send requests */ #define ECS_HTTP_SEND_QUEUE_MAX (256) /* 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_os_malloc(ECS_HTTP_SEND_RECV_BUFFER_SIZE); ecs_http_fragment_t frag = {0}; int32_t retries = 0; ecs_os_sleep(0, 10 * 1000 * 1000); do { if ((bytes_read = http_recv( sock, recv_buf, ECS_HTTP_SEND_RECV_BUFFER_SIZE, 0)) > 0) { bool is_alive = conn->pub.id == conn_id; if (!is_alive) { /* Connection has been purged by main thread */ goto done; } if (http_parse_request(&frag, recv_buf, bytes_read)) { if (frag.method == EcsHttpOptions) { ecs_http_reply_t reply; reply.body = ECS_STRBUF_INIT; reply.code = 200; reply.content_type = NULL; reply.headers = ECS_STRBUF_INIT; reply.status = "OK"; http_send_reply(conn, &reply, true); ecs_os_linc(&ecs_http_request_preflight_count); } else { ecs_http_request_entry_t *entry = http_enqueue_request(conn, conn_id, &frag); if (entry) { ecs_http_reply_t reply; reply.body = ECS_STRBUF_INIT; reply.code = entry->code; reply.content_type = "application/json"; reply.headers = ECS_STRBUF_INIT; reply.status = "OK"; ecs_strbuf_appendstrn(&reply.body, entry->content, entry->content_length); http_send_reply(conn, &reply, false); http_connection_free(conn); /* Lock was transferred from enqueue_request */ ecs_os_mutex_unlock(srv->lock); } } } else { ecs_os_linc(&ecs_http_request_invalid_count); } } ecs_os_sleep(0, 10 * 1000 * 1000); } while ((bytes_read == -1) && (++retries < ECS_HTTP_REQUEST_RECV_RETRY)); if (bytes_read == ECS_HTTP_SEND_RECV_BUFFER_SIZE) { ecs_warn("request exceeded receive buffer size (%d)", ECS_HTTP_SEND_RECV_BUFFER_SIZE); } if (retries == ECS_HTTP_REQUEST_RECV_RETRY) { http_close(&sock); } done: ecs_os_free(recv_buf); ecs_strbuf_reset(&frag.buf); } typedef struct { ecs_http_connection_impl_t *conn; uint64_t id; } http_conn_res_t; static http_conn_res_t http_init_connection( ecs_http_server_t *srv, ecs_http_socket_t sock_conn, struct sockaddr_storage *remote_addr, ecs_size_t remote_addr_len) { http_sock_set_timeout(sock_conn, 100); http_sock_keep_alive(sock_conn); http_sock_nonblock(sock_conn, true); /* Create new connection */ ecs_os_mutex_lock(srv->lock); ecs_http_connection_impl_t *conn = flecs_sparse_add_t( &srv->connections, ecs_http_connection_impl_t); uint64_t conn_id = conn->pub.id = flecs_sparse_last_id(&srv->connections); conn->pub.server = srv; conn->sock = sock_conn; ecs_os_mutex_unlock(srv->lock); char *remote_host = conn->pub.host; char *remote_port = conn->pub.port; /* Fetch name & port info */ if (http_getnameinfo((struct sockaddr*) remote_addr, remote_addr_len, remote_host, ECS_SIZEOF(conn->pub.host), remote_port, ECS_SIZEOF(conn->pub.port), NI_NUMERICHOST | NI_NUMERICSERV)) { ecs_os_strcpy(remote_host, "unknown"); ecs_os_strcpy(remote_port, "unknown"); } ecs_dbg_2("http: connection established from '%s:%s' (socket %u)", remote_host, remote_port, sock_conn); return (http_conn_res_t){ .conn = conn, .id = conn_id }; } static int http_accept_connections( ecs_http_server_t* srv, const struct sockaddr* addr, ecs_size_t addr_len) { #ifdef ECS_TARGET_WINDOWS /* If on Windows, test if winsock needs to be initialized */ SOCKET testsocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (INVALID_SOCKET == testsocket && WSANOTINITIALISED == WSAGetLastError()){ WSADATA data = { 0 }; int result = WSAStartup(MAKEWORD(2, 2), &data); if (result) { ecs_warn("http: WSAStartup failed with GetLastError = %d\n", GetLastError()); return 0; } } else { http_close(&testsocket); } #endif /* Resolve name + port (used for logging) */ char addr_host[256]; char addr_port[20]; int ret = 0; /* 0 = ok, 1 = port occupied */ ecs_http_socket_t sock = HTTP_SOCKET_INVALID; ecs_assert(srv->sock == HTTP_SOCKET_INVALID, ECS_INTERNAL_ERROR, NULL); if (http_getnameinfo( addr, addr_len, addr_host, ECS_SIZEOF(addr_host), addr_port, ECS_SIZEOF(addr_port), NI_NUMERICHOST | NI_NUMERICSERV)) { ecs_os_strcpy(addr_host, "unknown"); ecs_os_strcpy(addr_port, "unknown"); } ecs_os_mutex_lock(srv->lock); if (srv->should_run) { ecs_dbg_2("http: initializing connection socket"); sock = socket(addr->sa_family, SOCK_STREAM, IPPROTO_TCP); if (!http_socket_is_valid(sock)) { ecs_err("http: unable to create new connection socket: %s", ecs_os_strerror(errno)); ecs_os_mutex_unlock(srv->lock); goto done; } int reuse = 1, result; result = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char*)&reuse, ECS_SIZEOF(reuse)); if (result) { ecs_warn("http: failed to setsockopt: %s", ecs_os_strerror(errno)); } if (addr->sa_family == AF_INET6) { int ipv6only = 0; if (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&ipv6only, ECS_SIZEOF(ipv6only))) { ecs_warn("http: failed to setsockopt: %s", ecs_os_strerror(errno)); } } result = http_bind(sock, addr, addr_len); if (result) { if (errno == EADDRINUSE) { ret = 1; ecs_warn("http: address '%s:%s' in use, retrying with port %u", addr_host, addr_port, srv->port + 1); } else { ecs_err("http: failed to bind to '%s:%s': %s", addr_host, addr_port, ecs_os_strerror(errno)); } ecs_os_mutex_unlock(srv->lock); goto done; } http_sock_set_timeout(sock, 1000); srv->sock = sock; result = listen(srv->sock, SOMAXCONN); if (result) { ecs_warn("http: could not listen for SOMAXCONN (%d) connections: %s", SOMAXCONN, ecs_os_strerror(errno)); } ecs_trace("http: listening for incoming connections on '%s:%s'", addr_host, addr_port); } else { ecs_dbg_2("http: server shut down while initializing"); } ecs_os_mutex_unlock(srv->lock); struct sockaddr_storage remote_addr; ecs_size_t remote_addr_len = 0; while (srv->should_run) { remote_addr_len = ECS_SIZEOF(remote_addr); ecs_http_socket_t sock_conn = http_accept(srv->sock, (struct sockaddr*) &remote_addr, &remote_addr_len); if (!http_socket_is_valid(sock_conn)) { if (srv->should_run) { ecs_dbg("http: connection attempt failed: %s", ecs_os_strerror(errno)); } continue; } http_conn_res_t conn = http_init_connection(srv, sock_conn, &remote_addr, remote_addr_len); http_recv_connection(srv, conn.conn, conn.id, sock_conn); } done: ecs_os_mutex_lock(srv->lock); if (http_socket_is_valid(sock) && errno != EBADF) { http_close(&sock); srv->sock = sock; } ecs_os_mutex_unlock(srv->lock); ecs_trace("http: no longer accepting connections on '%s:%s'", addr_host, addr_port); return ret; } static void* http_server_thread(void* arg) { ecs_http_server_t *srv = arg; struct sockaddr_in addr; ecs_os_zeromem(&addr); addr.sin_family = AF_INET; int retries = 0; retry: addr.sin_port = htons(srv->port); if (!srv->ipaddr) { addr.sin_addr.s_addr = htonl(INADDR_ANY); } else { inet_pton(AF_INET, srv->ipaddr, &(addr.sin_addr)); } if (http_accept_connections( srv, (struct sockaddr*)&addr, ECS_SIZEOF(addr)) == 1) { srv->port ++; retries ++; if (retries < 10) { goto retry; } else { ecs_err("http: failed to connect (retried 10 times)"); } } return NULL; } static void http_do_request( ecs_http_server_t *srv, ecs_http_reply_t *reply, const ecs_http_request_impl_t *req) { ecs_check(srv != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(srv->callback != NULL, ECS_INVALID_OPERATION, "missing request handler for server"); if (srv->callback(ECS_CONST_CAST(ecs_http_request_t*, req), reply, srv->ctx) == false) { reply->status = "Resource not found"; ecs_os_linc(&ecs_http_request_not_handled_count); } else { if (reply->code >= 400) { ecs_os_linc(&ecs_http_request_handled_error_count); } else { ecs_os_linc(&ecs_http_request_handled_ok_count); } } error: return; } static void http_handle_request( ecs_http_server_t *srv, ecs_http_request_impl_t *req) { ecs_http_reply_t reply = ECS_HTTP_REPLY_INIT; ecs_http_connection_impl_t *conn = (ecs_http_connection_impl_t*)req->pub.conn; if (req->pub.method != EcsHttpOptions) { if (srv->callback((ecs_http_request_t*)req, &reply, srv->ctx) == false) { reply.code = 404; reply.status = "Resource not found"; ecs_os_linc(&ecs_http_request_not_handled_count); } else { if (reply.code >= 400) { ecs_os_linc(&ecs_http_request_handled_error_count); } else { ecs_os_linc(&ecs_http_request_handled_ok_count); } } if (req->pub.method == EcsHttpGet) { http_insert_request_entry(srv, req, &reply); } http_send_reply(conn, &reply, false); ecs_dbg_2("http: reply sent to '%s:%s'", conn->pub.host, conn->pub.port); } else { /* Already taken care of */ } http_reply_fini(&reply); http_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_http_server_t* srv = ecs_os_calloc_t(ecs_http_server_t); if (ecs_os_has_threading()) { srv->lock = ecs_os_mutex_new(); } srv->sock = HTTP_SOCKET_INVALID; srv->should_run = false; srv->initialized = true; srv->cache_timeout = desc->cache_timeout; srv->cache_purge_timeout = desc->cache_purge_timeout; if (!ECS_EQZERO(srv->cache_timeout) && ECS_EQZERO(srv->cache_purge_timeout)) { srv->cache_purge_timeout = srv->cache_timeout * 10; } srv->callback = desc->callback; srv->ctx = desc->ctx; srv->port = desc->port; srv->ipaddr = desc->ipaddr; srv->send_queue.wait_ms = desc->send_queue_wait_ms; if (!srv->send_queue.wait_ms) { srv->send_queue.wait_ms = 1; } flecs_sparse_init_t(&srv->connections, NULL, NULL, ecs_http_connection_impl_t); 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; } void ecs_http_server_fini( ecs_http_server_t* srv) { if (srv->should_run) { ecs_http_server_stop(srv); } if (ecs_os_has_threading()) { ecs_os_mutex_free(srv->lock); } http_purge_request_cache(srv, true); flecs_sparse_fini(&srv->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); ecs_check(ecs_os_has_threading(), ECS_UNSUPPORTED, "missing OS API implementation"); srv->should_run = true; ecs_dbg("http: starting server thread"); srv->thread = ecs_os_thread_new(http_server_thread, srv); if (!srv->thread) { goto error; } srv->send_queue.thread = ecs_os_thread_new(http_server_send_queue, srv); if (!srv->send_queue.thread) { goto error; } return 0; error: return -1; } void ecs_http_server_stop( ecs_http_server_t* srv) { ecs_check(srv != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(srv->initialized, ECS_INVALID_OPERATION, "cannot stop HTTP server: not initialized"); ecs_check(srv->should_run, ECS_INVALID_PARAMETER, "cannot stop HTTP server: already stopped/stopping"); ecs_check(ecs_os_has_threading(), ECS_UNSUPPORTED, "missing OS API implementation"); /* Stop server thread */ ecs_dbg("http: shutting down server thread"); ecs_os_mutex_lock(srv->lock); srv->should_run = false; if (http_socket_is_valid(srv->sock)) { http_close(&srv->sock); } ecs_os_mutex_unlock(srv->lock); ecs_os_thread_join(srv->thread); ecs_os_thread_join(srv->send_queue.thread); ecs_trace("http: server threads shut down"); /* 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, const char *body, ecs_http_reply_t *reply_out) { ecs_check(srv != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(method != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(req != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(reply_out != NULL, ECS_INVALID_PARAMETER, NULL); const char *http_ver = " HTTP/1.1\r\n"; int32_t method_len = ecs_os_strlen(method); int32_t req_len = ecs_os_strlen(req); int32_t body_len = body ? ecs_os_strlen(body) : 0; int32_t http_ver_len = ecs_os_strlen(http_ver); char reqbuf[1024], *reqstr = reqbuf; char content_length[32] = {0}; if (body_len) { ecs_os_snprintf(content_length, 32, "Content-Length: %d\r\n\r\n", body_len); } int32_t content_length_len = ecs_os_strlen(content_length); int32_t len = method_len + req_len + content_length_len + body_len + http_ver_len; if (len >= 1024) { reqstr = ecs_os_malloc(len); } len += 3; char *ptr = reqstr; ecs_os_memcpy(ptr, method, method_len); ptr += method_len; ptr[0] = ' '; ptr ++; ecs_os_memcpy(ptr, req, req_len); ptr += req_len; ecs_os_memcpy(ptr, http_ver, http_ver_len); ptr += http_ver_len; if (body) { ecs_os_memcpy(ptr, content_length, content_length_len); ptr += content_length_len; ecs_os_memcpy(ptr, body, body_len); ptr += body_len; } ptr[0] = '\r'; ptr[1] = '\n'; int result = ecs_http_server_http_request(srv, reqstr, len, reply_out); if (reqbuf != reqstr) { ecs_os_free(reqstr); } return result; error: return -1; } void* ecs_http_server_ctx( ecs_http_server_t* srv) { return srv->ctx; } #endif /** * @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); } 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("blue]"))) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_BLUE); } else if (!ecs_os_strncmp(&ptr[2], "magenta]", ecs_os_strlen("magenta]"))) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_MAGENTA); } else if (!ecs_os_strncmp(&ptr[2], "cyan]", ecs_os_strlen("cyan]"))) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_CYAN); } else if (!ecs_os_strncmp(&ptr[2], "yellow]", ecs_os_strlen("yellow]"))) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_YELLOW); } else if (!ecs_os_strncmp(&ptr[2], "grey]", ecs_os_strlen("grey]"))) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_GREY); } else if (!ecs_os_strncmp(&ptr[2], "white]", ecs_os_strlen("white]"))) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); } else if (!ecs_os_strncmp(&ptr[2], "bold]", ecs_os_strlen("bold]"))) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_BOLD); } else if (!ecs_os_strncmp(&ptr[2], "normal]", ecs_os_strlen("normal]"))) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); } else if (!ecs_os_strncmp(&ptr[2], "reset]", ecs_os_strlen("reset]"))) { overrideColor = false; if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); } else { isColor = false; overrideColor = false; } if (isColor) { ptr += 2; while ((ch = *ptr) != ']') ptr ++; dontAppend = true; } if (!autoColor) { overrideColor = true; } } if (ch == '\n') { if (isNum || isStr || isVar || overrideColor) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); overrideColor = false; isNum = false; isStr = false; isVar = false; } } if (!dontAppend) { ecs_strbuf_appendstrn(buf, ptr, 1); } if (!overrideColor) { if (((ch == '\'') || (ch == '"')) && !isStr) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); } } prev = ch; } if (isNum || isStr || isVar || overrideColor) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); } } void ecs_printv_( 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); } } static void flecs_parser_errorv( const char *name, const char *expr, int64_t column_arg, const char *fmt, va_list args, bool is_warning) { if (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); if (is_warning) { ecs_os_warn(name, 0, msg); } else { ecs_os_err(name, 0, msg); } ecs_os_free(msg); } } void ecs_parser_errorv_( const char *name, const char *expr, int64_t column_arg, const char *fmt, va_list args) { flecs_parser_errorv(name, expr, column_arg, fmt, args, false); } void ecs_parser_warningv_( const char *name, const char *expr, int64_t column_arg, const char *fmt, va_list args) { flecs_parser_errorv(name, expr, column_arg, fmt, args, true); } void ecs_parser_error_( const char *name, const char *expr, int64_t column, const char *fmt, ...) { if (ecs_os_api.log_level_ >= -2) { va_list args; va_start(args, fmt); ecs_parser_errorv_(name, expr, column, fmt, args); va_end(args); } } void ecs_parser_warning_( const char *name, const char *expr, int64_t column, const char *fmt, ...) { if (ecs_os_api.log_level_ >= -2) { va_list args; va_start(args, fmt); ecs_parser_warningv_(name, expr, column, fmt, args); va_end(args); } } void ecs_abort_( int32_t err, const char *file, int32_t line, const char *fmt, ...) { if (fmt) { va_list args; va_start(args, fmt); char *msg = flecs_vasprintf(fmt, args); va_end(args); ecs_fatal_(file, line, "%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_parser_warning_( const char *name, const char *expr, int64_t column, const char *fmt, ...) { (void)name; (void)expr; (void)column; (void)fmt; } void ecs_parser_warningv_( const char *name, const char *expr, int64_t column, const char *fmt, va_list args) { (void)name; (void)expr; (void)column; (void)fmt; (void)args; } void ecs_abort_( int32_t error_code, const char *file, int32_t line, const char *fmt, ...) { (void)error_code; (void)file; (void)line; (void)fmt; } void ecs_assert_log_( int32_t error_code, const char *condition_str, const char *file, int32_t line, const char *fmt, ...) { (void)error_code; (void)condition_str; (void)file; (void)line; (void)fmt; } #endif int ecs_log_get_level(void) { return ecs_os_api.log_level_; } int ecs_log_set_level( int level) { int prev = ecs_os_api.log_level_; ecs_os_api.log_level_ = level; return prev; } bool ecs_log_enable_colors( bool enabled) { bool prev = ecs_os_api.flags_ & EcsOsApiLogWithColors; ECS_BIT_COND(ecs_os_api.flags_, EcsOsApiLogWithColors, enabled); return prev; } bool ecs_log_enable_timestamp( bool enabled) { bool prev = ecs_os_api.flags_ & EcsOsApiLogWithTimeStamp; ECS_BIT_COND(ecs_os_api.flags_, EcsOsApiLogWithTimeStamp, enabled); return prev; } bool ecs_log_enable_timedelta( bool enabled) { bool prev = ecs_os_api.flags_ & EcsOsApiLogWithTimeDelta; ECS_BIT_COND(ecs_os_api.flags_, EcsOsApiLogWithTimeDelta, enabled); return prev; } int ecs_log_last_error(void) { int result = ecs_os_api.log_last_error_; ecs_os_api.log_last_error_ = 0; return result; } /** * @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, it->trs[0]->column, it->offset); EcsMetricOneOfInstance *mi = ecs_field(it, EcsMetricOneOfInstance, 1); ecs_ftime_t dt = it->delta_time; int32_t i, count = it->count; for (i = 0; i < count; i ++) { ecs_oneof_metric_ctx_t *ctx = mi[i].ctx; ecs_record_t *r = mi[i].r; if (!r) { continue; } ecs_table_t *mtable = r->table; double *value = ECS_ELEM(m, ctx->size, i); if (!counter) { ecs_os_memset(value, 0, ctx->size); } if (!mtable) { ecs_delete(it->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 }, .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 }, .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 = metric }); ecs_os_free(to_snake_case); ecs_set(world, mbr, EcsMember, { .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }); /* Truncate upper 32 bits of target so we can lookup the offset * with the id we get from the pair */ ecs_map_ensure(&ctx->target_offset, (uint32_t)tgt)[0] = offset; offset += sizeof(double); } } ctx->size = flecs_uto(ecs_size_t, offset); ecs_observer(world, { .entity = metric, .events = { EcsMonitor }, .query.terms[0] = { .id = desc->id }, .callback = flecs_metrics_on_oneof_metric, .yield_existing = true, .ctx = ctx }); ecs_set(world, metric, EcsMetricOneOf, { .ctx = ctx }); ecs_add_pair(world, metric, EcsMetric, desc->kind); ecs_add_id(world, metric, EcsMetric); return 0; error: return -1; } static int flecs_count_id_targets_metric_init( ecs_world_t *world, ecs_entity_t metric, const ecs_metric_desc_t *desc) { ecs_count_targets_metric_ctx_t *ctx = ecs_os_calloc_t(ecs_count_targets_metric_ctx_t); ctx->metric.metric = metric; ctx->metric.kind = desc->kind; ctx->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) { flecs_poly_assert(world, ecs_world_t); ecs_check(!(world->flags & EcsWorldReadonly), ECS_INVALID_WHILE_READONLY, NULL); ecs_entity_t old_scope = ecs_set_scope(world, 0); const char *old_name_prefix = world->info.name_prefix; char *path = flecs_module_path_from_c(module_name); ecs_entity_t e = ecs_lookup(world, path); 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 ecs_os_api_log_t rest_prev_fatal_log; static void flecs_set_prev_log( ecs_os_api_log_t prev_log, bool try) { rest_prev_log = try ? NULL : prev_log; rest_prev_fatal_log = prev_log; } static void flecs_rest_capture_log( int32_t level, const char *file, int32_t line, const char *msg) { (void)file; (void)line; if (level <= -4) { /* Make sure to always log fatal errors */ if (rest_prev_fatal_log) { ecs_log_enable_colors(true); rest_prev_fatal_log(level, file, line, msg); ecs_log_enable_colors(false); return; } else { fprintf(stderr, "%s:%d: %s", file, line, msg); } } #ifdef FLECS_DEBUG /* In debug mode, log unexpected errors to the console */ if (level < 0) { /* Also log to previous log function in debug mode */ if (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 <= -3) { 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) { code = req->body; } bool try = false; flecs_rest_bool_param(req, "try", &try); if (!code) { flecs_reply_error(reply, "missing code parameter"); if (!try) { reply->code = 400; } return true; } bool prev_color = ecs_log_enable_colors(false); ecs_os_api_log_t prev_log = ecs_os_api.log_; flecs_set_prev_log(ecs_os_api.log_, try); 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, "%s", 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, "%s", 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; } bool try = false; flecs_rest_bool_param(req, "try", &try); ecs_query_t *q = NULL; const EcsPoly *poly_comp = ecs_get_pair(world, qe, EcsPoly, EcsQuery); if (!poly_comp) { poly_comp = ecs_get_pair(world, qe, EcsPoly, EcsObserver); if (poly_comp) { q = ((ecs_observer_t*)poly_comp->poly)->query; } else { flecs_reply_error(reply, "resolved identifier '%s' is not a query", name); reply->code = 400; return true; } } else { q = poly_comp->poly; } if (!q) { flecs_reply_error(reply, "query '%s' is not initialized", name); reply->code = 400; return true; } ecs_iter_t it = ecs_query_iter(world, q); ecs_dbg_2("rest: request query '%s'", name); bool prev_color = ecs_log_enable_colors(false); flecs_set_prev_log(ecs_os_api.log_, try); 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_; flecs_set_prev_log(ecs_os_api.log_, try); 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, true); ecs_strbuf_appendch(reply, '"'); bool disabled = ecs_has_id(world, system, EcsDisabled); ecs_strbuf_list_appendlit(reply, "\"disabled\":"); ecs_strbuf_appendstr(reply, disabled ? "true" : "false"); if (!stats->task) { ECS_GAUGE_APPEND(reply, &stats->query, matched_table_count, ""); ECS_GAUGE_APPEND(reply, &stats->query, matched_entity_count, ""); } ECS_COUNTER_APPEND_T(reply, stats, time_spent, stats->query.t, ""); ecs_strbuf_list_pop(reply, "}"); } static void flecs_sync_stats_to_json( ecs_http_reply_t *reply, const ecs_pipeline_stats_t *pstats, const ecs_sync_stats_t *stats) { ecs_strbuf_list_push(&reply->body, "{", ","); ecs_strbuf_list_appendlit(&reply->body, "\"multi_threaded\":"); ecs_strbuf_appendbool(&reply->body, stats->multi_threaded); ecs_strbuf_list_appendlit(&reply->body, "\"immediate\":"); ecs_strbuf_appendbool(&reply->body, stats->immediate); ECS_GAUGE_APPEND_T(&reply->body, stats, time_spent, pstats->t, ""); ECS_GAUGE_APPEND_T(&reply->body, stats, commands_enqueued, pstats->t, ""); ecs_strbuf_list_pop(&reply->body, "}"); } static void flecs_all_systems_stats_to_json( ecs_world_t *world, ecs_http_reply_t *reply, ecs_entity_t period) { const EcsSystemStats *stats = ecs_get_pair(world, EcsWorld, EcsSystemStats, period); ecs_strbuf_list_push(&reply->body, "[", ","); if (stats) { ecs_map_iter_t it = ecs_map_iter(&stats->stats); while (ecs_map_next(&it)) { ecs_entity_t id = ecs_map_key(&it); ecs_system_stats_t *sys_stats = ecs_map_ptr(&it); if (!ecs_is_alive(world, id)) { continue; } ecs_strbuf_list_next(&reply->body); flecs_system_stats_to_json(world, &reply->body, id, sys_stats); } } ecs_strbuf_list_pop(&reply->body, "]"); } static void flecs_pipeline_stats_to_json( ecs_world_t *world, const ecs_http_request_t* req, ecs_http_reply_t *reply, ecs_entity_t period) { char *pipeline_name = NULL; flecs_rest_string_param(req, "name", &pipeline_name); if (!pipeline_name || !ecs_os_strcmp(pipeline_name, "all")) { flecs_all_systems_stats_to_json(world, reply, period); return; } ecs_entity_t e = ecs_lookup(world, pipeline_name); if (!e) { flecs_reply_error(reply, "pipeline '%s' not found", pipeline_name); reply->code = 404; return; } const EcsPipelineStats *stats = ecs_get_pair(world, EcsWorld, EcsPipelineStats, period); const EcsSystemStats *system_stats = ecs_get_pair(world, EcsWorld, EcsSystemStats, period); if (!stats || !system_stats) { goto noresults; } ecs_pipeline_stats_t *pstats = ecs_map_get_deref( &stats->stats, ecs_pipeline_stats_t, e); if (!pstats) { goto noresults; } const EcsPipeline *p = ecs_get(world, e, EcsPipeline); ecs_strbuf_list_push(&reply->body, "[", ","); ecs_pipeline_op_t *ops = ecs_vec_first_t(&p->state->ops, ecs_pipeline_op_t); ecs_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; int32_t count = ecs_table_count(table), size = ecs_table_size(table); used += count * ECS_SIZEOF(ecs_entity_t); allocated += 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 += count * columns[i].ti->size; allocated += 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 = ecs_field(it, EcsRest, 0); int i; for(i = 0; i < it->count; i ++) { if (!rest[i].port) { rest[i].port = ECS_REST_DEFAULT_PORT; } ecs_http_server_t *srv = 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; if (size != 24) { /* Prevent stack overflow as map uses block allocator */ ba->outstanding = ecs_os_malloc_t(ecs_map_t); ecs_map_init(ba->outstanding, NULL); } size += ECS_SIZEOF(int64_t); #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 if (ba->alloc_count != 0) { ecs_err("Leak detected! (size %u, remaining = %d)", (uint32_t)ba->data_size, ba->alloc_count); if (ba->outstanding) { ecs_map_iter_t it = ecs_map_iter(ba->outstanding); while (ecs_map_next(&it)) { uint64_t key = ecs_map_key(&it); char *type_name = ecs_map_ptr(&it); if (type_name) { printf(" - %p (%s)\n", (void*)key, type_name); } else { printf(" - %p (unknown type)\n", (void*)key); } } } ecs_abort(ECS_LEAK_DETECTED, NULL); } if (ba->outstanding) { ecs_map_fini(ba->outstanding); ecs_os_free(ba->outstanding); } #endif ecs_block_allocator_block_t *block; for (block = ba->block_head; block;) { ecs_block_allocator_block_t *next = block->next; ecs_os_free(block); ecs_os_linc(&ecs_block_allocator_free_count); block = next; } ba->block_head = NULL; } 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) { return flecs_balloc_w_dbg_info(ba, NULL); } void* flecs_balloc_w_dbg_info( ecs_block_allocator_t *ba, const char *type_name) { (void)type_name; void *result; #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"); if (ba->outstanding) { uint64_t *v = ecs_map_ensure(ba->outstanding, (uintptr_t)result); *(const char**)v = type_name; } ba->alloc_count ++; *(int64_t*)result = (uintptr_t)ba; result = ECS_OFFSET(result, ECS_SIZEOF(int64_t)); #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) { return flecs_bcalloc_w_dbg_info(ba, NULL); } void* flecs_bcalloc_w_dbg_info( ecs_block_allocator_t *ba, const char *type_name) { (void)type_name; #ifdef FLECS_USE_OS_ALLOC ecs_assert(ba != NULL, ECS_INTERNAL_ERROR, NULL); return ecs_os_calloc(ba->data_size); #else if (!ba) return NULL; void *result = flecs_balloc_w_dbg_info(ba, type_name); ecs_os_memset(result, 0, ba->data_size); return result; #endif } void flecs_bfree( ecs_block_allocator_t *ba, void *memory) { flecs_bfree_w_dbg_info(ba, memory, NULL); } void flecs_bfree_w_dbg_info( ecs_block_allocator_t *ba, void *memory, const char *type_name) { (void)type_name; #ifdef FLECS_USE_OS_ALLOC (void)ba; ecs_os_free(memory); return; #else if (!ba) { ecs_assert(memory == NULL, ECS_INTERNAL_ERROR, NULL); return; } if (memory == NULL) { return; } #ifdef FLECS_SANITIZE memory = ECS_OFFSET(memory, -ECS_SIZEOF(int64_t)); ecs_block_allocator_t *actual = *(ecs_block_allocator_t**)memory; if (actual != ba) { if (type_name) { ecs_err("chunk %p returned to wrong allocator " "(chunk = %ub, allocator = %ub, type = %s)", memory, actual->data_size, ba->data_size, type_name); } else { ecs_err("chunk %p returned to wrong allocator " "(chunk = %ub, allocator = %ub)", memory, actual->data_size, ba->chunk_size); } ecs_abort(ECS_INTERNAL_ERROR, NULL); } if (ba->outstanding) { ecs_map_remove(ba->outstanding, (uintptr_t)memory); } ba->alloc_count --; ecs_assert(ba->alloc_count >= 0, ECS_INTERNAL_ERROR, "corrupted allocator (size = %d)", ba->chunk_size); #endif ecs_block_allocator_chunk_header_t *chunk = memory; chunk->next = ba->head; ba->head = chunk; #endif } void* flecs_brealloc( ecs_block_allocator_t *dst, ecs_block_allocator_t *src, void *memory) { return flecs_brealloc_w_dbg_info(dst, src, memory, NULL); } void* flecs_brealloc_w_dbg_info( ecs_block_allocator_t *dst, ecs_block_allocator_t *src, void *memory, const char *type_name) { (void)type_name; void *result; #ifdef FLECS_USE_OS_ALLOC (void)src; result = ecs_os_realloc(memory, dst->data_size); #else if (dst == src) { return memory; } result = flecs_balloc_w_dbg_info(dst, type_name); if (result && src) { ecs_size_t size = src->data_size; if (dst->data_size < size) { size = dst->data_size; } ecs_os_memcpy(result, memory, size); } flecs_bfree_w_dbg_info(src, memory, type_name); #endif #ifdef FLECS_MEMSET_UNINITIALIZED if (dst && src && (dst->data_size > src->data_size)) { ecs_os_memset(ECS_OFFSET(result, src->data_size), 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); #if defined(FLECS_SANITIZE) || defined(FLECS_USE_OS_ALLOC) 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; #if defined(FLECS_SANITIZE) || defined(FLECS_USE_OS_ALLOC) 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; result->sp = 0; 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; ecs_assert(page->data != NULL, ECS_INTERNAL_ERROR, NULL); int16_t sp = flecs_ito(int16_t, ECS_ALIGN(page->sp, align)); int16_t next_sp = flecs_ito(int16_t, sp + size); 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: ecs_assert(result != NULL, ECS_INTERNAL_ERROR, NULL); #ifdef FLECS_SANITIZE ecs_os_memset(result, 0xAA, size); #endif return result; } void* flecs_stack_calloc( ecs_stack_t *stack, ecs_size_t size, ecs_size_t align) { void *ptr = flecs_stack_alloc(stack, size, align); ecs_os_memset(ptr, 0, size); return ptr; } void flecs_stack_free( void *ptr, ecs_size_t size) { if (size > 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->first = flecs_stack_page_new(0); stack->first->sp = 0; stack->tail_page = stack->first; } 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; 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; node->next = 0; } 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; ecs_assert(node->next != element, ECS_INTERNAL_ERROR, NULL); } 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_vec_init_w_dbg_info(allocator, v, size, elem_count, NULL); } void ecs_vec_init_w_dbg_info( struct ecs_allocator_t *allocator, ecs_vec_t *v, ecs_size_t size, int32_t elem_count, const char *type_name) { (void)type_name; ecs_assert(size != 0, ECS_INVALID_PARAMETER, NULL); v->array = NULL; v->count = 0; if (elem_count) { if (allocator) { v->array = flecs_alloc_w_dbg_info( allocator, size * elem_count, type_name); } else { v->array = ecs_os_malloc(size * elem_count); } } v->size = elem_count; #ifdef FLECS_SANITIZE v->elem_size = size; v->type_name = type_name; #endif } void ecs_vec_init_if( ecs_vec_t *vec, ecs_size_t size) { ecs_san_assert(!vec->elem_size || vec->elem_size == size, ECS_INVALID_PARAMETER, NULL); (void)vec; (void)size; #ifdef FLECS_SANITIZE if (!vec->elem_size) { ecs_assert(vec->count == 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(vec->size == 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(vec->array == NULL, ECS_INTERNAL_ERROR, NULL); vec->elem_size = size; } #endif } void ecs_vec_fini( ecs_allocator_t *allocator, ecs_vec_t *v, ecs_size_t size) { if (v->array) { ecs_san_assert(!size || size == v->elem_size, ECS_INVALID_PARAMETER, NULL); if (allocator) { flecs_free(allocator, size * v->size, v->array); } else { ecs_os_free(v->array); } v->array = NULL; v->count = 0; v->size = 0; } } ecs_vec_t* ecs_vec_reset( ecs_allocator_t *allocator, ecs_vec_t *v, ecs_size_t size) { if (!v->size) { ecs_vec_init(allocator, v, size, 0); } else { ecs_san_assert(size == v->elem_size, ECS_INTERNAL_ERROR, NULL); ecs_vec_clear(v); } return v; } void ecs_vec_clear( ecs_vec_t *vec) { vec->count = 0; } ecs_vec_t ecs_vec_copy( ecs_allocator_t *allocator, const ecs_vec_t *v, ecs_size_t size) { ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); void *array; if (allocator) { array = flecs_dup(allocator, size * v->size, v->array); } else { array = ecs_os_memdup(v->array, size * v->size); } return (ecs_vec_t) { .count = v->count, .size = v->size, .array = array #ifdef FLECS_SANITIZE , .elem_size = size #endif }; } 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) { #ifdef FLECS_SANITIZE v->array = flecs_realloc_w_dbg_info( allocator, size * elem_count, size * v->size, v->array, v->type_name); #else v->array = flecs_realloc( allocator, size * elem_count, size * v->size, v->array); #endif } else { v->array = ecs_os_realloc(v->array, size * elem_count); } v->size = elem_count; } } } void ecs_vec_set_min_size( struct ecs_allocator_t *allocator, ecs_vec_t *vec, ecs_size_t size, int32_t elem_count) { if (elem_count > vec->size) { ecs_vec_set_size(allocator, vec, size, elem_count); } } void ecs_vec_set_min_count( struct ecs_allocator_t *allocator, ecs_vec_t *vec, ecs_size_t size, int32_t elem_count) { ecs_vec_set_min_size(allocator, vec, size, elem_count); if (vec->count < elem_count) { vec->count = elem_count; } } void ecs_vec_set_min_count_zeromem( struct ecs_allocator_t *allocator, ecs_vec_t *vec, ecs_size_t size, int32_t elem_count) { int32_t count = vec->count; if (count < elem_count) { ecs_vec_set_min_count(allocator, vec, size, elem_count); ecs_os_memset(ECS_ELEM(vec->array, size, count), 0, size * (elem_count - count)); } } void ecs_vec_set_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 /* Placeholder arrays for queries that only have $this variable */ ecs_query_var_t flecs_this_array = { .kind = EcsVarTable, .table_id = EcsVarNone }; char *flecs_this_name_array = NULL; 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; bool group_order_by = desc->group_by || desc->group_by_callback || desc->order_by || desc->order_by_callback; /* If caching policy is default, try to pick a policy that does the right * thing in most cases. */ if (kind == EcsQueryCacheDefault) { if (desc->entity || group_order_by) { /* 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. Make sure to only create a cache * if the cacheable part of the query contains not just not/optional * terms, as this would build a cache that contains all tables. */ int32_t not_optional_terms = 0, cacheable_terms = 0; if (!group_order_by) { int32_t i, term_count = impl->pub.term_count; const ecs_term_t *terms = impl->pub.terms; for (i = 0; i < term_count; i ++) { const ecs_term_t *term = &terms[i]; if (term->flags_ & EcsTermIsCacheable) { cacheable_terms ++; if (term->oper == EcsNot || term->oper == EcsOptional) { not_optional_terms ++; } } } } if (group_order_by || cacheable_terms != not_optional_terms) { impl->pub.cache_kind = EcsQueryCacheAuto; } else { impl->pub.cache_kind = EcsQueryCacheNone; } } } 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->cache->field_map = flecs_alloc_n(&impl->stage->allocator, int8_t, FLECS_TERM_COUNT_MAX); ecs_os_memcpy_n(impl->cache->field_map, field_map, int8_t, dst_count); } } else { /* Check if query has features that are unsupported for uncached */ ecs_assert(q->cache_kind == EcsQueryCacheNone, ECS_INTERNAL_ERROR, NULL); if (!(q->flags & EcsQueryNested)) { /* If uncached query is not create to populate a cached query, it * should not have cascade modifiers */ int32_t i, count = q->term_count; ecs_term_t *terms = q->terms; for (i = 0; i < count; i ++) { ecs_term_t *term = &terms[i]; if (term->src.id & EcsCascade) { char *query_str = ecs_query_str(q); ecs_err( "cascade is unsupported for uncached query\n %s", query_str); ecs_os_free(query_str); goto error; } } } } 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 != &flecs_this_array) { flecs_free(a, (ECS_SIZEOF(ecs_query_var_t) + ECS_SIZEOF(char*)) * impl->var_size, impl->vars); flecs_name_index_fini(&impl->tvar_index); flecs_name_index_fini(&impl->evar_index); } flecs_free_n(a, ecs_query_op_t, impl->op_count, impl->ops); flecs_free_n(a, ecs_var_id_t, impl->pub.field_count, impl->src_vars); flecs_free_n(a, int32_t, impl->pub.field_count, impl->monitor); 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 (!(ecs_world_get_flags(q->world) & 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_free_n(a, int8_t, FLECS_TERM_COUNT_MAX, impl->cache->field_map); 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 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); /* Validate input, translate to canonical query representation */ if (flecs_query_finalize_query(world, &result->pub, &desc)) { goto error; } /* If query terms have itself as source, add term ids to self. This makes it * easy to attach components to queries, which is one of the ways * applications can attach data to systems. */ flecs_query_add_self_ref(&result->pub); /* Initialize static context */ result->pub.ctx = const_desc->ctx; result->pub.binding_ctx = const_desc->binding_ctx; result->ctx_free = const_desc->ctx_free; result->binding_ctx_free = const_desc->binding_ctx_free; result->dtor = flecs_query_poly_fini; result->cache = NULL; /* Initialize query cache if necessary */ if (flecs_query_create_cache(result, &desc)) { goto error; } 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); 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); 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_iter_t it = flecs_query_iter(q->world, q); it.flags |= EcsIterNoData; while (ecs_query_next(&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_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; } } const ecs_query_t* ecs_query_get_cache_query( const ecs_query_t *q) { ecs_query_impl_t *impl = flecs_query_impl(q); if (!impl->cache) { return NULL; } else { return impl->cache->query; } } const ecs_query_t* ecs_query_get( const ecs_world_t *world, ecs_entity_t query) { const EcsPoly *poly_comp = ecs_get_pair(world, query, EcsPoly, EcsQuery); if (!poly_comp) { return NULL; } else { flecs_poly_assert(poly_comp->poly, ecs_query_t); return poly_comp->poly; } } /** * @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 EcsQueryAndAny: return "andany "; case EcsQueryTriv: return "triv "; case EcsQueryCache: return "cache "; case EcsQueryIsCache: return "xcache "; case EcsQueryOnlyAny: return "any "; case EcsQueryUp: return "up "; case EcsQuerySelfUp: return "selfup "; 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 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; if (!count) { ecs_strbuf_append(buf, ""); return; /* No plan */ } for (i = 0; i < count; i ++) { ecs_query_op_t *op = &ops[i]; ecs_flags16_t flags = op->flags; ecs_flags16_t src_flags = flecs_query_ref_flags(flags, EcsQuerySrc); ecs_flags16_t first_flags = flecs_query_ref_flags(flags, EcsQueryFirst); ecs_flags16_t second_flags = flecs_query_ref_flags(flags, EcsQuerySecond); if (it) { #ifdef FLECS_DEBUG const ecs_query_iter_t *rit = &it->priv_.iter.query; ecs_strbuf_append(buf, "#[green]%4d -> #[red]%4d <- #[grey] | ", rit->profile[i].count[0], rit->profile[i].count[1]); #endif } ecs_strbuf_append(buf, "#[normal]%2d. [#[grey]%2d#[reset], #[green]%2d#[reset]] ", i, op->prev, op->next); int32_t hidden_chars, start = ecs_strbuf_written(buf); if (op->kind == EcsQueryEnd) { indent --; } ecs_strbuf_append(buf, "%*s", indent, ""); ecs_strbuf_appendstr(buf, flecs_query_op_str(op->kind)); ecs_strbuf_appendstr(buf, " "); int32_t written = ecs_strbuf_written(buf); for (int32_t j = 0; j < (12 - (written - start)); j ++) { ecs_strbuf_appendch(buf, ' '); } hidden_chars = flecs_query_op_ref_str(impl, &op->src, src_flags, buf); if (op->kind == EcsQueryNot || op->kind == EcsQueryOr || op->kind == EcsQueryOptional || op->kind == EcsQueryIfVar || op->kind == EcsQueryIfSet) { indent ++; } if (op->kind == EcsQueryTriv) { flecs_query_str_append_bitset(buf, op->src.entity); } if (op->kind == EcsQueryIfSet) { ecs_strbuf_append(buf, "[%d]\n", op->other); continue; } bool is_toggle = op->kind == EcsQueryToggle || op->kind == EcsQueryToggleOption; if (!first_flags && !second_flags && !is_toggle) { ecs_strbuf_appendstr(buf, "\n"); continue; } written = ecs_strbuf_written(buf) - hidden_chars; for (int32_t j = 0; j < (30 - (written - start)); j ++) { ecs_strbuf_appendch(buf, ' '); } if (is_toggle) { if (op->first.entity) { flecs_query_str_append_bitset(buf, op->first.entity); } if (op->second.entity) { if (op->first.entity) { ecs_strbuf_appendlit(buf, ", !"); } flecs_query_str_append_bitset(buf, op->second.entity); } ecs_strbuf_appendstr(buf, "\n"); continue; } ecs_strbuf_appendstr(buf, "("); if (op->kind == EcsQueryMemberEq || op->kind == EcsQueryMemberNeq) { uint32_t offset = (uint32_t)op->first.entity; uint32_t size = (uint32_t)(op->first.entity >> 32); ecs_strbuf_append(buf, "#[yellow]elem#[reset]([%d], 0x%x, 0x%x)", op->field_index, size, offset); } else { flecs_query_op_ref_str(impl, &op->first, first_flags, buf); } if (second_flags) { ecs_strbuf_appendstr(buf, ", "); flecs_query_op_ref_str(impl, &op->second, second_flags, buf); } else { switch (op->kind) { case EcsQueryPredEqName: case EcsQueryPredNeqName: case EcsQueryPredEqMatch: case EcsQueryPredNeqMatch: { int8_t term_index = op->term_index; ecs_strbuf_appendstr(buf, ", #[yellow]\""); ecs_strbuf_appendstr(buf, q->terms[term_index].second.name); ecs_strbuf_appendstr(buf, "\"#[reset]"); break; } case EcsQueryLookup: { ecs_var_id_t src_id = op->src.var; ecs_strbuf_appendstr(buf, ", #[yellow]\""); ecs_strbuf_appendstr(buf, impl->vars[src_id].lookup); ecs_strbuf_appendstr(buf, "\"#[reset]"); break; } default: break; } } ecs_strbuf_appendch(buf, ')'); ecs_strbuf_appendch(buf, '\n'); } } char* ecs_query_plan_w_profile( const ecs_query_t *q, const ecs_iter_t *it) { flecs_poly_assert(q, ecs_query_t); ecs_strbuf_t buf = ECS_STRBUF_INIT; flecs_query_plan_w_profile(q, it, &buf); // 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) { if (term->src.name) { ecs_strbuf_appendstr(buf, term->src.name); } else { ecs_strbuf_appendstr(buf, "<>"); } } else { /* Shouldn't happen */ } if (ECS_TERM_REF_ID(&term->first) == EcsPredEq) { if (term->oper == EcsNot) { ecs_strbuf_appendlit(buf, " != "); } else { ecs_strbuf_appendlit(buf, " == "); } } else if (ECS_TERM_REF_ID(&term->first) == EcsPredMatch) { ecs_strbuf_appendlit(buf, " ~= "); } if (term->second.id & EcsIsEntity) { if (term->second.id != 0) { ecs_get_path_w_sep_buf(world, 0, ECS_TERM_REF_ID(&term->second), ".", NULL, buf, false); } } else { if (term->second.id & EcsIsVariable) { ecs_strbuf_appendlit(buf, "$"); if (term->second.name) { ecs_strbuf_appendstr(buf, term->second.name); } else if (ECS_TERM_REF_ID(&term->second) == EcsThis) { ecs_strbuf_appendlit(buf, "this"); } } else if (term->second.id & EcsIsName) { ecs_strbuf_appendlit(buf, "\""); if ((ECS_TERM_REF_ID(&term->first) == EcsPredMatch) && (term->oper == EcsNot)) { ecs_strbuf_appendlit(buf, "!"); } ecs_strbuf_appendstr(buf, term->second.name); ecs_strbuf_appendlit(buf, "\""); } } return; } if (!t || !(term[-1].oper == EcsOr)) { if (term->inout == EcsIn) { ecs_strbuf_appendlit(buf, "[in] "); } else if (term->inout == EcsInOut) { ecs_strbuf_appendlit(buf, "[inout] "); } else if (term->inout == EcsOut) { ecs_strbuf_appendlit(buf, "[out] "); } else if (term->inout == EcsInOutNone && term->oper != EcsNot) { ecs_strbuf_appendlit(buf, "[none] "); } } if (term->oper == EcsNot) { ecs_strbuf_appendlit(buf, "!"); } else if (term->oper == EcsOptional) { ecs_strbuf_appendlit(buf, "?"); } if (!src_set) { flecs_query_str_add_id(world, buf, term, &term->first, false); if (!second_set) { ecs_strbuf_appendlit(buf, "()"); } else { ecs_strbuf_appendlit(buf, "(#0,"); flecs_query_str_add_id(world, buf, term, &term->second, false); ecs_strbuf_appendlit(buf, ")"); } } else { ecs_id_t flags = term->id & ECS_ID_FLAGS_MASK; if (flags && !ECS_HAS_ID_FLAG(flags, PAIR)) { ecs_strbuf_appendstr(buf, ecs_id_flag_str(flags)); ecs_strbuf_appendch(buf, '|'); } flecs_query_str_add_id(world, buf, term, &term->first, false); ecs_strbuf_appendlit(buf, "("); 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, EcsIterHasCondSet, ECS_BIT_IS_SET(query->flags, EcsQueryHasCondSet)); ECS_BIT_COND(it->flags, EcsIterNoData, query->data_fields == 0); } /** * @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->first.id & EcsCascade) { flecs_query_validator_error(ctx, "cascade modifier invalid for term.first"); return -1; } if (term->second.id & EcsCascade) { flecs_query_validator_error(ctx, "cascade modifier invalid for term.second"); return -1; } if (term->first.id & EcsDesc) { flecs_query_validator_error(ctx, "desc modifier invalid for term.first"); return -1; } if (term->second.id & EcsDesc) { flecs_query_validator_error(ctx, "desc modifier invalid for term.second"); return -1; } if (term->src.id & EcsDesc && !(term->src.id & EcsCascade)) { flecs_query_validator_error(ctx, "desc modifier invalid without cascade"); return -1; } if (term->src.id & EcsCascade) { /* Cascade always implies up traversal */ term->src.id |= EcsUp; } if ((src->id & EcsUp) && !term->trav) { /* When traversal flags are specified but no traversal relationship, * default to ChildOf, which is the most common kind of traversal. */ term->trav = EcsChildOf; } } if (!(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) { if (ecs_term_ref_is_set(second)) { /* Add traversal flags for transitive relationships */ if (!((second_flags & EcsTraverseFlags) == EcsSelf)) { if (!((src->id & EcsIsVariable) && (src_id == EcsAny))) { if (!((second->id & EcsIsVariable) && (second_id == EcsAny))) { if (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->flags_ & EcsTermReflexive) { 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) { int8_t i, term_count = q->term_count, field_count = 0; ecs_term_t *terms = q->terms; int32_t scope_nesting = 0, cacheable_terms = 0; bool cond_set = false; ecs_query_validator_ctx_t ctx = {0}; ctx.world = world; ctx.query = q; ctx.desc = desc; q->flags |= EcsQueryMatchOnlyThis; for (i = 0; i < term_count; i ++) { ecs_term_t *term = &terms[i]; if (term->oper == EcsOr) { term->flags_ |= EcsTermIsOr; if (i != (term_count - 1)) { term[1].flags_ |= EcsTermIsOr; } } } bool cacheable = true; bool match_nothing = true; 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 (term->src.id != EcsIsEntity) { /* If term doesn't match 0 entity, query doesn't match nothing */ match_nothing = false; } if (scope_nesting) { /* Terms inside a scope are not cacheable */ ECS_BIT_CLEAR16(term->flags_, EcsTermIsCacheable); } /* If 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(int8_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 (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 (term->flags_ & EcsTermIsUnion) { nodata_term = true; } else if (term->flags_ & EcsTermIsMember) { nodata_term = true; } else if (scope_nesting) { 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 (!nodata_term && term->inout != EcsIn && term->inout != EcsInOutNone) { /* Non-this terms default to EcsIn */ if (ecs_term_match_this(term) || term->inout != EcsInOutDefault) { q->flags |= EcsQueryHasOutTerms; } bool match_non_this = !ecs_term_match_this(term) || (term->src.id & EcsUp); if (match_non_this && term->inout != EcsInOutDefault) { q->flags |= EcsQueryHasNonThisOutTerms; } } if (!nodata_term) { /* 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) { nodata_term = true; } q->data_fields &= (ecs_termset_t)~(1llu << term->field_index); } } if (term->flags_ & EcsTermIsMember) { nodata_term = false; } if (!nodata_term && term->oper != EcsNot) { ECS_TERMSET_SET(q->data_fields, 1u << term->field_index); if (term->inout != EcsIn) { ECS_TERMSET_SET(q->write_fields, 1u << term->field_index); } if (term->inout != EcsOut) { ECS_TERMSET_SET(q->read_fields, 1u << term->field_index); } if (term->inout == EcsInOutDefault) { ECS_TERMSET_SET(q->shared_readonly_fields, 1u << term->field_index); } } if (ECS_TERM_REF_ID(&term->src) && (term->src.id & EcsIsEntity)) { ECS_TERMSET_SET(q->fixed_fields, 1u << term->field_index); } if ((term->src.id & EcsIsVariable) && (ECS_TERM_REF_ID(&term->src) != EcsThis)) { ECS_TERMSET_SET(q->var_fields, 1u << term->field_index); } bool is_sparse = false; 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) { is_sparse = true; } } else { ecs_entity_t type = ecs_get_typeid(world, term->id); if (type && ecs_has_id(world, type, EcsSparse)) { is_sparse = true; } } if (is_sparse) { term->flags_ |= EcsTermIsSparse; ECS_BIT_CLEAR16(term->flags_, EcsTermIsTrivial); if (term->flags_ & EcsTermIsCacheable) { cacheable_terms --; ECS_BIT_CLEAR16(term->flags_, EcsTermIsCacheable); } /* Sparse component fields must be accessed with ecs_field_at */ if (!nodata_term) { q->row_fields |= flecs_uto(uint32_t, 1llu << i); } } 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 '{'"); return -1; } } if (scope_nesting != 0) { flecs_query_validator_error(&ctx, "missing '}'"); return -1; } if (term_count && (terms[term_count - 1].oper == EcsOr)) { flecs_query_validator_error(&ctx, "last term of query can't have OR operator"); return -1; } 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; } } } } 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 (term_count && (i == term_count)) { ECS_BIT_SET(q->flags, EcsQueryIsTrivial); } } } /* Set cacheable flags */ ECS_BIT_COND(q->flags, EcsQueryHasCacheable, cacheable_terms != 0); /* Exclude queries with order_by from setting the IsCacheable flag. This * allows the routine that evaluates entirely cached queries to use more * optimized logic as it doesn't have to deal with order_by edge cases */ ECS_BIT_COND(q->flags, EcsQueryIsCacheable, cacheable && (cacheable_terms == term_count) && !desc->order_by_callback); /* If none of the terms match a source, the query matches nothing */ ECS_BIT_COND(q->flags, EcsQueryMatchNothing, match_nothing); 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( &flecs_query_impl(q)->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; } #ifndef FLECS_SANITIZE static bool flecs_query_finalize_simple( ecs_world_t *world, ecs_query_t *q, const ecs_query_desc_t *desc) { /* Filter out queries that aren't simple enough */ if (desc->expr) { return false; } if (desc->order_by_callback || desc->group_by_callback) { return false; } int8_t i, term_count; for (i = 0; i < FLECS_TERM_COUNT_MAX; i ++) { if (!ecs_term_is_initialized(&desc->terms[i])) { break; } ecs_id_t id = desc->terms[i].id; if (ecs_id_is_wildcard(id)) { return false; } if (id == EcsThis || ECS_PAIR_FIRST(id) == EcsThis || ECS_PAIR_SECOND(id) == EcsThis) { return false; } if (id == EcsVariable || ECS_PAIR_FIRST(id) == EcsVariable || ECS_PAIR_SECOND(id) == EcsVariable) { return false; } if (id == EcsPrefab || id == EcsDisabled) { return false; } ecs_term_t term = { .id = desc->terms[i].id }; if (ecs_os_memcmp_t(&term, &desc->terms[i], ecs_term_t)) { return false; } } if (!i) { return false; /* No terms */ } term_count = i; ecs_os_memcpy_n(&q->terms, desc->terms, ecs_term_t, term_count); /* Simple query that only queries for component ids */ /* Populate terms */ int8_t cacheable_count = 0, trivial_count = 0, up_count = 0; for (i = 0; i < term_count; i ++) { ecs_term_t *term = &q->terms[i]; ecs_id_t id = term->id; ecs_entity_t first = id; if (ECS_IS_PAIR(id)) { ecs_entity_t second = flecs_entities_get_alive(world, ECS_PAIR_SECOND(id)); first = flecs_entities_get_alive(world, ECS_PAIR_FIRST(id)); term->second.id = second | EcsIsEntity | EcsSelf; } term->field_index = i; term->first.id = first | EcsIsEntity | EcsSelf; term->src.id = EcsThis | EcsIsVariable | EcsSelf; q->ids[i] = id; ecs_id_record_t *idr = flecs_id_record_get(world, id); if (idr) { idr->keep_alive ++; term->flags_ |= EcsTermKeepAlive; } if (!idr && ECS_IS_PAIR(id)) { idr = flecs_id_record_get(world, ecs_pair(ECS_PAIR_FIRST(id), EcsWildcard)); } bool cacheable = true, trivial = true; if (idr) { if (idr->type_info) { q->sizes[i] = idr->type_info->size; q->flags |= EcsQueryHasOutTerms; q->data_fields |= (ecs_termset_t)(1llu << i); } if (idr->flags & EcsIdOnInstantiateInherit) { term->src.id |= EcsUp; term->trav = EcsIsA; up_count ++; } if (idr->flags & EcsIdCanToggle) { term->flags_ |= EcsTermIsToggle; trivial = false; } if (ECS_IS_PAIR(id)) { if (idr->flags & EcsIdIsUnion) { term->flags_ |= EcsTermIsUnion; trivial = false; cacheable = false; } } if (idr->flags & EcsIdIsSparse) { term->flags_ |= EcsTermIsSparse; cacheable = false; trivial = false; q->row_fields |= flecs_uto(uint32_t, 1llu << i); } } if (ECS_IS_PAIR(id)) { if (ecs_has_id(world, first, EcsTransitive)) { term->flags_ |= EcsTermTransitive; trivial = false; cacheable = false; } if (ecs_has_id(world, first, EcsReflexive)) { term->flags_ |= EcsTermReflexive; trivial = false; cacheable = false; } } if (flecs_id_record_get(world, ecs_pair(EcsIsA, first)) != NULL) { term->flags_ |= EcsTermIdInherited; cacheable = false; trivial = false; } if (cacheable) { term->flags_ |= EcsTermIsCacheable; cacheable_count ++; } if (trivial) { term->flags_ |= EcsTermIsTrivial; trivial_count ++; } } /* Initialize static data */ q->term_count = term_count; q->field_count = term_count; q->set_fields = (ecs_termset_t)((1llu << i) - 1); q->static_id_fields = (ecs_termset_t)((1llu << i) - 1); q->flags |= EcsQueryMatchThis|EcsQueryMatchOnlyThis|EcsQueryHasTableThisVar; if (cacheable_count) { q->flags |= EcsQueryHasCacheable; } if (cacheable_count == term_count && trivial_count == term_count) { q->flags |= EcsQueryIsCacheable|EcsQueryIsTrivial; } if (!up_count) { q->flags |= EcsQueryMatchOnlySelf; } return true; } #endif 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); } } 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; /* Fast routine that initializes simple queries and skips complex validation * logic if it's not needed. When running in sanitized mode, always take the * slow path. This in combination with the test suite ensures that the * result of the fast & slow code is the same. */ #ifndef FLECS_SANITIZE if (flecs_query_finalize_simple(world, q, desc)) { return 0; } #endif /* Populate term array from desc terms & DSL expression */ if (flecs_query_query_populate_terms(world, stage, q, desc)) { goto error; } /* Ensure all fields are consistent and properly filled out */ if (flecs_query_finalize_terms(world, q, desc)) { goto error; } /* Store remaining string tokens in terms (after entity lookups) in single * token buffer which simplifies memory management & reduces allocations. */ flecs_query_populate_tokens(flecs_query_impl(q)); 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_assert(index->max_id <= UINT32_MAX, ECS_INVALID_OPERATION, "max id %u exceeds 32 bits", index->max_id); /* Make sure id hasn't been issued before */ ecs_assert(!flecs_entity_index_exists(index, id), ECS_INVALID_OPERATION, "new entity %u id already in use (likely due to overlapping ranges)", (uint32_t)id); ecs_vec_append_t(index->allocator, &index->dense, uint64_t)[0] = id; ecs_entity_index_page_t *page = flecs_entity_index_ensure_page(index, id); ecs_assert(page != NULL, ECS_INTERNAL_ERROR, NULL); ecs_record_t *r = &page->records[id & FLECS_ENTITY_PAGE_MASK]; r->dense = index->alive_count ++; ecs_assert(index->alive_count == ecs_vec_count(&index->dense), ECS_INTERNAL_ERROR, NULL); return id; } uint64_t* flecs_entity_index_new_ids( ecs_entity_index_t *index, int32_t count) { int32_t alive_count = index->alive_count; int32_t new_count = alive_count + count; int32_t dense_count = ecs_vec_count(&index->dense); if (new_count < dense_count) { /* Recycle ids */ index->alive_count = new_count; return ecs_vec_get_t(&index->dense, uint64_t, alive_count); } /* Allocate new ids */ ecs_vec_set_count_t(index->allocator, &index->dense, uint64_t, new_count); int32_t i, to_add = new_count - dense_count; for (i = 0; i < to_add; i ++) { uint32_t id = (uint32_t)++ index->max_id; ecs_assert(index->max_id <= UINT32_MAX, ECS_INVALID_OPERATION, "max id %u exceeds 32 bits", index->max_id); /* Make sure id hasn't been issued before */ ecs_assert(!flecs_entity_index_exists(index, id), ECS_INVALID_OPERATION, "new entity %u id already in use (likely due to overlapping ranges)", (uint32_t)id); 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_flags32_t flecs_id_record_event_flags( ecs_world_t *world, ecs_id_t id) { ecs_observable_t *o = &world->observable; ecs_flags32_t result = 0; result |= flecs_observers_exist(o, id, EcsOnAdd) * EcsIdHasOnAdd; result |= flecs_observers_exist(o, id, EcsOnRemove) * EcsIdHasOnRemove; result |= flecs_observers_exist(o, id, EcsOnSet) * EcsIdHasOnSet; result |= flecs_observers_exist(o, id, EcsOnTableCreate) * EcsIdHasOnTableCreate; result |= flecs_observers_exist(o, id, EcsOnTableDelete) * EcsIdHasOnTableDelete; return result; } 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); idr = flecs_bcalloc(&world->allocators.id_record); if (hash >= FLECS_HI_ID_RECORD_ID) { ecs_map_insert_ptr(&world->id_index_hi, hash, idr); } else { world->id_index_lo[hash] = 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; } /* If second element of pair determines the type, check if the pair * should be stored as a sparse component. */ if (idr->type_info && idr->type_info->component == tgt) { if (ecs_has_id(world, tgt, EcsSparse)) { idr->flags |= EcsIdIsSparse; } } } idr->flags |= flecs_id_record_event_flags(world, id); 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); } 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); } else { world->id_index_lo[hash] = NULL; } flecs_bfree(&world->allocators.id_record, idr); 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]; } 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) { 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); } static ecs_flags32_t flecs_id_flags( ecs_world_t *world, ecs_id_t id) { const ecs_id_record_t *idr = flecs_id_record_get(world, id); if (idr) { ecs_flags32_t extra_flags = 0; if (idr->flags & EcsIdOnInstantiateInherit) { extra_flags |= EcsIdHasOnAdd|EcsIdHasOnRemove; } return idr->flags|extra_flags; } return flecs_id_record_event_flags(world, id); } ecs_flags32_t flecs_id_flags_get( ecs_world_t *world, ecs_id_t id) { ecs_flags32_t result = flecs_id_flags(world, id); if (id != EcsAny) { result |= flecs_id_flags(world, EcsAny); } if (ECS_IS_PAIR(id)) { ecs_entity_t first = ECS_PAIR_FIRST(id); ecs_entity_t second = ECS_PAIR_SECOND(id); if (id != ecs_pair(first, EcsWildcard)) { result |= flecs_id_flags(world, ecs_pair(first, EcsWildcard)); } if (id != ecs_pair(EcsWildcard, second)) { result |= flecs_id_flags(world, ecs_pair(EcsWildcard, second)); } if (id != ecs_pair(EcsWildcard, EcsWildcard)) { result |= flecs_id_flags(world, ecs_pair(EcsWildcard, EcsWildcard)); } if (first == EcsIsA) { result |= EcsIdHasOnAdd|EcsIdHasOnRemove; } } else if (id != EcsWildcard) { result |= flecs_id_flags(world, EcsWildcard); } return result; } /** * @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_world_t *world, ecs_table_t *table) { int32_t i, count = ecs_table_count(table); int32_t size = ecs_table_size(table); ecs_assert(count <= size, ECS_INTERNAL_ERROR, NULL); int32_t bs_offset = table->_ ? table->_->bs_offset : 0; int32_t bs_count = table->_ ? table->_->bs_count : 0; int32_t type_count = table->type.count; ecs_id_t *ids = table->type.array; ecs_assert((bs_count + bs_offset) <= type_count, ECS_INTERNAL_ERROR, NULL); if (size) { ecs_assert(table->data.entities != NULL, ECS_INTERNAL_ERROR, NULL); } else { ecs_assert(table->data.entities == NULL, ECS_INTERNAL_ERROR, NULL); } if (table->column_count) { int32_t column_count = table->column_count; ecs_assert(type_count >= column_count, ECS_INTERNAL_ERROR, NULL); int16_t *column_map = table->column_map; ecs_assert(column_map != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(table->data.columns != NULL, ECS_INTERNAL_ERROR, NULL); for (i = 0; i < column_count; i ++) { int32_t column_map_id = column_map[i + type_count]; ecs_assert(column_map_id >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(table->data.columns[i].ti != NULL, ECS_INTERNAL_ERROR, NULL); if (size) { ecs_assert(table->data.columns[i].data != NULL, ECS_INTERNAL_ERROR, NULL); } else { ecs_assert(table->data.columns[i].data == NULL, ECS_INTERNAL_ERROR, NULL); } } } else { ecs_assert(table->column_map == NULL, ECS_INTERNAL_ERROR, NULL); } if (bs_count) { ecs_assert(table->_->bs_columns != NULL, ECS_INTERNAL_ERROR, NULL); 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(world, table) #endif /* Set flags for type hooks so table operations can quickly check whether a * fast or complex operation that invokes hooks is required. */ static ecs_flags32_t flecs_type_info_flags( const ecs_type_info_t *ti) { ecs_flags32_t flags = 0; if (ti->hooks.ctor) { flags |= EcsTableHasCtors; } if (ti->hooks.on_add) { flags |= EcsTableHasCtors; } if (ti->hooks.dtor) { flags |= EcsTableHasDtors; } if (ti->hooks.on_remove) { flags |= EcsTableHasDtors; } if (ti->hooks.copy) { flags |= EcsTableHasCopy; } if (ti->hooks.move) { flags |= EcsTableHasMove; } return flags; } static void flecs_table_init_columns( ecs_world_t *world, ecs_table_t *table, int32_t column_count) { int16_t i, cur = 0, ids_count = flecs_ito(int16_t, table->type.count); for (i = 0; i < ids_count; i ++) { ecs_id_t id = table->type.array[i]; if (id < FLECS_HI_COMPONENT_ID) { table->component_map[id] = flecs_ito(int16_t, -(i + 1)); } } if (!column_count) { return; } ecs_column_t *columns = flecs_wcalloc_n(world, ecs_column_t, column_count); table->data.columns = columns; ecs_id_t *ids = table->type.array; ecs_table_record_t *records = table->_->records; int16_t *t2s = table->column_map; int16_t *s2t = &table->column_map[ids_count]; for (i = 0; i < ids_count; i ++) { ecs_id_t id = ids[i]; ecs_table_record_t *tr = &records[i]; ecs_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); if (id < FLECS_HI_COMPONENT_ID) { table->component_map[id] = flecs_ito(int16_t, cur + 1); } table->flags |= flecs_type_info_flags(ti); cur ++; } int32_t record_count = table->_->record_count; for (; i < record_count; i ++) { ecs_table_record_t *tr = &records[i]; ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; ecs_id_t id = idr->id; if (ecs_id_is_wildcard(id)) { ecs_table_record_t *first_tr = &records[tr->index]; tr->column = first_tr->column; } } } /* Initialize table storage */ void flecs_table_init_data( ecs_world_t *world, ecs_table_t *table) { flecs_table_init_columns(world, table, table->column_count); ecs_table__t *meta = table->_; int32_t i, bs_count = meta->bs_count; if (bs_count) { meta->bs_columns = flecs_wcalloc_n(world, ecs_bitset_t, bs_count); for (i = 0; i < bs_count; i ++) { flecs_bitset_init(&meta->bs_columns[i]); } } } /* Initialize table flags. Table flags are used in lots of scenarios to quickly * check the features of a table without having to inspect the table type. Table * flags are typically used to early-out of potentially expensive operations. */ static void flecs_table_init_flags( ecs_world_t *world, ecs_table_t *table) { ecs_id_t *ids = table->type.array; int32_t count = table->type.count; 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) { ecs_defer_begin(world); flecs_emit(world, world, 0, &(ecs_event_desc_t) { .ids = &table->type, .event = event, .table = table, .flags = EcsEventTableOnly, .observable = world }); ecs_defer_end(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 ++; } } } table->component_map = flecs_wcalloc_n( world, int16_t, FLECS_HI_COMPONENT_ID); if (column_count) { table->column_map = flecs_walloc_n(world, int16_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_id_t id, ecs_entity_t event) { (void)world; ecs_flags32_t flags = 0; if (event == EcsOnAdd) { flags = EcsTableHasOnAdd; } else if (event == EcsOnRemove) { flags = EcsTableHasOnRemove; } else if (event == EcsOnSet) { flags = EcsTableHasOnSet; } else if (event == EcsOnTableCreate) { flags = EcsTableHasOnTableCreate; } else if (event == EcsOnTableDelete) { flags = EcsTableHasOnTableDelete; } else if (event == EcsWildcard) { flags = EcsTableHasOnAdd|EcsTableHasOnRemove|EcsTableHasOnSet| EcsTableHasOnTableCreate|EcsTableHasOnTableDelete; } table->flags |= flags; /* Add observer flags to incoming edges for id */ if (id && ((flags == EcsTableHasOnAdd) || (flags == EcsTableHasOnRemove))) { flecs_table_edges_add_flags(world, table, id, flags); } } /* Invoke OnRemove observers for all entities in table. Useful during table * deletion or when clearing entities from a table. */ static void flecs_table_notify_on_remove( ecs_world_t *world, ecs_table_t *table) { int32_t count = ecs_table_count(table); if (count) { ecs_table_diff_t diff = ECS_TABLE_DIFF_INIT; diff.removed = table->type; diff.removed_flags = table->flags & EcsTableRemoveEdgeFlags; flecs_notify_on_remove(world, table, NULL, 0, count, &diff); } } /* Invoke type hook for entities in table */ static void flecs_table_invoke_hook( ecs_world_t *world, ecs_table_t *table, ecs_iter_action_t callback, ecs_entity_t event, ecs_column_t *column, const ecs_entity_t *entities, int32_t row, int32_t count) { int32_t column_index = flecs_ito(int32_t, column - table->data.columns); ecs_assert(column_index >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(column_index < table->column_count, ECS_INTERNAL_ERROR, NULL); int32_t type_index = table->column_map[table->type.count + column_index]; ecs_assert(type_index >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(type_index < table->type.count, ECS_INTERNAL_ERROR, NULL); const ecs_table_record_t *tr = &table->_->records[type_index]; flecs_invoke_hook(world, table, tr, count, row, entities, table->type.array[type_index], column->ti, event, callback); } /* Construct components */ static void flecs_table_invoke_ctor( ecs_column_t *column, int32_t row, int32_t count) { ecs_assert(column != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(column->data != NULL, ECS_INTERNAL_ERROR, NULL); ecs_type_info_t *ti = column->ti; ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); ecs_xtor_t ctor = ti->hooks.ctor; if (ctor) { void *ptr = ECS_ELEM(column->data, ti->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_assert(column != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(column->data != NULL, ECS_INTERNAL_ERROR, NULL); ecs_type_info_t *ti = column->ti; ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); ecs_xtor_t dtor = ti->hooks.dtor; if (dtor) { void *ptr = ECS_ELEM(column->data, ti->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_assert(column != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(column->data != NULL, ECS_INTERNAL_ERROR, NULL); ecs_type_info_t *ti = column->ti; ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); if (construct) { flecs_table_invoke_ctor(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_assert(column != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(column->data != NULL, ECS_INTERNAL_ERROR, NULL); ecs_type_info_t *ti = column->ti; ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); ecs_iter_action_t on_remove = ti->hooks.on_remove; if (on_remove) { flecs_table_invoke_hook(world, table, on_remove, EcsOnRemove, column, entities, row, count); } if (dtor) { flecs_table_invoke_dtor(column, row, count); } } /* 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, int32_t row, int32_t count, bool is_delete) { const ecs_entity_t *entities = ecs_table_entities(table); int32_t column_count = table->column_count; int32_t i, c, end = row + count; ecs_assert(!column_count || table->data.columns != NULL, ECS_INTERNAL_ERROR, NULL); 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 < column_count; c++) { ecs_column_t *column = &table->data.columns[c]; ecs_iter_action_t on_remove = column->ti->hooks.on_remove; if (on_remove) { flecs_table_invoke_hook(world, table, on_remove, EcsOnRemove, column, &entities[row], row, count); } } /* Destruct components */ for (c = 0; c < column_count; c++) { flecs_table_invoke_dtor(&table->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, bool do_on_remove, bool is_delete, bool deallocate) { ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG); if (do_on_remove) { flecs_table_notify_on_remove(world, table); } int32_t count = ecs_table_count(table); if (count) { flecs_table_dtor_all(world, table, 0, count, is_delete); } if (deallocate) { ecs_column_t *columns = table->data.columns; if (columns) { int32_t c, column_count = table->column_count; for (c = 0; c < column_count; c ++) { ecs_column_t *column = &columns[c]; ecs_vec_t v = ecs_vec_from_column(column, table, column->ti->size); ecs_vec_fini(&world->allocator, &v, column->ti->size); column->data = NULL; } flecs_wfree_n(world, ecs_column_t, table->column_count, columns); table->data.columns = NULL; } } ecs_table__t *meta = table->_; ecs_bitset_t *bs_columns = meta->bs_columns; if (bs_columns) { int32_t c, column_count = meta->bs_count; if (deallocate) { for (c = 0; c < column_count; c ++) { flecs_bitset_fini(&bs_columns[c]); } } else { for (c = 0; c < column_count; c++) { bs_columns[c].count = 0; } } if (deallocate) { flecs_wfree_n(world, ecs_bitset_t, column_count, bs_columns); meta->bs_columns = NULL; } } if (deallocate) { ecs_vec_t v = ecs_vec_from_entities(table); ecs_vec_fini_t(&world->allocator, &v, ecs_entity_t); table->data.entities = NULL; table->data.size = 0; } table->data.count = 0; table->_->traversable_count = 0; table->flags &= ~EcsTableHasTraversable; } const ecs_entity_t* ecs_table_entities( const ecs_table_t *table) { return table->data.entities; } /* Cleanup, no OnRemove, delete from entity index, deactivate table, retain allocations */ void ecs_table_clear_entities( ecs_world_t* world, ecs_table_t* table) { flecs_table_fini_data(world, table, true, true, false); } /* Cleanup, no OnRemove, clear entity index, deactivate table, free allocations */ void flecs_table_clear_entities_silent( ecs_world_t *world, ecs_table_t *table) { flecs_table_fini_data(world, table, false, false, true); } /* Cleanup, run OnRemove, clear entity index, deactivate table, free allocations */ void flecs_table_clear_entities( ecs_world_t *world, ecs_table_t *table) { flecs_table_fini_data(world, table, true, false, true); } /* Cleanup, run OnRemove, delete from entity index, deactivate table, free allocations */ void flecs_table_delete_entities( ecs_world_t *world, ecs_table_t *table) { flecs_table_fini_data(world, table, 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); } /* Free table resources. */ void flecs_table_fini( ecs_world_t *world, ecs_table_t *table) { flecs_poly_assert(world, ecs_world_t); 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; ecs_os_perf_trace_push("flecs.table.free"); if (!is_root && !(world->flags & EcsWorldQuit)) { if (table->flags & EcsTableHasOnTableDelete) { flecs_table_emit(world, table, EcsOnTableDelete); } } if (ecs_should_log_2()) { char *expr = ecs_type_str(world, &table->type); ecs_dbg_2( "#[green]table#[normal] [%s] #[red]deleted#[reset] with id %d", expr, table->id); ecs_os_free(expr); ecs_log_push_2(); } /* Cleanup data, no OnRemove, delete from entity index, don't deactivate */ flecs_table_fini_data(world, table, false, true, true); flecs_table_clear_edges(world, table); if (!is_root) { ecs_type_t ids = { .array = table->type.array, .count = table->type.count }; flecs_hashmap_remove_w_hash( &world->store.table_map, &ids, ecs_table_t*, table->_->hash); } flecs_wfree_n(world, int32_t, table->column_count + 1, table->dirty_state); flecs_wfree_n(world, int16_t, table->column_count + table->type.count, table->column_map); flecs_wfree_n(world, int16_t, FLECS_HI_COMPONENT_ID, table->component_map); flecs_table_records_unregister(world, table); /* Update counters */ world->info.table_count --; world->info.table_delete_total ++; flecs_free_t(&world->allocator, ecs_table__t, table->_); if (!(world->flags & EcsWorldFini)) { ecs_assert(!is_root, ECS_INTERNAL_ERROR, NULL); flecs_table_free_type(world, table); flecs_sparse_remove_t(&world->store.tables, ecs_table_t, table->id); } ecs_log_pop_2(); ecs_os_perf_trace_pop("flecs.table.free"); } /* Free table type. Do this separately from freeing the table as types can be * in use by application destructors. */ void flecs_table_free_type( ecs_world_t *world, ecs_table_t *table) { flecs_wfree_n(world, ecs_id_t, table->type.count, table->type.array); } /* Reset a table to its initial state. */ void flecs_table_reset( ecs_world_t *world, ecs_table_t *table) { ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG); 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) { int32_t column; if (component < FLECS_HI_COMPONENT_ID) { column = table->component_map[component]; if (column <= 0) { return; } } else { 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; } column = tr->column + 1; } /* Column is offset by 1, 0 is reserved for entity column. */ table->dirty_state[column] ++; } } /* 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_vec_t *column, const ecs_type_info_t *ti, int32_t to_add, int32_t dst_size, bool construct) { ecs_assert(column != NULL, ECS_INTERNAL_ERROR, NULL); int32_t count = ecs_vec_count(column); int32_t size = ecs_vec_size(column); int32_t elem_size = ti->size; int32_t dst_count = count + to_add; bool can_realloc = dst_size != 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, elem_size, dst_size); dst.count = dst_count; void *src_buffer = column->array; void *dst_buffer = dst.array; /* Move (and construct) existing elements to new vector */ move_ctor(dst_buffer, src_buffer, count, ti); if (construct) { /* Construct new element(s) */ result = ECS_ELEM(dst_buffer, elem_size, count); ctor(result, to_add, ti); } /* Free old vector */ ecs_vec_fini(&world->allocator, column, elem_size); *column = dst; } else { /* If array won't realloc or has no move, simply add new elements */ if (can_realloc) { ecs_vec_set_size(&world->allocator, column, elem_size, dst_size); } result = ecs_vec_grow(&world->allocator, column, elem_size, to_add); ecs_xtor_t ctor; if (construct && (ctor = ti->hooks.ctor)) { /* If new elements need to be constructed and component has a * constructor, construct */ ctor(result, to_add, ti); } } ecs_assert(column->size == dst_size, ECS_INTERNAL_ERROR, NULL); } /* Grow all data structures in a table */ static int32_t flecs_table_grow_data( ecs_world_t *world, ecs_table_t *table, int32_t to_add, int32_t size, const ecs_entity_t *ids) { flecs_poly_assert(world, ecs_world_t); ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); int32_t count = ecs_table_count(table); int32_t column_count = table->column_count; /* Add entity to column with entity ids */ ecs_vec_t v_entities = ecs_vec_from_entities(table); ecs_vec_set_size_t(&world->allocator, &v_entities, ecs_entity_t, size); ecs_entity_t *e = ecs_vec_last_t(&v_entities, ecs_entity_t) + 1; v_entities.count += to_add; if (v_entities.size > size) { size = v_entities.size; } /* Update table entities/count/size */ int32_t prev_count = table->data.count, prev_size = table->data.size; table->data.entities = v_entities.array; table->data.count = v_entities.count; table->data.size = v_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 = table->data.columns; for (i = 0; i < column_count; i ++) { ecs_column_t *column = &columns[i]; const ecs_type_info_t *ti = column->ti; ecs_vec_t v_column = ecs_vec_from_column_ext(column, prev_count, prev_size, ti->size); flecs_table_grow_column(world, &v_column, ti, to_add, size, true); ecs_assert(v_column.size == size, ECS_INTERNAL_ERROR, NULL); ecs_assert(v_column.size == v_entities.size, ECS_INTERNAL_ERROR, NULL); ecs_assert(v_column.count == v_entities.count, ECS_INTERNAL_ERROR, NULL); column->data = v_column.array; flecs_table_invoke_add_hooks(world, table, column, e, 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); /* Return index of first added entity */ return count; } /* Append operation for tables that don't have any complex logic */ static void flecs_table_fast_append( ecs_world_t *world, ecs_table_t *table) { /* Add elements to each column array */ ecs_column_t *columns = table->data.columns; int32_t i, count = table->column_count; for (i = 0; i < count; i ++) { ecs_column_t *column = &columns[i]; const ecs_type_info_t *ti = column->ti; ecs_vec_t v = ecs_vec_from_column(column, table, ti->size); ecs_vec_append(&world->allocator, &v, ti->size); column->data = v.array; } } /* 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(world, table); /* Get count & size before growing entities array. This tells us whether the * arrays will realloc */ int32_t count = ecs_table_count(table); 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_vec_t v_entities = ecs_vec_from_entities(table); ecs_entity_t *e = ecs_vec_append_t(&world->allocator, &v_entities, ecs_entity_t); ecs_assert(e != NULL, ECS_INTERNAL_ERROR, NULL); ecs_entity_t *entities = table->data.entities = v_entities.array; *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, table); table->data.count = v_entities.count; table->data.size = v_entities.size; return count; } int32_t prev_count = table->data.count; int32_t prev_size = table->data.size; ecs_assert(table->data.count == v_entities.count - 1, ECS_INTERNAL_ERROR, NULL); table->data.count = v_entities.count; table->data.size = v_entities.size; /* 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 = v_entities.size; /* Grow component arrays with 1 element */ int32_t i; for (i = 0; i < column_count; i ++) { ecs_column_t *column = &columns[i]; const ecs_type_info_t *ti = column->ti; ecs_vec_t v_column = ecs_vec_from_column_ext(column, prev_count, prev_size, ti->size); flecs_table_grow_column(world, &v_column, ti, 1, size, construct); column->data = v_column.array; 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(v_column.size == v_entities.size, ECS_INTERNAL_ERROR, NULL); ecs_assert(v_column.count == v_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); } flecs_table_check_sanity(world, table); return count; } /* Delete operation for tables that don't have any complex logic */ static void flecs_table_fast_delete( ecs_table_t *table, int32_t row) { ecs_column_t *columns = table->data.columns; int32_t i, count = table->column_count; for (i = 0; i < count; i ++) { ecs_column_t *column = &columns[i]; const ecs_type_info_t *ti = column->ti; ecs_vec_t v = ecs_vec_from_column(column, table, ti->size); ecs_vec_remove(&v, ti->size, row); column->data = v.array; } } /* Delete entity from table */ void flecs_table_delete( ecs_world_t *world, ecs_table_t *table, int32_t row, bool destruct) { ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG); flecs_table_check_sanity(world, table); int32_t count = ecs_table_count(table); ecs_assert(count > 0, ECS_INTERNAL_ERROR, NULL); count --; ecs_assert(row <= count, ECS_INTERNAL_ERROR, NULL); /* Move last entity id to row */ ecs_entity_t *entities = table->data.entities; ecs_entity_t entity_to_move = entities[count]; ecs_entity_t entity_to_delete = entities[row]; entities[row] = entity_to_move; /* Update record of moved entity in entity index */ if (row != count) { ecs_record_t *record_to_move = flecs_entities_get(world, entity_to_move); if (record_to_move) { uint32_t row_flags = record_to_move->row & ECS_ROW_FLAGS_MASK; record_to_move->row = ECS_ROW_TO_RECORD(row, row_flags); ecs_assert(record_to_move->table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(record_to_move->table == table, ECS_INTERNAL_ERROR, NULL); } } /* If the table is monitored indicate that there has been a change */ flecs_table_mark_table_dirty(world, table, 0); /* Destruct component data */ ecs_column_t *columns = table->data.columns; int32_t column_count = table->column_count; int32_t i; /* If this is a table without lifecycle callbacks or special columns, take * fast path that just remove an element from the array(s) */ if (!(table->flags & EcsTableIsComplex)) { if (row != count) { flecs_table_fast_delete(table, row); } table->data.count --; flecs_table_check_sanity(world, table); return; } /* Last element, destruct & remove */ if (row == count) { /* If table has component destructors, invoke */ if (destruct && (table->flags & EcsTableHasDtors)) { for (i = 0; i < column_count; i ++) { flecs_table_invoke_remove_hooks(world, table, &columns[i], &entity_to_delete, row, 1, true); } } /* Not last element, move last element to deleted element & destruct */ } else { /* If table has component destructors, invoke */ if ((table->flags & (EcsTableHasDtors | EcsTableHasMove))) { for (i = 0; i < column_count; i ++) { ecs_column_t *column = &columns[i]; ecs_type_info_t *ti = column->ti; ecs_size_t size = ti->size; void *dst = ECS_ELEM(column->data, size, row); void *src = ECS_ELEM(column->data, size, count); 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, row, 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); } } } else { flecs_table_fast_delete(table, row); } } /* Remove elements from bitset columns */ ecs_table__t *meta = table->_; ecs_bitset_t *bs_columns = meta->bs_columns; int32_t bs_count = meta->bs_count; for (i = 0; i < bs_count; i ++) { flecs_bitset_remove(&bs_columns[i], row); } table->data.count --; flecs_table_check_sanity(world, table); } /* Move operation for tables that don't have any complex logic */ static void flecs_table_fast_move( ecs_table_t *dst_table, int32_t dst_index, ecs_table_t *src_table, int32_t src_index) { int32_t i_new = 0, dst_column_count = dst_table->column_count; int32_t i_old = 0, src_column_count = src_table->column_count; ecs_column_t *src_columns = src_table->data.columns; ecs_column_t *dst_columns = dst_table->data.columns; for (; (i_new < dst_column_count) && (i_old < src_column_count);) { ecs_column_t *dst_column = &dst_columns[i_new]; ecs_column_t *src_column = &src_columns[i_old]; ecs_id_t dst_id = flecs_column_id(dst_table, i_new); ecs_id_t src_id = flecs_column_id(src_table, i_old); if (dst_id == src_id) { int32_t size = dst_column->ti->size; void *dst = ECS_ELEM(dst_column->data, size, dst_index); void *src = ECS_ELEM(src_column->data, size, src_index); 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(world, dst_table); flecs_table_check_sanity(world, 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(world, dst_table); flecs_table_check_sanity(world, 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 = flecs_column_id(dst_table, i_new); ecs_id_t src_id = flecs_column_id(src_table, i_old); if (dst_id == src_id) { ecs_type_info_t *ti = dst_column->ti; int32_t size = ti->size; ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); void *dst = ECS_ELEM(dst_column->data, size, dst_index); void *src = ECS_ELEM(src_column->data, size, src_index); 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(world, dst_table); flecs_table_check_sanity(world, src_table); } /* Append n entities to table */ int32_t flecs_table_appendn( ecs_world_t *world, ecs_table_t *table, int32_t to_add, const ecs_entity_t *ids) { ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG); flecs_table_check_sanity(world, table); int32_t cur_count = ecs_table_count(table); int32_t result = flecs_table_grow_data( world, table, to_add, cur_count + to_add, ids); flecs_table_check_sanity(world, table); return result; } /* Shrink table storage to fit number of entities */ bool flecs_table_shrink( ecs_world_t *world, ecs_table_t *table) { ecs_assert(table != NULL, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG); ecs_assert(!table->_->lock, ECS_LOCKED_STORAGE, FLECS_LOCKED_STORAGE_MSG); (void)world; flecs_table_check_sanity(world, table); bool has_payload = table->data.entities != NULL; ecs_vec_t v_entities = ecs_vec_from_entities(table); ecs_vec_reclaim_t(&world->allocator, &v_entities, ecs_entity_t); int32_t i, count = table->column_count; for (i = 0; i < count; i ++) { ecs_column_t *column = &table->data.columns[i]; const ecs_type_info_t *ti = column->ti; ecs_vec_t v_column = ecs_vec_from_column(column, table, ti->size); ecs_vec_reclaim(&world->allocator, &v_column, ti->size); column->data = v_column.array; } table->data.count = v_entities.count; table->data.size = v_entities.size; table->data.entities = v_entities.array; flecs_table_check_sanity(world, table); return has_payload; } /* Swap operation for bitset (toggle component) columns */ static void flecs_table_swap_bitset_columns( ecs_table_t *table, int32_t row_1, int32_t row_2) { int32_t i = 0, column_count = table->_->bs_count; if (!column_count) { return; } ecs_bitset_t *columns = table->_->bs_columns; for (i = 0; i < column_count; i ++) { ecs_bitset_t *bs = &columns[i]; flecs_bitset_swap(bs, row_1, row_2); } } /* Swap two rows in a table. Used for table sorting. */ 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(world, table); if (row_1 == row_2) { return; } /* If the table is monitored indicate that there has been a change */ flecs_table_mark_table_dirty(world, table, 0); ecs_entity_t *entities = table->data.entities; ecs_entity_t e1 = entities[row_1]; ecs_entity_t e2 = entities[row_2]; ecs_record_t *record_ptr_1 = flecs_entities_get(world, e1); ecs_record_t *record_ptr_2 = flecs_entities_get(world, e2); ecs_assert(record_ptr_1 != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(record_ptr_2 != NULL, ECS_INTERNAL_ERROR, NULL); /* Keep track of 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(world, table); return; } /* Find the maximum size of column elements * and allocate a temporary buffer for swapping */ int32_t i, temp_buffer_size = ECS_SIZEOF(uint64_t), column_count = table->column_count; for (i = 0; i < column_count; i++) { temp_buffer_size = ECS_MAX(temp_buffer_size, columns[i].ti->size); } void* tmp = ecs_os_alloca(temp_buffer_size); /* Swap columns */ for (i = 0; i < column_count; i ++) { int32_t size = columns[i].ti->size; ecs_column_t *column = &columns[i]; void *ptr = column->data; const ecs_type_info_t *ti = column->ti; ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); void *el_1 = ECS_ELEM(ptr, size, row_1); void *el_2 = ECS_ELEM(ptr, size, row_2); 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(world, 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_vec_t *dst_vec, ecs_vec_t *src_vec, ecs_column_t *dst, ecs_column_t *src, int32_t column_size) { const ecs_type_info_t *ti = dst->ti; ecs_assert(ti == src->ti, ECS_INTERNAL_ERROR, NULL); ecs_size_t elem_size = ti->size; int32_t dst_count = ecs_vec_count(dst_vec); if (!dst_count) { ecs_vec_fini(&world->allocator, dst_vec, elem_size); *dst_vec = *src_vec; /* If the new table is not empty, copy the contents from the * src into the dst. */ } else { int32_t src_count = src_vec->count; flecs_table_grow_column(world, dst_vec, ti, src_count, column_size, false); void *dst_ptr = ECS_ELEM(dst_vec->array, elem_size, dst_count); void *src_ptr = src_vec->array; /* Move values into column */ 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, elem_size * src_count); } ecs_vec_fini(&world->allocator, src_vec, elem_size); } dst->data = dst_vec->array; src->data = NULL; } /* 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 dst_count, int32_t src_count) { 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; ecs_assert(!dst_column_count || dst_columns, ECS_INTERNAL_ERROR, NULL); if (!src_count) { return; } /* Merge entities */ ecs_vec_t dst_entities = ecs_vec_from_entities(dst_table); ecs_vec_t src_entities = ecs_vec_from_entities(src_table); flecs_table_merge_vec(world, &dst_entities, &src_entities, ECS_SIZEOF(ecs_entity_t), 0); ecs_assert(dst_entities.count == src_count + dst_count, ECS_INTERNAL_ERROR, NULL); int32_t column_size = dst_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 = flecs_column_id(dst_table, i_new); ecs_id_t src_id = flecs_column_id(src_table, i_old); ecs_size_t dst_elem_size = dst_column->ti->size; ecs_size_t src_elem_size = src_column->ti->size; ecs_vec_t dst_vec = ecs_vec_from_column( dst_column, dst_table, dst_elem_size); ecs_vec_t src_vec = ecs_vec_from_column( src_column, src_table, src_elem_size); if (dst_id == src_id) { flecs_table_merge_column(world, &dst_vec, &src_vec, 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_vec_set_size(a, &dst_vec, dst_elem_size, column_size); dst_column->data = dst_vec.array; 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_vec, src_elem_size); src_column->data = NULL; 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 elem_size = column->ti->size; ecs_assert(elem_size != 0, ECS_INTERNAL_ERROR, NULL); ecs_vec_t vec = ecs_vec_from_column(column, dst_table, elem_size); ecs_vec_set_size(a, &vec, elem_size, column_size); column->data = vec.array; 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]; int32_t elem_size = column->ti->size; ecs_assert(elem_size != 0, ECS_INTERNAL_ERROR, NULL); flecs_table_invoke_dtor(column, 0, src_count); ecs_vec_t vec = ecs_vec_from_column(column, src_table, elem_size); ecs_vec_fini(a, &vec, elem_size); column->data = vec.array; } /* Mark entity column as dirty */ flecs_table_mark_table_dirty(world, dst_table, 0); dst_table->data.entities = dst_entities.array; dst_table->data.count = dst_entities.count; dst_table->data.size = dst_entities.size; src_table->data.entities = src_entities.array; src_table->data.count = src_entities.count; src_table->data.size = src_entities.size; } /* 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(world, src_table); flecs_table_check_sanity(world, dst_table); const ecs_entity_t *src_entities = ecs_table_entities(src_table); int32_t src_count = ecs_table_count(src_table); int32_t dst_count = ecs_table_count(dst_table); /* 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, dst_count, src_count); if (src_count) { 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(world, src_table); flecs_table_check_sanity(world, dst_table); } /* Internal mechanism for propagating information to tables */ void flecs_table_notify( ecs_world_t *world, ecs_table_t *table, ecs_id_t id, ecs_table_event_t *event) { flecs_poly_assert(world, ecs_world_t); if (world->flags & EcsWorldFini) { return; } switch(event->kind) { case EcsTableTriggersForId: flecs_table_add_trigger_flags(world, table, id, event->event); break; case EcsTableNoTriggersForId: break; /* TODO */ } } 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]; } ecs_id_t flecs_column_id( ecs_table_t *table, int32_t column_index) { int32_t type_index = table->column_map[table->type.count + column_index]; return table->type.array[type_index]; } /* -- Public API -- */ void ecs_table_lock( ecs_world_t *world, ecs_table_t *table) { if (table) { if (flecs_poly_is(world, ecs_world_t) && !(world->flags & EcsWorldReadonly)) { table->_->lock ++; } } } void ecs_table_unlock( ecs_world_t *world, ecs_table_t *table) { if (table) { if (flecs_poly_is(world, ecs_world_t) && !(world->flags & EcsWorldReadonly)) { table->_->lock --; ecs_assert(table->_->lock >= 0, ECS_INVALID_OPERATION, "table_unlock called more often than table_lock"); } } } const ecs_type_t* ecs_table_get_type( const ecs_table_t *table) { if (table) { return &table->type; } else { return NULL; } } int32_t ecs_table_get_type_index( const ecs_world_t *world, const ecs_table_t *table, ecs_id_t id) { flecs_poly_assert(world, ecs_world_t); ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); if (id < FLECS_HI_COMPONENT_ID) { int16_t res = table->component_map[id]; if (res > 0) { return table->column_map[table->type.count + (res - 1)]; } return -res - 1; } ecs_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); if (id < FLECS_HI_COMPONENT_ID) { int16_t res = table->component_map[id]; if (res > 0) { return res - 1; } return -1; } 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); int16_t *column_map = table->column_map; if (column_map) { return column_map[index]; } error: return -1; } int32_t ecs_table_column_to_type_index( const ecs_table_t *table, int32_t index) { ecs_check(index < table->column_count, ECS_INVALID_PARAMETER, NULL); ecs_check(table->column_map != NULL, ECS_INVALID_PARAMETER, NULL); int32_t offset = table->type.count; return table->column_map[offset + index]; error: return -1; } void* ecs_table_get_column( const ecs_table_t *table, int32_t index, int32_t offset) { ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(index < table->column_count, ECS_INVALID_PARAMETER, NULL); ecs_column_t *column = &table->data.columns[index]; void *result = column->data; if (offset) { result = ECS_ELEM(result, column->ti->size, offset); } return result; error: return NULL; } void* ecs_table_get_id( const ecs_world_t *world, const ecs_table_t *table, ecs_id_t id, int32_t offset) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); int32_t index = ecs_table_get_column_index(world, table, id); if (index == -1) { return NULL; } return ecs_table_get_column(table, index, offset); error: return NULL; } size_t ecs_table_get_column_size( const ecs_table_t *table, int32_t column) { ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(column < table->column_count, ECS_INVALID_PARAMETER, NULL); ecs_check(table->column_map != NULL, ECS_INVALID_PARAMETER, NULL); return flecs_ito(size_t, table->data.columns[column].ti->size); error: return 0; } int32_t ecs_table_count( const ecs_table_t *table) { ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); return table->data.count; } int32_t ecs_table_size( const ecs_table_t *table) { ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); return table->data.size; } bool ecs_table_has_id( const ecs_world_t *world, const ecs_table_t *table, ecs_id_t id) { return ecs_table_get_type_index(world, table, id) != -1; } 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->ti->size; ecs_check(!flecs_utosize(c_size) || flecs_utosize(c_size) == size, ECS_INVALID_PARAMETER, NULL); return ECS_ELEM(column->data, size, ECS_RECORD_TO_ROW(r->row)); error: return NULL; } ecs_record_t* ecs_record_find( const ecs_world_t *world, ecs_entity_t entity) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); ecs_record_t *r = flecs_entities_get(world, entity); if (r) { return r; } error: return NULL; } /** * @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->tables.count --; if (cache->tables.first == elem) { cache->tables.first = next; } if (cache->tables.last == elem) { cache->tables.last = prev; } ecs_assert(cache->tables.first == NULL || cache->tables.count, ECS_INTERNAL_ERROR, NULL); ecs_assert(cache->tables.first == NULL || cache->tables.last != NULL, ECS_INTERNAL_ERROR, NULL); } static void flecs_table_cache_list_insert( ecs_table_cache_t *cache, ecs_table_cache_hdr_t *elem) { ecs_table_cache_hdr_t *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; } ecs_assert( cache->tables.count != 1 || cache->tables.first == cache->tables.last, ECS_INTERNAL_ERROR, NULL); } 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); } void ecs_table_cache_insert( ecs_table_cache_t *cache, const ecs_table_t *table, ecs_table_cache_hdr_t *result) { ecs_assert(cache != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(ecs_table_cache_get(cache, table) == NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(result != NULL, ECS_INTERNAL_ERROR, NULL); result->cache = cache; result->table = ECS_CONST_CAST(ecs_table_t*, table); flecs_table_cache_list_insert(cache, result); if (table) { ecs_map_insert_ptr(&cache->index, table->id, result); } ecs_assert(cache->tables.first != NULL, ECS_INTERNAL_ERROR, NULL); } void ecs_table_cache_replace( ecs_table_cache_t *cache, const ecs_table_t *table, ecs_table_cache_hdr_t *elem) { ecs_table_cache_hdr_t **r = ecs_map_get_ref( &cache->index, ecs_table_cache_hdr_t, table->id); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_cache_hdr_t *old = *r; ecs_assert(old != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_cache_hdr_t *prev = old->prev, *next = old->next; if (prev) { ecs_assert(prev->next == old, ECS_INTERNAL_ERROR, NULL); prev->next = elem; } if (next) { ecs_assert(next->prev == old, ECS_INTERNAL_ERROR, NULL); next->prev = elem; } if (cache->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 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->cur = NULL; out->iter_fill = true; out->iter_empty = false; 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->tables.first; out->cur = NULL; out->iter_fill = false; out->iter_empty = true; 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->tables.first; out->cur = NULL; out->iter_fill = true; out->iter_empty = true; return out->next != NULL; } ecs_table_cache_hdr_t* flecs_table_cache_next_( ecs_table_cache_iter_t *it) { ecs_table_cache_hdr_t *next; repeat: next = it->next; it->cur = next; if (next) { it->next = next->next; if (ecs_table_count(next->table)) { if (!it->iter_fill) { goto repeat; } } else { if (!it->iter_empty) { goto repeat; } } } 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); builder->added_flags = 0; builder->removed_flags = 0; } void flecs_table_diff_builder_fini( ecs_world_t *world, ecs_table_diff_builder_t *builder) { ecs_allocator_t *a = &world->allocator; ecs_vec_fini_t(a, &builder->added, ecs_id_t); ecs_vec_fini_t(a, &builder->removed, ecs_id_t); } void flecs_table_diff_builder_clear( ecs_table_diff_builder_t *builder) { ecs_vec_clear(&builder->added); ecs_vec_clear(&builder->removed); } 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); diff->added_flags = builder->added_flags; diff->removed_flags = builder->removed_flags; } void flecs_table_diff_build_noalloc( ecs_table_diff_builder_t *builder, ecs_table_diff_t *diff) { diff->added = (ecs_type_t){ .array = builder->added.array, .count = builder->added.count }; diff->removed = (ecs_type_t){ .array = builder->removed.array, .count = builder->removed.count }; diff->added_flags = builder->added_flags; diff->removed_flags = builder->removed_flags; } static void flecs_table_diff_build_add_type_to_vec( ecs_world_t *world, ecs_vec_t *vec, ecs_type_t *add) { if (!add || !add->count) { return; } int32_t offset = vec->count; ecs_vec_grow_t(&world->allocator, vec, ecs_id_t, add->count); ecs_os_memcpy_n(ecs_vec_get_t(vec, ecs_id_t, offset), add->array, ecs_id_t, add->count); } void flecs_table_diff_build_append_table( ecs_world_t *world, ecs_table_diff_builder_t *dst, ecs_table_diff_t *src) { flecs_table_diff_build_add_type_to_vec(world, &dst->added, &src->added); flecs_table_diff_build_add_type_to_vec(world, &dst->removed, &src->removed); dst->added_flags |= src->added_flags; dst->removed_flags |= src->removed_flags; } static 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); } 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_os_perf_trace_push("flecs.table.create"); ecs_table_t *result = flecs_sparse_add_t(&world->store.tables, ecs_table_t); ecs_assert(result != NULL, ECS_INTERNAL_ERROR, NULL); result->_ = flecs_calloc_t(&world->allocator, ecs_table__t); ecs_assert(result->_ != NULL, ECS_INTERNAL_ERROR, NULL); #ifdef FLECS_SANITIZE int32_t i, j, count = type->count; for (i = 0; i < count - 1; i ++) { if (type->array[i] >= type->array[i + 1]) { for (j = 0; j < count; j ++) { char *str = ecs_id_str(world, type->array[j]); if (i == j) { ecs_err(" > %d: %s", j, str); } else { ecs_err(" %d: %s", j, str); } ecs_os_free(str); } ecs_abort(ECS_CONSTRAINT_VIOLATED, "table type is not ordered"); } } #endif result->id = flecs_sparse_last_id(&world->store.tables); result->type = *type; if (ecs_should_log_2()) { char *expr = ecs_type_str(world, &result->type); ecs_dbg_2( "#[green]table#[normal] [%s] #[green]created#[reset] with id %d", expr, result->id); ecs_os_free(expr); } ecs_log_push_2(); /* Store table in table hashmap */ *(ecs_table_t**)table_elem.value = result; /* Set keyvalue to one that has the same lifecycle as the table */ *(ecs_type_t*)table_elem.key = result->type; result->_->hash = table_elem.hash; flecs_init_table(world, result, prev); /* Update counters */ world->info.table_count ++; world->info.table_create_total ++; ecs_log_pop_2(); ecs_os_perf_trace_pop("flecs.table.create"); return result; } static ecs_table_t* flecs_table_ensure( ecs_world_t *world, ecs_type_t *type, bool own_type, ecs_table_t *prev) { flecs_poly_assert(world, ecs_world_t); int32_t id_count = type->count; if (!id_count) { return &world->store.root; } ecs_table_t *table; flecs_hashmap_result_t elem = flecs_hashmap_ensure( &world->store.table_map, type, ecs_table_t*); if ((table = *(ecs_table_t**)elem.value)) { if (own_type) { flecs_type_free(world, type); } return table; } /* If we get here, table needs to be created which is only allowed when the * application is not currently in progress */ ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INTERNAL_ERROR, NULL); /* If we get here, the table has not been found, so create it. */ if (own_type) { return flecs_table_new(world, type, elem, prev); } ecs_type_t copy = flecs_type_copy(world, type); return flecs_table_new(world, ©, elem, prev); } static 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); diff->added_flags = EcsTableHasUnion; 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; ecs_flags32_t added_flags = 0, removed_flags = 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; if (added) { added_flags |= flecs_id_flags_get(world, id_next) & EcsTableAddEdgeFlags; added_count ++; } if (removed) { removed_flags |= flecs_id_flags_get(world, id_node) & EcsTableRemoveEdgeFlags; removed_count ++; } i_node += id_node <= id_next; i_next += id_next <= id_node; } for (; i_next < next_count; i_next ++) { added_flags |= flecs_id_flags_get(world, ids_next[i_next]) & EcsTableAddEdgeFlags; added_count ++; } for (; i_node < node_count; i_node ++) { removed_flags |= flecs_id_flags_get(world, ids_node[i_node]) & EcsTableRemoveEdgeFlags; removed_count ++; } trivial_edge &= (added_count + removed_count) <= 1 && !ecs_id_is_wildcard(id) && !(added_flags|removed_flags); 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); diff->added_flags = added_flags; diff->removed_flags = removed_flags; 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_INVALID_PARAMETER, "target of IsA pair is not alive"); ecs_table_t *base_table = ecs_get_table(world, base); if (!base_table) { return; } ecs_id_t *ids = base_table->type.array; ecs_flags32_t flags = base_table->flags; if (flags & EcsTableHasOverrides) { int32_t i, count = base_table->type.count; for (i = 0; i < count; i ++) { ecs_id_t id = ids[i]; ecs_id_t to_add = 0; if (ECS_HAS_ID_FLAG(id, AUTO_OVERRIDE)) { to_add = id & ~ECS_AUTO_OVERRIDE; } 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; if (diff->added_flags & EcsIdIsUnion) { if (diff->added.count == 1) { diff->added.array = id_ptr; diff->added.count = 1; diff->removed.count = 0; } } } else { diff->added.array = id_ptr; diff->added.count = 1; diff->removed.count = 0; } } return to; error: return NULL; } ecs_table_t* flecs_table_find_or_create( ecs_world_t *world, ecs_type_t *type) { flecs_poly_assert(world, ecs_world_t); return flecs_table_ensure(world, type, false, NULL); } void flecs_init_root_table( ecs_world_t *world) { flecs_poly_assert(world, ecs_world_t); world->store.root.type = (ecs_type_t){0}; world->store.root._ = flecs_calloc_t(&world->allocator, ecs_table__t); flecs_init_table(world, &world->store.root, NULL); /* Ensure table indices start at 1, as 0 is reserved for the root */ uint64_t new_id = flecs_sparse_new_id(&world->store.tables); ecs_assert(new_id == 0, ECS_INTERNAL_ERROR, NULL); (void)new_id; } void flecs_table_edges_add_flags( ecs_world_t *world, ecs_table_t *table, ecs_id_t id, ecs_flags32_t flags) { ecs_graph_node_t *table_node = &table->node; ecs_graph_edge_hdr_t *node_refs = &table_node->refs; /* Add flags to incoming matching add edges */ if (flags == EcsTableHasOnAdd) { ecs_graph_edge_hdr_t *next, *cur = node_refs->next; if (cur) { do { ecs_graph_edge_t *edge = (ecs_graph_edge_t*)cur; if ((id == EcsAny) || ecs_id_match(edge->id, id)) { if (!edge->diff) { edge->diff = flecs_bcalloc(&world->allocators.table_diff); edge->diff->added.array = flecs_walloc_t(world, ecs_id_t); edge->diff->added.count = 1; edge->diff->added.array[0] = edge->id; } edge->diff->added_flags |= EcsTableHasOnAdd; } next = cur->next; } while ((cur = next)); } } /* Add flags to outgoing matching remove edges */ if (flags == EcsTableHasOnRemove) { ecs_map_iter_t it = ecs_map_iter(table->node.remove.hi); while (ecs_map_next(&it)) { ecs_id_t edge_id = ecs_map_key(&it); if ((id == EcsAny) || ecs_id_match(edge_id, id)) { ecs_graph_edge_t *edge = ecs_map_ptr(&it); if (!edge->diff) { edge->diff = flecs_bcalloc(&world->allocators.table_diff); edge->diff->removed.array = flecs_walloc_t(world, ecs_id_t); edge->diff->removed.count = 1; edge->diff->removed.array[0] = edge->id; } edge->diff->removed_flags |= EcsTableHasOnRemove; } } } } 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 { const 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, "has_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_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, true); ecs_strbuf_appendch(buf, '"'); } static const char* flecs_json_entity_label( const ecs_world_t *world, ecs_entity_t e) { const char *lbl = NULL; if (!e) { return "#0"; } #ifdef FLECS_DOC lbl = ecs_doc_get_name(world, e); #else lbl = ecs_get_name(world, e); #endif return lbl; } void flecs_json_label( ecs_strbuf_t *buf, const ecs_world_t *world, ecs_entity_t e) { const char *lbl = flecs_json_entity_label(world, e); if (lbl) { flecs_json_string_escape(buf, lbl); } else { ecs_strbuf_appendch(buf, '"'); ecs_strbuf_appendch(buf, '#'); ecs_strbuf_appendint(buf, (uint32_t)e); ecs_strbuf_appendch(buf, '"'); } } void flecs_json_path_or_label( ecs_strbuf_t *buf, const ecs_world_t *world, ecs_entity_t e, bool path) { if (!path) { flecs_json_label(buf, world, e); } else { flecs_json_path(buf, world, e); } } void flecs_json_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, true); ecs_strbuf_appendch(buf, '"'); ecs_strbuf_appendch(buf, ','); ecs_strbuf_appendch(buf, '"'); ecs_get_path_w_sep_buf(world, 0, second, ".", "", buf, true); ecs_strbuf_appendch(buf, '"'); } else { ecs_strbuf_appendch(buf, '"'); ecs_get_path_w_sep_buf(world, 0, id & ECS_COMPONENT_MASK, ".", "", buf, true); ecs_strbuf_appendch(buf, '"'); } ecs_strbuf_appendch(buf, ']'); } static void flecs_json_id_member_fullpath( ecs_strbuf_t *buf, const ecs_world_t *world, ecs_id_t id) { if (ECS_IS_PAIR(id)) { ecs_strbuf_appendch(buf, '('); ecs_entity_t first = ecs_pair_first(world, id); ecs_entity_t second = ecs_pair_second(world, id); ecs_get_path_w_sep_buf(world, 0, first, ".", "", buf, true); ecs_strbuf_appendch(buf, ','); ecs_get_path_w_sep_buf(world, 0, second, ".", "", buf, true); ecs_strbuf_appendch(buf, ')'); } else { ecs_get_path_w_sep_buf(world, 0, id & ECS_COMPONENT_MASK, ".", "", buf, true); } } void flecs_json_id_member( ecs_strbuf_t *buf, const ecs_world_t *world, ecs_id_t id, bool fullpath) { ecs_id_t flags = id & ECS_ID_FLAGS_MASK; if (flags & ECS_AUTO_OVERRIDE) { ecs_strbuf_appendlit(buf, "auto_override|"); id &= ~ECS_AUTO_OVERRIDE; } if (flags & ECS_TOGGLE) { ecs_strbuf_appendlit(buf, "toggle|"); id &= ~ECS_TOGGLE; } if (fullpath) { flecs_json_id_member_fullpath(buf, world, id); return; } if (ECS_IS_PAIR(id)) { ecs_strbuf_appendch(buf, '('); ecs_entity_t first = ecs_pair_first(world, id); ecs_entity_t second = ecs_pair_second(world, id); { const char *lbl = flecs_json_entity_label(world, first); if (lbl) { ecs_strbuf_appendstr(buf, lbl); } } ecs_strbuf_appendch(buf, ','); { const char *lbl = flecs_json_entity_label(world, second); if (lbl) { ecs_strbuf_appendstr(buf, lbl); } } ecs_strbuf_appendch(buf, ')'); } else { const char *lbl = flecs_json_entity_label(world, id & ECS_COMPONENT_MASK); if (lbl) { ecs_strbuf_appendstr(buf, lbl); } } } ecs_primitive_kind_t flecs_json_op_to_primitive_kind( ecs_meta_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); void flecs_rtt_init_default_hooks( ecs_iter_t *it); #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 = &ecs_table_entities(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, true); ecs_strbuf_appendch(buf, ','); ecs_get_path_w_sep_buf(world, 0, second, ".", "", buf, true); ecs_strbuf_appendch(buf, ')'); } else { ecs_get_path_w_sep_buf( world, 0, id & ECS_COMPONENT_MASK, ".", "", buf, true); } 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); const ecs_query_t *cq = ecs_query_get_cache_query(q); flecs_json_memberl(buf, "query_plan"); bool prev_color = ecs_log_enable_colors(true); char *plan = ecs_query_plan(q); char *cache_plan = NULL; if (cq) { flecs_poly_assert(cq, ecs_query_t); cache_plan = ecs_query_plan(cq); } ecs_strbuf_t plan_buf = ECS_STRBUF_INIT; if (plan) { ecs_strbuf_appendstr(&plan_buf, plan); } else { if (q->term_count) { ecs_strbuf_append(&plan_buf, " %sOptimized out (trivial query)\n", ECS_GREY); } } if (cq) { ecs_strbuf_appendstr(&plan_buf, "\n\n"); ecs_strbuf_appendstr(&plan_buf, " Cache plan\n"); ecs_strbuf_appendstr(&plan_buf, " ---\n"); if (cache_plan) { ecs_strbuf_appendstr(&plan_buf, cache_plan); } else { ecs_strbuf_append(&plan_buf, " %sOptimized out (trivial query)\n", ECS_GREY); } } char *plan_str = ecs_strbuf_get(&plan_buf); if (plan_str) { flecs_json_string_escape(buf, plan_str); ecs_os_free(plan_str); } else { flecs_json_null(buf); } ecs_os_free(plan); ecs_os_free(cache_plan); ecs_log_enable_colors(prev_color); } static void flecs_json_serialize_query_profile( const ecs_world_t *world, ecs_strbuf_t *buf, const ecs_iter_t *it, const ecs_iter_to_json_desc_t *desc) { if (!desc->query) { return; } ecs_time_t t = {0}; int32_t result_count = 0, entity_count = 0, i, sample_count = 100; ecs_size_t component_bytes = 0, shared_component_bytes = 0; double eval_time = 0, eval_min = 0, eval_max = 0; ecs_time_measure(&t); for (i = 0; i < sample_count; i ++) { result_count = 0; entity_count = 0; component_bytes = 0; shared_component_bytes = 0; ecs_iter_t qit = ecs_query_iter(world, desc->query); while (ecs_query_next(&qit)) { result_count ++; entity_count += qit.count; int8_t f, field_count = qit.field_count; for (f = 0; f < field_count; f ++) { size_t size = ecs_field_size(&qit, f); if (ecs_field_is_set(&qit, f) && size) { if (ecs_field_is_self(&qit, f)) { component_bytes += flecs_uto(ecs_size_t, size) * qit.count; } else { shared_component_bytes += flecs_uto(ecs_size_t, size); } } } } double time_measure = ecs_time_measure(&t); if (!i) { 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); /* 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); const ecs_entity_t *entities = ecs_table_entities(table); int32_t i, count = ecs_table_count(table); for (i = 0; i < count; i ++) { ecs_query_t *q = queries[i].poly; if (!q) { continue; } ecs_assert(flecs_poly_is(q, ecs_query_t), ECS_INTERNAL_ERROR, NULL); if (!(q->flags & EcsQueryMatchThis)) { continue; } ecs_iter_t qit = ecs_query_iter(world, q); if (!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; const ecs_entity_t *entities = ecs_table_entities(table); int32_t i, count = ecs_table_count(table); for (i = 0; i < count; i ++) { ecs_entity_t e = entities[i]; flecs_json_next(buf); flecs_json_path(buf, world, e); } } } flecs_json_array_pop(buf); return 0; } int flecs_json_serialize_refs( const ecs_world_t *world, ecs_strbuf_t *buf, ecs_entity_t entity, ecs_entity_t relationship) { flecs_json_memberl(buf, "refs"); flecs_json_object_push(buf); ecs_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, "has_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 = &ecs_table_entities(table)[it->offset]; /* Get path to parent once for entire table */ if (table->flags & EcsTableHasChildOf) { const ecs_table_record_t *tr = flecs_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); int8_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); uint32_t field_mask = (uint32_t)((1llu << field_count) - 1); if (q->static_id_fields == field_mask) { /* All matched ids are static, nothing to serialize */ return false; } flecs_json_memberl(buf, "ids"); flecs_json_array_push(buf); for (f = 0; f < field_count; f ++) { ecs_termset_t field_bit = (ecs_termset_t)(1u << f); if (!(it->set_fields & field_bit)) { /* 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_termset_t field_bit = (ecs_termset_t)(1u << 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) { int8_t f, field_count = it->field_count; if (!field_count) { return 0; } ecs_strbuf_appendlit(buf, "\"values\":"); flecs_json_array_push(buf); ecs_termset_t fields = it->set_fields; if (it->query) { fields &= it->query->data_fields; } ecs_termset_t row_fields = it->query ? it->query->row_fields : 0; for (f = 0; f < field_count; f ++) { ecs_termset_t field_bit = (ecs_termset_t)(1u << f); if (!(fields & field_bit)) { ecs_strbuf_list_appendlit(buf, "0"); continue; } ecs_json_value_ser_ctx_t *value_ctx = &ser_ctx->value_ctx[f]; if (!flecs_json_serialize_get_value_ctx( world, it->ids[f], value_ctx, desc)) { ecs_strbuf_list_appendlit(buf, "0"); continue; } void *ptr; if (row_fields & field_bit) { ptr = ecs_field_at_w_size(it, 0, f, i); } else { ecs_size_t size = it->sizes[f]; ptr = ecs_field_w_size(it, flecs_itosize(size), f); if (!ptr) { ecs_strbuf_list_appendlit(buf, "0"); continue; } if (!it->sources[f]) { ptr = ECS_ELEM(ptr, size, 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 #define FLECS_JSON_MAX_TABLE_COMPONENTS (256) 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) { flecs_json_memberl(buf, "type_info"); flecs_json_object_push(buf); int32_t i, type_count = table->type.count; for (i = 0; i < type_count; i ++) { const ecs_table_record_t *tr = &table->_->records[i]; ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; ecs_id_t id = table->type.array[i]; if (!(idr->flags & EcsIdIsSparse) && (!table->column_map || (table->column_map[i] == -1))) { continue; } if (!desc || !desc->serialize_builtin) { if (flecs_json_is_builtin(id)) { continue; } } const ecs_type_info_t *ti = idr->type_info; ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); flecs_json_next(buf); ecs_strbuf_appendlit(buf, "\""); flecs_json_id_member(buf, world, 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) { int16_t f, type_count = flecs_ito(int16_t, table->type.count); ecs_id_t *ids = table->type.array; int16_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 (idr->flags & EcsIdIsSparse) { continue; } if (!tag_count) { flecs_json_memberl(buf, "tags"); flecs_json_array_push(buf); } flecs_json_next(buf); ecs_strbuf_appendlit(buf, "\""); flecs_json_id_member(buf, world, id, desc ? desc->serialize_full_paths : true); ecs_strbuf_appendlit(buf, "\""); 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) { int16_t f, type_count = flecs_ito(int16_t, table->type.count); ecs_id_t *ids = table->type.array; int16_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; } } if (idr->flags & EcsIdIsSparse) { 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 = ecs_table_entities(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 int flecs_json_serialize_table_components( const ecs_world_t *world, ecs_table_t *table, const ecs_table_t *src_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 *component_count) { int32_t i, count = table->type.count; for (i = 0; i < count; i ++) { if (component_count[0] == FLECS_JSON_MAX_TABLE_COMPONENTS) { break; } ecs_id_t id = table->type.array[i]; if (!desc || !desc->serialize_builtin) { if (flecs_json_is_builtin(id)) { continue; } } void *ptr; const ecs_table_record_t *tr = &table->_->records[i]; ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; if (src_table) { if (!(idr->flags & EcsIdOnInstantiateInherit)) { continue; } } const ecs_type_info_t *ti; int32_t column_index = table->column_map ? table->column_map[i] : -1; if (column_index != -1) { ecs_column_t *column = &table->data.columns[column_index]; ti = column->ti; ptr = ECS_ELEM(column->data, ti->size, row); } else { if (!(idr->flags & EcsIdIsSparse)) { continue; } ecs_entity_t e = ecs_table_entities(table)[row]; ptr = flecs_sparse_get_any(idr->sparse, 0, e); ti = idr->type_info; } if (!ptr) { continue; } if (!component_count[0]) { flecs_json_memberl(buf, "components"); flecs_json_object_push(buf); } bool has_reflection; const EcsTypeSerializer *type_ser; if (values_ctx) { ecs_json_value_ser_ctx_t *value_ctx = &values_ctx[component_count[0]]; has_reflection = flecs_json_serialize_get_value_ctx( world, id, value_ctx, desc); flecs_json_member(buf, value_ctx->id_label); type_ser = value_ctx->ser; } else { ecs_strbuf_list_next(buf); ecs_strbuf_appendlit(buf, "\""); flecs_json_id_member(buf, world, id, desc ? desc->serialize_full_paths : true); ecs_strbuf_appendlit(buf, "\":"); type_ser = NULL; if (!desc || desc->serialize_values) { ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); type_ser = ecs_get(world, ti->component, EcsTypeSerializer); } has_reflection = type_ser != NULL; } component_count[0] ++; if (has_reflection && (!desc || desc->serialize_values)) { ecs_assert(type_ser != NULL, ECS_INTERNAL_ERROR, NULL); if (flecs_json_ser_type( world, &type_ser->ops, ptr, buf) != 0) { goto error; } } else { ecs_strbuf_appendlit(buf, "null"); } } if (component_count[0]) { flecs_json_object_pop(buf); } return 0; error: return -1; } 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); int32_t component_count = 0; flecs_json_serialize_table_components( world, base_table, table, buf, NULL, desc, ECS_RECORD_TO_ROW(base_record->row), &component_count); 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; } 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[FLECS_JSON_MAX_TABLE_COMPONENTS] = {{0}}; int32_t component_count = 0; 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; } component_count = 0; /* Each row has the same number of components */ if (flecs_json_serialize_table_components( world, table, NULL, buf, values_ctx, desc, i, &component_count)) { result = -1; break; } if (desc->serialize_matches) { flecs_json_serialize_matches( world, buf, it->entities[i - it->offset]); } if (desc->serialize_refs) { flecs_json_serialize_refs(world, buf, it->entities[i - it->offset], desc->serialize_refs); } if (desc->serialize_alerts) { flecs_json_serialize_alerts(world, buf, it->entities[i - it->offset]); } flecs_json_object_pop(buf); } for (i = 0; i < component_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, !!((1llu << term->field_index) & q->data_fields)); 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, false); } 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_entity_t underlying = desc->underlying_type; if (!underlying) { underlying = ecs_id(ecs_i32_t); } ecs_assert(ecs_is_valid(world, underlying), ECS_INVALID_PARAMETER, "invalid underlying type for enum"); const EcsPrimitive *p = ecs_get(world, underlying, EcsPrimitive); if (!p) { char *path = ecs_get_path(world, underlying); ecs_err("underlying type '%s' must be a primitive type", path); ecs_os_free(path); return 0; } bool ut_is_unsigned = false; ecs_primitive_kind_t kind = p->kind; if (kind == EcsU8 || kind == EcsU16 || kind == EcsU32 || kind == EcsU64) { ut_is_unsigned = true; } ecs_set(world, t, EcsEnum, { .underlying_type = underlying }); ecs_entity_t old_scope = ecs_set_scope(world, t); int i; for (i = 0; i < ECS_MEMBER_DESC_CACHE_SIZE; i ++) { const ecs_enum_constant_t *m_desc = &desc->constants[i]; if (!m_desc->name) { break; } ecs_entity_t c = ecs_entity(world, { .name = m_desc->name }); if (!m_desc->value && !m_desc->value_unsigned) { ecs_add_id(world, c, EcsConstant); } else { void *ptr = ecs_ensure_id(world, c, ecs_pair(EcsConstant, underlying)); ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_meta_cursor_t cur = ecs_meta_cursor(world, underlying, ptr); int ret; if (m_desc->value) { if (ut_is_unsigned) { char *path = ecs_get_path(world, c); ecs_err("use desc::value_unsigned for constant '%s' which" "has an unsigned underlying type", path); ecs_os_free(path); return 0; } ret = ecs_meta_set_int(&cur, m_desc->value); } else { if (!ut_is_unsigned) { char *path = ecs_get_path(world, c); ecs_err("use desc::value for constant '%s' which" "has a signed underlying type", path); ecs_os_free(path); return 0; } ret = ecs_meta_set_uint(&cur, m_desc->value_unsigned); } if (ret) { char *type_str = ecs_get_path(world, t); char *utype_str = ecs_get_path(world, underlying); ecs_err("value for constant '%s' for enum '%s' is not valid " "for underlying type '%s'", type_str, utype_str); ecs_os_free(utype_str); ecs_os_free(type_str); continue; } ecs_modified_id(world, c, ecs_pair(EcsConstant, underlying)); } } ecs_set_scope(world, old_scope); flecs_resume_readonly(world, &rs); if (i == 0) { ecs_err("enum '%s' has no constants", ecs_get_name(world, t)); ecs_delete(world, t); return 0; } return t; } ecs_entity_t ecs_bitmask_init( ecs_world_t *world, const ecs_bitmask_desc_t *desc) { ecs_suspend_readonly_state_t rs; world = flecs_suspend_readonly(world, &rs); ecs_entity_t t = desc->entity; if (!t) { t = ecs_new_low_id(world); } ecs_add(world, t, EcsBitmask); ecs_entity_t old_scope = ecs_set_scope(world, t); int i; for (i = 0; i < ECS_MEMBER_DESC_CACHE_SIZE; i ++) { const ecs_bitmask_constant_t *m_desc = &desc->constants[i]; if (!m_desc->name) { break; } ecs_entity_t c = ecs_entity(world, { .name = m_desc->name }); if (!m_desc->value) { ecs_add_id(world, c, EcsConstant); } else { ecs_set_pair_second(world, c, EcsConstant, ecs_u32_t, { flecs_uto(uint32_t, m_desc->value) }); } } ecs_set_scope(world, old_scope); flecs_resume_readonly(world, &rs); if (i == 0) { ecs_err("bitmask '%s' has no constants", ecs_get_name(world, t)); ecs_delete(world, t); return 0; } return t; } ecs_entity_t ecs_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, .use_offset = m_desc->use_offset }); 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 flecs_meta_utils_parse_ctx_t { const char *name; const char *desc; } flecs_meta_utils_parse_ctx_t; typedef struct flecs_meta_utils_type_t { ecs_meta_token_t type; ecs_meta_token_t params; bool is_const; bool is_ptr; } flecs_meta_utils_type_t; typedef struct flecs_meta_utils_member_t { flecs_meta_utils_type_t type; ecs_meta_token_t name; int64_t count; bool is_partial; } flecs_meta_utils_member_t; typedef struct flecs_meta_utils_constant_t { ecs_meta_token_t name; int64_t value; bool is_value_set; } flecs_meta_utils_constant_t; typedef struct flecs_meta_utils_params_t { flecs_meta_utils_type_t key_type; flecs_meta_utils_type_t type; int64_t count; bool is_key_value; bool is_fixed_size; } flecs_meta_utils_params_t; static const char* skip_scope(const char *ptr, flecs_meta_utils_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, flecs_meta_utils_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 * flecs_meta_utils_open_scope( const char *ptr, flecs_meta_utils_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* flecs_meta_utils_parse_constant( const char *ptr, flecs_meta_utils_constant_t *token, flecs_meta_utils_parse_ctx_t *ctx) { ptr = flecs_meta_utils_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* flecs_meta_utils_parse_type( const char *ptr, flecs_meta_utils_type_t *token, flecs_meta_utils_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* flecs_meta_utils_parse_member( const char *ptr, flecs_meta_utils_member_t *token, flecs_meta_utils_parse_ctx_t *ctx) { ptr = flecs_meta_utils_open_scope(ptr, ctx); if (!ptr) { return NULL; } token->count = 1; token->is_partial = false; /* Parse member type */ ptr = flecs_meta_utils_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 flecs_meta_utils_parse_desc( const char *ptr, flecs_meta_utils_params_t *token, flecs_meta_utils_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 = flecs_meta_utils_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 = flecs_meta_utils_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 flecs_meta_utils_lookup( ecs_world_t *world, flecs_meta_utils_type_t *token, const char *ptr, int64_t count, flecs_meta_utils_parse_ctx_t *ctx); static ecs_entity_t flecs_meta_utils_lookup_array( ecs_world_t *world, ecs_entity_t e, const char *params_decl, flecs_meta_utils_parse_ctx_t *ctx) { flecs_meta_utils_parse_ctx_t param_ctx = { .name = ctx->name, .desc = params_decl }; flecs_meta_utils_params_t params; if (flecs_meta_utils_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 flecs_meta_utils_lookup_vector( ecs_world_t *world, ecs_entity_t e, const char *params_decl, flecs_meta_utils_parse_ctx_t *ctx) { flecs_meta_utils_parse_ctx_t param_ctx = { .name = ctx->name, .desc = params_decl }; flecs_meta_utils_params_t params; if (flecs_meta_utils_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 = flecs_meta_utils_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 flecs_meta_utils_lookup_bitmask( ecs_world_t *world, ecs_entity_t e, const char *params_decl, flecs_meta_utils_parse_ctx_t *ctx) { (void)e; flecs_meta_utils_parse_ctx_t param_ctx = { .name = ctx->name, .desc = params_decl }; flecs_meta_utils_params_t params; if (flecs_meta_utils_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 = flecs_meta_utils_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 flecs_meta_utils_lookup( ecs_world_t *world, flecs_meta_utils_type_t *token, const char *ptr, int64_t count, flecs_meta_utils_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 = flecs_meta_utils_lookup_array(world, 0, token->params, ctx); } else if (!ecs_os_strcmp(typename, "ecs_vector") || !ecs_os_strcmp(typename, "flecs::vector")) { type = flecs_meta_utils_lookup_vector(world, 0, token->params, ctx); } else if (!ecs_os_strcmp(typename, "flecs::bitmask")) { type = flecs_meta_utils_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 flecs_meta_utils_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); flecs_meta_utils_member_t token; flecs_meta_utils_parse_ctx_t ctx = { .name = name, .desc = ptr }; ecs_entity_t old_scope = ecs_set_scope(world, t); while ((ptr = flecs_meta_utils_parse_member(ptr, &token, &ctx)) && ptr[0]) { ecs_entity_t m = ecs_entity(world, { .name = token.name }); ecs_entity_t type = flecs_meta_utils_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 flecs_meta_utils_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; flecs_meta_utils_parse_ctx_t ctx = { .name = name, .desc = ptr }; flecs_meta_utils_constant_t token; int64_t last_value = 0; ecs_entity_t old_scope = ecs_set_scope(world, t); while ((ptr = flecs_meta_utils_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 flecs_meta_utils_parse_enum( ecs_world_t *world, ecs_entity_t t, const char *desc) { ecs_set(world, t, EcsEnum, { .underlying_type = ecs_id(ecs_i32_t) }); return flecs_meta_utils_parse_constants(world, t, desc, false); } static int flecs_meta_utils_parse_bitmask( ecs_world_t *world, ecs_entity_t t, const char *desc) { ecs_add(world, t, EcsBitmask); return flecs_meta_utils_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 (flecs_meta_utils_parse_struct(world, component, desc)) { goto error; } break; case EcsEnumType: if (flecs_meta_utils_parse_enum(world, component, desc)) { goto error; } break; case EcsBitmaskType: if (flecs_meta_utils_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 #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 elements vs. size %d)", scope->elem_cur + 1, get_elem_count(scope)); 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) { bool is_primitive = false; if ((op->kind > EcsOpScope) && (op->count <= 1)) { is_primitive = true; } else if (op->kind == EcsOpOpaque) { const EcsOpaque *t = ecs_get(world, op->type, EcsOpaque); ecs_assert(t != NULL, ECS_INTERNAL_ERROR, NULL); if (ecs_has(world, t->as_type, EcsPrimitive)) { is_primitive = true; } } if ((cursor->is_primitive_scope = is_primitive)) { 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) { if (cursor->depth == 0) { return cursor->scope[0].type; } 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 EcsOpString: { char *result; if (value) { result = ecs_os_strdup("true"); } else { result = ecs_os_strdup("false"); } ecs_os_free(*(ecs_string_t*)ptr); set_T(ecs_string_t, ptr, result); break; } 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: 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 EcsOpString: { char *result = flecs_asprintf("%c", value); ecs_os_free(*(ecs_string_t*)ptr); set_T(ecs_string_t, ptr, result); break; } 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 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 EcsOpString: { char *result = flecs_asprintf("%"PRId64, value); ecs_os_free(*(ecs_string_t*)ptr); set_T(ecs_string_t, ptr, result); break; } 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: { 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 EcsOpString: { char *result = flecs_asprintf("%"PRIu64, value); ecs_os_free(*(ecs_string_t*)ptr); set_T(ecs_string_t, ptr, result); break; } 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: 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 EcsOpString: { char *result = flecs_asprintf("%f", value); ecs_os_free(*(ecs_string_t*)ptr); set_T(ecs_string_t, ptr, result); break; } 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: 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); if (!value) { return -1; } 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 EcsOpString: { char *result = ecs_get_path(cursor->world, value); ecs_os_free(*(ecs_string_t*)ptr); set_T(ecs_string_t, ptr, result); 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: 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 EcsOpString: { char *result = ecs_id_str(cursor->world, value); ecs_os_free(*(ecs_string_t*)ptr); set_T(ecs_string_t, ptr, result); 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 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); } /* Handler to get string from opaque (see ecs_meta_get_string below) */ static int ecs_meta_get_string_value_from_opaque( const struct ecs_serializer_t *ser, ecs_entity_t type, const void *value) { if(type != ecs_id(ecs_string_t)) { ecs_err("Expected value call for opaque type to be a string"); return -1; } char*** ctx = (char ***) ser->ctx; *ctx = ECS_CONST_CAST(char**, value); return 0; } /* Handler to get string from opaque (see ecs_meta_get_string below) */ static int ecs_meta_get_string_member_from_opaque( const struct ecs_serializer_t* ser, const char* name) { (void)ser; // silence unused warning (void)name; // silence unused warning ecs_err("Unexpected member call when serializing string from opaque"); return -1; } const char* ecs_meta_get_string( const ecs_meta_cursor_t *cursor) { ecs_meta_scope_t *scope = flecs_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 EcsOpOpaque: { /* If opaque type happens to map to a string, retrieve it. Otherwise, fallback to default case (error). */ const EcsOpaque *opaque = ecs_get(cursor->world, op->type, EcsOpaque); if(opaque && opaque->as_type == ecs_id(ecs_string_t) && opaque->serialize) { char** str = NULL; ecs_serializer_t ser = { .world = cursor->world, .value = ecs_meta_get_string_value_from_opaque, .member = ecs_meta_get_string_member_from_opaque, .ctx = &str }; opaque->serialize(&ser, ptr); if(str && *str) return *str; /* invalid string, so fall through */ } /* Not a compatible opaque type, so fall through */ } /* fall through */ case EcsOpArray: case EcsOpVector: 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); } static int flecs_const_str_serialize(const ecs_serializer_t *ser, const void *ptr) { char **data = ECS_CONST_CAST(char**, ptr); ser->value(ser, ecs_id(ecs_string_t), data); return 0; } /* Initialize reflection data for core components */ static void flecs_meta_import_core_definitions( ecs_world_t *world) { ecs_struct(world, { .entity = ecs_id(EcsComponent), .members = { { .name = "size", .type = ecs_id(ecs_i32_t) }, { .name = "alignment", .type = ecs_id(ecs_i32_t) } } }); ecs_struct(world, { .entity = ecs_id(EcsDefaultChildComponent), .members = { { .name = "component", .type = ecs_id(ecs_entity_t) } } }); /* Define const string as an opaque type that maps to string This enables reflection for strings that are in .rodata, (read-only) so that the meta add-on does not try to free them. This opaque type defines how to serialize (read) the string, but won't let users assign a new value. */ ecs_entity_t const_string = ecs_opaque(world, { .entity = ecs_component(world, { .entity = ecs_entity(world, { .name = "flecs.core.const_string_t", .root_sep = "" }), .type = { .size = ECS_SIZEOF(const char*), .alignment = ECS_ALIGNOF(const char*) } }), .type = { .as_type = ecs_id(ecs_string_t), .serialize = flecs_const_str_serialize, } }); ecs_entity_t string_vec = ecs_vector(world, { .entity = ecs_entity(world, { .name = "flecs.core.string_vec_t", .root_sep = "" }), .type = ecs_id(ecs_string_t) }); ecs_entity_t addon_vec = ecs_opaque(world, { .entity = ecs_component(world, { .entity = ecs_entity(world, { .name = "flecs.core.addon_vec_t", .root_sep = "" }), .type = { .size = ECS_SIZEOF(char**), .alignment = ECS_ALIGNOF(char**) } }), .type = { .as_type = string_vec, .serialize = flecs_addon_vec_serialize, .count = flecs_addon_vec_count, } }); ecs_struct(world, { .entity = ecs_entity(world, { .name = "flecs.core.build_info_t", .root_sep = "" }), .members = { { .name = "compiler", .type = const_string }, { .name = "addons", .type = addon_vec }, { .name = "version", .type = const_string }, { .name = "version_major", .type = ecs_id(ecs_i16_t) }, { .name = "version_minor", .type = ecs_id(ecs_i16_t) }, { .name = "version_patch", .type = ecs_id(ecs_i16_t) }, { .name = "debug", .type = ecs_id(ecs_bool_t) }, { .name = "sanitize", .type = ecs_id(ecs_bool_t) }, { .name = "perf_trace", .type = ecs_id(ecs_bool_t) } } }); } /* Initialize reflection data for 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) }, { .name = "use_offset", .type = ecs_id(ecs_bool_t) } } }); ecs_entity_t vr = ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_entity(world, { .name = "value_range" }), .members = { { .name = "min", .type = ecs_id(ecs_f64_t) }, { .name = "max", .type = ecs_id(ecs_f64_t) } } }); ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_id(EcsMemberRanges), .members = { { .name = "value", .type = vr }, { .name = "warning", .type = vr }, { .name = "error", .type = vr } } }); ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_id(EcsEnum), .members = { { .name = "underlying_type", .type = ecs_id(ecs_entity_t) } } }); ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_id(EcsArray), .members = { { .name = "type", .type = ecs_id(ecs_entity_t) }, { .name = "count", .type = ecs_id(ecs_i32_t) }, } }); ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_id(EcsVector), .members = { { .name = "type", .type = ecs_id(ecs_entity_t) } } }); ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_id(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); dst->underlying_type = src->underlying_type; }) static ECS_MOVE(EcsEnum, dst, src, { flecs_constants_dtor(&dst->constants); dst->constants = src->constants; dst->underlying_type = src->underlying_type; 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) { /* Determine if this is an existing type or a reflection-defined type (runtime type) */ meta_type->existing = ecs_has(world, type, EcsComponent); /* For existing types, ensure that component has a default constructor, to prevent crashing * serializers on uninitialized values. For runtime types (rtt), the default hooks are set by flecs_meta_rtt_init_default_hooks */ ecs_type_info_t *ti = flecs_type_info_ensure(world, type); if (meta_type->existing && !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 = m->offset || m->use_offset; /* 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); ecs_entity_t ut = ptr->underlying_type; /* It's possible that a constant is added to an entity that didn't have an * Enum component yet. In that case derive the underlying type from the * first constant. */ if (!ut) { if (ecs_id_is_pair(constant_id)) { ut = ptr->underlying_type = ecs_pair_second(world, constant_id); } else { /* Default to i32 */ ut = ecs_id(ecs_i32_t); } } ecs_assert(ut != 0, ECS_INVALID_OPERATION, "missing underlying type for enum"); const EcsPrimitive *p = ecs_get(world, ut, EcsPrimitive); if (!p) { char *path = ecs_get_path(world, ut); ecs_err("underlying type '%s' must be a primitive type", path); ecs_os_free(path); return -1; } bool ut_is_unsigned = false; ecs_primitive_kind_t kind = p->kind; if (kind == EcsU8 || kind == EcsU16 || kind == EcsU32 || kind == EcsU64) { ut_is_unsigned = true; } /* 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 */ int64_t value = 0; uint64_t value_unsigned = 0; bool value_set = false; if (ecs_id_is_pair(constant_id)) { ecs_value_t v = { .type = ut }; v.ptr = ecs_get_mut_id(world, e, ecs_pair(EcsConstant, ut)); if (!v.ptr) { char *has_pair = ecs_id_str(world, constant_id); char *expect_pair = ecs_id_str(world, ecs_pair(EcsConstant, ut)); char *path = ecs_get_path(world, e); ecs_err( "enum constant '%s' has incorrect value pair (expected %s, got %s)", path, expect_pair, has_pair); ecs_os_free(path); ecs_os_free(has_pair); ecs_os_free(expect_pair); return -1; } ecs_meta_cursor_t c; if (ut_is_unsigned) { /* It doesn't matter that the underlying value is an i64*/ c = ecs_meta_cursor(world, ecs_id(ecs_u64_t), &value_unsigned); } else { c = ecs_meta_cursor(world, ecs_id(ecs_i64_t), &value); } if (ecs_meta_set_value(&c, &v)) { char *path = ecs_get_path(world, e); ecs_err("failed to get constant value for '%s'", path); ecs_os_free(path); return -1; } value_set = true; } /* 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 (ut_is_unsigned) { if (value_set) { if (c->value_unsigned == value_unsigned) { char *path = ecs_get_path(world, e); ecs_abort(ECS_INTERNAL_ERROR, "conflicting constant value %u for '%s' (other is '%s')", value_unsigned, path, c->name); ecs_os_free(path); return -1; } } else { if (c->value_unsigned >= value_unsigned) { value_unsigned = c->value_unsigned + 1; } } } else { 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); flecs_dump_backtrace(stdout); return -1; } } else { if (c->value >= value) { value = c->value + 1; } } } } ecs_map_init_if(&ptr->constants, &world->allocator); ecs_enum_constant_t *c; if (ut_is_unsigned) { c = ecs_map_insert_alloc_t(&ptr->constants, ecs_enum_constant_t, value_unsigned); c->value_unsigned = value_unsigned; c->value = 0; } else { c = ecs_map_insert_alloc_t(&ptr->constants, ecs_enum_constant_t, (ecs_map_key_t)value); c->value_unsigned = 0; c->value = value; } c->name = ecs_os_strdup(ecs_get_name(world, e)); c->constant = e; if (!value_set) { void *cptr = ecs_ensure_id(world, e, ecs_pair(EcsConstant, ut)); ecs_assert(cptr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_meta_cursor_t cur = ecs_meta_cursor(world, ut, cptr); if (ut_is_unsigned) { if (ecs_meta_set_uint(&cur, value_unsigned)) { char *path = ecs_get_path(world, e); ecs_err("failed to assign value to constant '%s'", path); ecs_os_free(path); return -1; } } else { if (ecs_meta_set_int(&cur, value)) { char *path = ecs_get_path(world, e); ecs_err("failed to assign value to constant '%s'", path); ecs_os_free(path); return -1; } } } ecs_modified(world, type, EcsEnum); return 0; } static int flecs_add_constant_to_bitmask( ecs_world_t *world, ecs_entity_t type, ecs_entity_t e, ecs_id_t constant_id) { 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; EcsEnum *data = ecs_field(it, EcsEnum, 0); int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; ecs_entity_t underlying_type = data[i].underlying_type; if (!underlying_type) { underlying_type = data[i].underlying_type = ecs_id(ecs_i32_t); } const EcsComponent *uc = ecs_get(world, underlying_type, EcsComponent); if (!uc) { char *str = ecs_get_path(world, underlying_type); ecs_err("uderlying_type entity for enum '%s' is not a type", str); ecs_os_free(str); continue; } if (flecs_init_type(world, e, EcsEnumType, uc->size, uc->alignment)) { continue; } ecs_add_id(world, e, EcsExclusive); 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)); flecs_dump_backtrace(stdout); 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 flecs_member_on_set(ecs_iter_t *it) { EcsMember *mbr = ecs_field(it, EcsMember, 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", .add = ecs_ids(ecs_pair(EcsOnInstantiate, EcsDontInherit)) }), .type.size = sizeof(EcsType), .type.alignment = ECS_ALIGNOF(EcsType), }); ecs_component(world, { .entity = ecs_entity(world, { .id = ecs_id(EcsPrimitive), .name = "primitive", .symbol = "EcsPrimitive", .add = ecs_ids(ecs_pair(EcsOnInstantiate, EcsDontInherit)) }), .type.size = sizeof(EcsPrimitive), .type.alignment = ECS_ALIGNOF(EcsPrimitive) }); ecs_component(world, { .entity = ecs_entity(world, { .id = EcsConstant, .name = "constant", .symbol = "EcsConstant", .add = ecs_ids(ecs_pair(EcsOnInstantiate, EcsDontInherit)) }) }); ecs_component(world, { .entity = ecs_entity(world, { .id = ecs_id(EcsEnum), .name = "enum", .symbol = "EcsEnum", .add = ecs_ids(ecs_pair(EcsOnInstantiate, EcsDontInherit)) }), .type.size = sizeof(EcsEnum), .type.alignment = ECS_ALIGNOF(EcsEnum) }); ecs_component(world, { .entity = ecs_entity(world, { .id = ecs_id(EcsBitmask), .name = "bitmask", .symbol = "EcsBitmask", .add = ecs_ids(ecs_pair(EcsOnInstantiate, EcsDontInherit)) }), .type.size = sizeof(EcsBitmask), .type.alignment = ECS_ALIGNOF(EcsBitmask) }); ecs_component(world, { .entity = ecs_entity(world, { .id = ecs_id(EcsMember), .name = "member", .symbol = "EcsMember", .add = ecs_ids(ecs_pair(EcsOnInstantiate, EcsDontInherit)) }), .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", .add = ecs_ids(ecs_pair(EcsOnInstantiate, EcsDontInherit)) }), .type.size = sizeof(EcsMemberRanges), .type.alignment = ECS_ALIGNOF(EcsMemberRanges) }); ecs_component(world, { .entity = ecs_entity(world, { .id = ecs_id(EcsStruct), .name = "struct", .symbol = "EcsStruct", .add = ecs_ids(ecs_pair(EcsOnInstantiate, EcsDontInherit)) }), .type.size = sizeof(EcsStruct), .type.alignment = ECS_ALIGNOF(EcsStruct) }); ecs_component(world, { .entity = ecs_entity(world, { .id = ecs_id(EcsArray), .name = "array", .symbol = "EcsArray", .add = ecs_ids(ecs_pair(EcsOnInstantiate, EcsDontInherit)) }), .type.size = sizeof(EcsArray), .type.alignment = ECS_ALIGNOF(EcsArray) }); ecs_component(world, { .entity = ecs_entity(world, { .id = ecs_id(EcsVector), .name = "vector", .symbol = "EcsVector", .add = ecs_ids(ecs_pair(EcsOnInstantiate, EcsDontInherit)) }), .type.size = sizeof(EcsVector), .type.alignment = ECS_ALIGNOF(EcsVector) }); ecs_component(world, { .entity = ecs_entity(world, { .id = ecs_id(EcsOpaque), .name = "opaque", .symbol = "EcsOpaque", .add = ecs_ids(ecs_pair(EcsOnInstantiate, EcsDontInherit)) }), .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) }, .events = {EcsOnSet}, .callback = flecs_set_primitive }); ecs_observer(world, { .query.terms[0] = { .id = ecs_id(EcsMember) }, .events = {EcsOnSet}, .callback = flecs_set_member }); ecs_observer(world, { .query.terms[0] = { .id = ecs_id(EcsMemberRanges) }, .events = {EcsOnSet}, .callback = flecs_set_member_ranges }); ecs_observer(world, { .query.terms[0] = { .id = ecs_id(EcsEnum) }, .events = {EcsOnSet}, .callback = flecs_add_enum }); ecs_observer(world, { .query.terms[0] = { .id = ecs_id(EcsBitmask) }, .events = {EcsOnAdd}, .callback = flecs_add_bitmask }); ecs_observer(world, { .query.terms[0] = { .id = EcsConstant }, .events = {EcsOnAdd}, .callback = flecs_add_constant }); ecs_observer(world, { .query.terms[0] = { .id = ecs_pair(EcsConstant, EcsWildcard) }, .events = {EcsOnSet}, .callback = flecs_add_constant }); ecs_observer(world, { .query.terms[0] = { .id = ecs_id(EcsArray) }, .events = {EcsOnSet}, .callback = flecs_set_array }); ecs_observer(world, { .query.terms[0] = { .id = ecs_id(EcsVector) }, .events = {EcsOnSet}, .callback = flecs_set_vector }); ecs_observer(world, { .query.terms[0] = { .id = ecs_id(EcsOpaque) }, .events = {EcsOnSet}, .callback = flecs_set_custom_type }); ecs_observer(world, { .query.terms[0] = { .id = ecs_id(EcsUnit) }, .events = {EcsOnSet}, .callback = flecs_set_unit }); ecs_observer(world, { .query.terms[0] = { .id = ecs_id(EcsType) }, .events = {EcsOnSet}, .callback = ecs_meta_type_serialized_init }); ecs_observer(world, { .query.terms[0] = { .id = ecs_id(EcsType) }, .events = {EcsOnSet}, .callback = flecs_rtt_init_default_hooks }); 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. Can be used as hint by deserializers */ 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/rtt_lifecycle.c * @brief Runtime components lifecycle management */ #ifdef FLECS_META /* Stores all the information necessary to forward a hook call to a * struct's member type */ typedef struct ecs_rtt_call_data_t { union { ecs_xtor_t xtor; ecs_move_t move; ecs_copy_t copy; } hook; const ecs_type_info_t *type_info; int32_t offset; int32_t count; } ecs_rtt_call_data_t; /* Lifecycle context for runtime structs */ typedef struct ecs_rtt_struct_ctx_t { ecs_vec_t vctor; /* vector */ ecs_vec_t vdtor; /* vector */ ecs_vec_t vmove; /* vector */ ecs_vec_t vcopy; /* vector */ } ecs_rtt_struct_ctx_t; /* Lifecycle context for runtime arrays */ typedef struct ecs_rtt_array_ctx_t { const ecs_type_info_t *type_info; int32_t elem_count; } ecs_rtt_array_ctx_t; /* Lifecycle context for runtime vectors */ typedef struct ecs_rtt_vector_ctx_t { const ecs_type_info_t *type_info; } ecs_rtt_vector_ctx_t; /* Generic copy assign hook */ static void flecs_rtt_default_copy( void *dst_ptr, const void *src_ptr, int32_t count, const ecs_type_info_t *type_info) { ecs_os_memcpy(dst_ptr, src_ptr, count * type_info->size); } /* Generic move assign hook */ static void flecs_rtt_default_move( void *dst_ptr, void *src_ptr, int32_t count, const ecs_type_info_t *type_info) { flecs_rtt_default_copy(dst_ptr, src_ptr, count, type_info); } /* * * RTT struct support * */ /* Invokes struct member type's constructor/destructor using saved information * in the lifecycle context */ static void flecs_rtt_struct_xtor( ecs_vec_t *xtor_data_vec, void *ptr, int32_t count, const ecs_type_info_t *type_info) { int cb_count = ecs_vec_count(xtor_data_vec); int i, j; for (j = 0; j < count; j++) { void *elem_ptr = ECS_ELEM(ptr, type_info->size, j); for (i = 0; i < cb_count; i++) { ecs_rtt_call_data_t *xtor_data = ecs_vec_get_t(xtor_data_vec, ecs_rtt_call_data_t, i); xtor_data->hook.xtor( ECS_OFFSET(elem_ptr, xtor_data->offset), xtor_data->count, xtor_data->type_info); } } } /* Generic struct constructor. It will read hook information call data from * the structs's lifecycle context and call the constructors configured when * the type was created. */ static void flecs_rtt_struct_ctor( void *ptr, int32_t count, const ecs_type_info_t *type_info) { ecs_rtt_struct_ctx_t *rtt_ctx = type_info->hooks.lifecycle_ctx; ecs_assert(rtt_ctx != NULL, ECS_INTERNAL_ERROR, NULL); flecs_rtt_struct_xtor(&rtt_ctx->vctor, ptr, count, type_info); } /* Generic struct destructor. It will read hook information call data from * the structs's lifecycle context and call the constructors configured when * the type was created. */ static void flecs_rtt_struct_dtor( void *ptr, int32_t count, const ecs_type_info_t *type_info) { ecs_rtt_struct_ctx_t *rtt_ctx = type_info->hooks.lifecycle_ctx; ecs_assert(rtt_ctx != NULL, ECS_INTERNAL_ERROR, NULL); flecs_rtt_struct_xtor(&rtt_ctx->vdtor, ptr, count, type_info); } /* Generic move hook. It will read hook information call data from the * structs's lifecycle context and call the move hooks configured when * the type was created. */ static void flecs_rtt_struct_move( void *dst_ptr, void *src_ptr, int32_t count, const ecs_type_info_t *type_info) { ecs_rtt_struct_ctx_t *rtt_ctx = type_info->hooks.lifecycle_ctx; ecs_assert(rtt_ctx != NULL, ECS_INTERNAL_ERROR, NULL); int cb_count = ecs_vec_count(&rtt_ctx->vmove); int i, j; for (j = 0; j < count; j++) { ecs_size_t elem_offset = type_info->size * j; void *elem_dst_ptr = ECS_OFFSET(dst_ptr, elem_offset); void *elem_src_ptr = ECS_OFFSET(src_ptr, elem_offset); for (i = 0; i < cb_count; i++) { ecs_rtt_call_data_t *move_data = ecs_vec_get_t(&rtt_ctx->vmove, ecs_rtt_call_data_t, i); move_data->hook.move( ECS_OFFSET(elem_dst_ptr, move_data->offset), ECS_OFFSET(elem_src_ptr, move_data->offset), move_data->count, move_data->type_info); } } } /* Generic copy hook. It will read hook information call data from the * structs's lifecycle context and call the copy hooks configured when * the type was created. */ static void flecs_rtt_struct_copy( void *dst_ptr, const void *src_ptr, int32_t count, const ecs_type_info_t *type_info) { ecs_rtt_struct_ctx_t *rtt_ctx = type_info->hooks.lifecycle_ctx; ecs_assert(rtt_ctx != NULL, ECS_INTERNAL_ERROR, NULL); int cb_count = ecs_vec_count(&rtt_ctx->vcopy); int i, j; for (j = 0; j < count; j++) { ecs_size_t elem_offset = type_info->size * j; void *elem_dst_ptr = ECS_OFFSET(dst_ptr, elem_offset); const void *elem_src_ptr = ECS_OFFSET(src_ptr, elem_offset); for (i = 0; i < cb_count; i++) { ecs_rtt_call_data_t *copy_data = ecs_vec_get_t(&rtt_ctx->vcopy, ecs_rtt_call_data_t, i); copy_data->hook.copy( ECS_OFFSET(elem_dst_ptr, copy_data->offset), ECS_OFFSET(elem_src_ptr, copy_data->offset), copy_data->count, copy_data->type_info); } } } static void flecs_rtt_free_lifecycle_struct_ctx( void *ctx) { if (!ctx) { return; } ecs_rtt_struct_ctx_t *lifecycle_ctx = ctx; ecs_vec_fini_t(NULL, &lifecycle_ctx->vctor, ecs_rtt_call_data_t); ecs_vec_fini_t(NULL, &lifecycle_ctx->vdtor, ecs_rtt_call_data_t); ecs_vec_fini_t(NULL, &lifecycle_ctx->vmove, ecs_rtt_call_data_t); ecs_vec_fini_t(NULL, &lifecycle_ctx->vcopy, ecs_rtt_call_data_t); ecs_os_free(ctx); } static ecs_rtt_struct_ctx_t * flecs_rtt_configure_struct_hooks( ecs_world_t *world, const ecs_type_info_t *ti, ecs_flags32_t flags, bool ctor, bool dtor, bool move, bool copy) { ecs_type_hooks_t hooks = ti->hooks; if (hooks.lifecycle_ctx_free) { hooks.lifecycle_ctx_free(hooks.lifecycle_ctx); } ecs_rtt_struct_ctx_t *rtt_ctx = NULL; if (ctor || dtor || move || copy) { rtt_ctx = ecs_os_malloc_t(ecs_rtt_struct_ctx_t); ecs_vec_init_t(NULL, &rtt_ctx->vctor, ecs_rtt_call_data_t, 0); ecs_vec_init_t(NULL, &rtt_ctx->vdtor, ecs_rtt_call_data_t, 0); ecs_vec_init_t(NULL, &rtt_ctx->vmove, ecs_rtt_call_data_t, 0); ecs_vec_init_t(NULL, &rtt_ctx->vcopy, ecs_rtt_call_data_t, 0); hooks.lifecycle_ctx = rtt_ctx; hooks.lifecycle_ctx_free = flecs_rtt_free_lifecycle_struct_ctx; if (ctor) { hooks.ctor = flecs_rtt_struct_ctor; } if (dtor) { hooks.dtor = flecs_rtt_struct_dtor; } if (move) { hooks.move = flecs_rtt_struct_move; } if (copy) { hooks.copy = flecs_rtt_struct_copy; } } else { hooks.lifecycle_ctx = NULL; hooks.lifecycle_ctx_free = NULL; } hooks.flags |= flags; hooks.flags &= ECS_TYPE_HOOKS_ILLEGAL; ecs_set_hooks_id(world, ti->component, &hooks); return rtt_ctx; } /* Checks if a struct member's types have hooks installed. If so, it generates * and installs required hooks for the struct type itself. These hooks will * invoke the member hooks when necessary */ static void flecs_rtt_init_default_hooks_struct( ecs_world_t *world, ecs_entity_t component, const ecs_type_info_t *ti) { /* Obtain struct information to figure out what members it contains: */ const EcsStruct *struct_info = ecs_get(world, component, EcsStruct); ecs_assert(struct_info != NULL, ECS_INTERNAL_ERROR, NULL); /* These flags will be set to true if we determine we need to generate a * hook of a particular type: */ bool ctor_hook_required = false; bool dtor_hook_required = false; bool move_hook_required = false; bool copy_hook_required = false; /* Iterate all struct members and see if any member type has hooks. If so, * the struct itself will need to have that hook: */ int i, member_count = ecs_vec_count(&struct_info->members); ecs_member_t *members = ecs_vec_first(&struct_info->members); ecs_flags32_t flags = 0; for (i = 0; i < member_count; i++) { ecs_member_t *m = &members[i]; const ecs_type_info_t *member_ti = ecs_get_type_info(world, m->type); ctor_hook_required |= member_ti->hooks.ctor && member_ti->hooks.ctor != flecs_default_ctor; dtor_hook_required |= member_ti->hooks.dtor != NULL; move_hook_required |= member_ti->hooks.move != NULL; copy_hook_required |= member_ti->hooks.copy != NULL; flags |= member_ti->hooks.flags; } /* If any hook is required, then create a lifecycle context and configure a * generic hook that will interpret that context: */ ecs_rtt_struct_ctx_t *rtt_ctx = flecs_rtt_configure_struct_hooks( world, ti, flags, ctor_hook_required, dtor_hook_required, move_hook_required, copy_hook_required); if (!rtt_ctx) { return; /* no hooks required */ } /* At least a hook was configured, therefore examine each struct member to * build the vector of calls that will then be executed by the generic hook * handler: */ for (i = 0; i < member_count; i++) { ecs_member_t *m = &members[i]; const ecs_type_info_t *member_ti = ecs_get_type_info(world, m->type); if (ctor_hook_required) { ecs_rtt_call_data_t *ctor_data = ecs_vec_append_t(NULL, &rtt_ctx->vctor, ecs_rtt_call_data_t); ctor_data->count = m->count; ctor_data->offset = m->offset; ctor_data->type_info = member_ti; if (member_ti->hooks.ctor) { ctor_data->hook.xtor = member_ti->hooks.ctor; } else { ctor_data->hook.xtor = flecs_default_ctor; } } if (dtor_hook_required && member_ti->hooks.dtor) { ecs_rtt_call_data_t *dtor_data = ecs_vec_append_t(NULL, &rtt_ctx->vdtor, ecs_rtt_call_data_t); dtor_data->count = m->count; dtor_data->offset = m->offset; dtor_data->type_info = member_ti; dtor_data->hook.xtor = member_ti->hooks.dtor; } if (move_hook_required) { ecs_rtt_call_data_t *move_data = ecs_vec_append_t(NULL, &rtt_ctx->vmove, ecs_rtt_call_data_t); move_data->offset = m->offset; move_data->type_info = member_ti; move_data->count = m->count; if (member_ti->hooks.move) { move_data->hook.move = member_ti->hooks.move; } else { move_data->hook.move = flecs_rtt_default_move; } } if (copy_hook_required) { ecs_rtt_call_data_t *copy_data = ecs_vec_append_t(NULL, &rtt_ctx->vcopy, ecs_rtt_call_data_t); copy_data->offset = m->offset; copy_data->type_info = member_ti; copy_data->count = m->count; if (member_ti->hooks.copy) { copy_data->hook.copy = member_ti->hooks.copy; } else { copy_data->hook.copy = flecs_rtt_default_copy; } } } } /* * * RTT array support * */ static void flecs_rtt_free_lifecycle_array_ctx( void *ctx) { if (!ctx) { return; } ecs_os_free(ctx); } /* Generic array constructor. It will invoke the constructor of the underlying * type for all the elements */ static void flecs_rtt_array_ctor( void *ptr, int32_t count, /* note: "count" is how many arrays to initialize, not how many elements are in the array */ const ecs_type_info_t *type_info) { ecs_rtt_array_ctx_t *rtt_ctx = type_info->hooks.lifecycle_ctx; ecs_xtor_t ctor = rtt_ctx->type_info->hooks.ctor; int i; for (i = 0; i < count; i++) { void *arr = ECS_ELEM(ptr, type_info->size, i); ctor(arr, rtt_ctx->elem_count, rtt_ctx->type_info); } } /* Generic array constructor. It will invoke the destructor of the underlying * type for all the elements */ static void flecs_rtt_array_dtor( void *ptr, int32_t count, /* note: "count" is how many arrays to destroy, not how many elements are in the array */ const ecs_type_info_t *type_info) { ecs_rtt_array_ctx_t *rtt_ctx = type_info->hooks.lifecycle_ctx; ecs_xtor_t dtor = rtt_ctx->type_info->hooks.dtor; int i; for (i = 0; i < count; i++) { void *arr = ECS_ELEM(ptr, type_info->size, i); dtor(arr, rtt_ctx->elem_count, rtt_ctx->type_info); } } /* Generic array move hook. It will invoke the move hook of the underlying * type for all the elements */ static void flecs_rtt_array_move( void *dst_ptr, void *src_ptr, int32_t count, /* note: "count" is how many arrays to move, not how many elements are in the array */ const ecs_type_info_t *type_info) { ecs_rtt_array_ctx_t *rtt_ctx = type_info->hooks.lifecycle_ctx; ecs_move_t move = rtt_ctx->type_info->hooks.move; int i; for (i = 0; i < count; i++) { void *src_arr = ECS_ELEM(src_ptr, type_info->size, i); void *dst_arr = ECS_ELEM(dst_ptr, type_info->size, i); move(dst_arr, src_arr, rtt_ctx->elem_count, rtt_ctx->type_info); } } /* Generic array copy hook. It will invoke the copy hook of the underlying * type for all the elements */ static void flecs_rtt_array_copy( void *dst_ptr, const void *src_ptr, int32_t count, /* note: "count" is how many arrays to copy, not how many elements are in the array */ const ecs_type_info_t *type_info) { ecs_rtt_array_ctx_t *rtt_ctx = type_info->hooks.lifecycle_ctx; ecs_copy_t copy = rtt_ctx->type_info->hooks.copy; int i; for (i = 0; i < count; i++) { const void *src_arr = ECS_ELEM(src_ptr, type_info->size, i); void *dst_arr = ECS_ELEM(dst_ptr, type_info->size, i); copy(dst_arr, src_arr, rtt_ctx->elem_count, rtt_ctx->type_info); } } /* Checks if an array's underlying type has hooks installed. If so, it generates * and installs required hooks for the array type itself. These hooks will * invoke the underlying type's hook for each element in the array. */ static void flecs_rtt_init_default_hooks_array( ecs_world_t *world, ecs_entity_t component) { const EcsArray *array_info = ecs_get(world, component, EcsArray); ecs_assert(array_info != NULL, ECS_INTERNAL_ERROR, NULL); const ecs_type_info_t *array_ti = ecs_get_type_info(world, array_info->type); bool ctor_hook_required = array_ti->hooks.ctor && array_ti->hooks.ctor != flecs_default_ctor; bool dtor_hook_required = array_ti->hooks.dtor != NULL; bool move_hook_required = array_ti->hooks.move != NULL; bool copy_hook_required = array_ti->hooks.copy != NULL; ecs_flags32_t flags = array_ti->hooks.flags; ecs_type_hooks_t hooks = *ecs_get_hooks_id(world, component); if (hooks.lifecycle_ctx_free) { hooks.lifecycle_ctx_free(hooks.lifecycle_ctx); hooks.lifecycle_ctx_free = NULL; } if (ctor_hook_required || dtor_hook_required || move_hook_required || copy_hook_required) { ecs_rtt_array_ctx_t *rtt_ctx = ecs_os_malloc_t(ecs_rtt_array_ctx_t); rtt_ctx->type_info = array_ti; rtt_ctx->elem_count = array_info->count; if (hooks.lifecycle_ctx_free) { hooks.lifecycle_ctx_free(hooks.lifecycle_ctx); } hooks.lifecycle_ctx = rtt_ctx; hooks.lifecycle_ctx_free = flecs_rtt_free_lifecycle_array_ctx; } if (ctor_hook_required) { hooks.ctor = flecs_rtt_array_ctor; } if (dtor_hook_required) { hooks.dtor = flecs_rtt_array_dtor; } if (move_hook_required) { hooks.move = flecs_rtt_array_move; } if (copy_hook_required) { hooks.copy = flecs_rtt_array_copy; } hooks.flags |= flags; hooks.flags &= ECS_TYPE_HOOKS_ILLEGAL; ecs_set_hooks_id(world, component, &hooks); } /* * * RTT vector support * */ static void flecs_rtt_free_lifecycle_vector_ctx( void *ctx) { if (!ctx) { return; } ecs_os_free(ctx); } /* Generic vector constructor. Makes sure the vector structure is initialized to * 0 elements */ static void flecs_rtt_vector_ctor( void *ptr, int32_t count, const ecs_type_info_t *type_info) { ecs_rtt_vector_ctx_t *rtt_ctx = type_info->hooks.lifecycle_ctx; int i; for (i = 0; i < count; i++) { ecs_vec_t *vec = ECS_ELEM(ptr, type_info->size, i); ecs_vec_init(NULL, vec, rtt_ctx->type_info->size, 0); } } /* Generic vector destructor. It will invoke the destructor for each element of * the vector and finalize resources associated to the vector itself. */ static void flecs_rtt_vector_dtor( void *ptr, int32_t count, const ecs_type_info_t *type_info) { ecs_rtt_vector_ctx_t *rtt_ctx = type_info->hooks.lifecycle_ctx; ecs_xtor_t dtor = rtt_ctx->type_info->hooks.dtor; int i; for (i = 0; i < count; i++) { ecs_vec_t *vec = ECS_ELEM(ptr, type_info->size, i); int32_t num_elements = ecs_vec_count(vec); if (dtor && num_elements) { dtor(ecs_vec_first(vec), num_elements, rtt_ctx->type_info); } ecs_vec_fini(NULL, vec, rtt_ctx->type_info->size); } } /* Generic vector move hook. */ static void flecs_rtt_vector_move( void *dst_ptr, void *src_ptr, int32_t count, const ecs_type_info_t *type_info) { flecs_rtt_vector_dtor(dst_ptr, count, type_info); int i; for (i = 0; i < count; i++) { ecs_vec_t *src_vec = ECS_ELEM(src_ptr, type_info->size, i); ecs_vec_t *dst_vec = ECS_ELEM(dst_ptr, type_info->size, i); *dst_vec = *src_vec; src_vec->array = NULL; src_vec->count = 0; } } /* Generic vector copy hook. It makes a deep copy of vector contents */ static void flecs_rtt_vector_copy( void *dst_ptr, const void *src_ptr, int32_t count, const ecs_type_info_t *type_info) { ecs_rtt_vector_ctx_t *rtt_ctx = type_info->hooks.lifecycle_ctx; flecs_rtt_vector_dtor(dst_ptr, count, type_info); ecs_copy_t copy = rtt_ctx->type_info->hooks.copy ? rtt_ctx->type_info->hooks.copy : flecs_rtt_default_copy; ecs_xtor_t ctor = rtt_ctx->type_info->hooks.ctor ? rtt_ctx->type_info->hooks.ctor : flecs_default_ctor; ecs_xtor_t dtor = rtt_ctx->type_info->hooks.dtor; int i; for (i = 0; i < count; i++) { const ecs_vec_t *src_vec = ECS_ELEM(src_ptr, type_info->size, i); ecs_vec_t *dst_vec = ECS_ELEM(dst_ptr, type_info->size, i); int32_t src_count = ecs_vec_count(src_vec); int32_t dst_count = ecs_vec_count(dst_vec); if (dtor && dst_count) { dtor(ecs_vec_first(dst_vec), dst_count, rtt_ctx->type_info); } ecs_vec_set_count(NULL, dst_vec, rtt_ctx->type_info->size, src_count); ctor(ecs_vec_first(dst_vec), src_count, rtt_ctx->type_info); copy( ecs_vec_first(dst_vec), ecs_vec_first(src_vec), src_count, rtt_ctx->type_info); } } /* Generates and installs required hooks for managing the vector and underlying * type lifecycle. Vectors always have hooks because at the very least the * vector structure itself must be initialized/destroyed/copied/moved, even if * empty. */ static void flecs_rtt_init_default_hooks_vector( ecs_world_t *world, ecs_entity_t component) { const EcsVector *vector_info = ecs_get(world, component, EcsVector); ecs_assert(vector_info != NULL, ECS_INTERNAL_ERROR, NULL); const ecs_type_info_t *vector_ti = ecs_get_type_info(world, vector_info->type); ecs_rtt_vector_ctx_t *rtt_ctx = ecs_os_malloc_t(ecs_rtt_vector_ctx_t); rtt_ctx->type_info = vector_ti; ecs_type_hooks_t hooks = *ecs_get_hooks_id(world, component); if (hooks.lifecycle_ctx_free) { hooks.lifecycle_ctx_free(hooks.lifecycle_ctx); } hooks.lifecycle_ctx = rtt_ctx; hooks.lifecycle_ctx_free = flecs_rtt_free_lifecycle_vector_ctx; hooks.ctor = flecs_rtt_vector_ctor; hooks.dtor = flecs_rtt_vector_dtor; hooks.move = flecs_rtt_vector_move; hooks.copy = flecs_rtt_vector_copy; hooks.flags &= ECS_TYPE_HOOKS_ILLEGAL; ecs_set_hooks_id(world, component, &hooks); } void flecs_rtt_init_default_hooks( ecs_iter_t *it) { ecs_world_t *world = it->world; EcsType *type_field = ecs_field(it, EcsType, 0); int i; for (i = 0; i < it->count; i++) { EcsType *type = &type_field[i]; if (type->existing) { continue; /* non-rtt type. Ignore. */ } /* If a component is defined from reflection data, configure appropriate * default hooks. * - For trivial types, at least set a default constructor so memory is * zero-initialized * - For struct types, configure a hook that in turn calls hooks of * member types, if those member types have hooks defined themselves. * - For array types, configure a hook that in turn calls hooks for the * underlying type, for each element in the array. * - For vector types, configure hooks to manage the vector structure * itself, move the vector and deep-copy vector elements * */ ecs_entity_t component = it->entities[i]; const ecs_type_info_t *ti = ecs_get_type_info(world, component); if (ti) { if (type->kind == EcsStructType) { flecs_rtt_init_default_hooks_struct(world, component, ti); } else if (type->kind == EcsArrayType) { flecs_rtt_init_default_hooks_array(world, component); } else if (type->kind == EcsVectorType) { flecs_rtt_init_default_hooks_vector(world, component); } } /* Make sure there is at least a default constructor. This ensures that * a new component value does not contain uninitialized memory, which * could cause serializers to crash when for example inspecting string * fields. */ if (!ti || !ti->hooks.ctor) { ecs_set_hooks_id(world, component, &(ecs_type_hooks_t){ .ctor = flecs_default_ctor }); } } } #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. */ if (stage_index == 0) { 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); ecs_os_linc(&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_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_w_dbg_info( a, size, "ecs_script_node_t"); 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); ecs_vec_init_t(NULL, &result->components, ecs_id_t, 0); 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); ecs_vec_init_t(NULL, &result->components, ecs_id_t, 0); return result; } ecs_script_entity_t* flecs_script_insert_entity( ecs_script_parser_t *parser, const char *name, bool name_is_expr) { 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; if (name_is_expr) { parser->significant_newline = false; result->name_expr = (ecs_expr_node_t*) flecs_expr_interpolated_string(parser, name); if (!result->name_expr) { goto error; } parser->significant_newline = true; } 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; error: return NULL; } 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; id->first_sp = -1; id->second_sp = -1; } 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; result->sp = -1; 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; } ecs_script_for_range_t* flecs_script_insert_for_range( ecs_script_parser_t *parser) { ecs_script_scope_t *scope = parser->scope; ecs_assert(scope != NULL, ECS_INTERNAL_ERROR, NULL); ecs_script_for_range_t *result = flecs_ast_new( parser, ecs_script_for_range_t, EcsAstFor); result->scope = flecs_script_scope_new(parser); flecs_ast_append(parser, scope->stmts, ecs_script_for_range_t, result); return result; } #endif /** * @file addons/script/function.c * @brief Script function API. */ #ifdef FLECS_SCRIPT static void ecs_script_params_free(ecs_vec_t *params) { ecs_script_parameter_t *array = ecs_vec_first(params); int32_t i, count = ecs_vec_count(params); for (i = 0; i < count; i ++) { /* Safe, component owns string */ ecs_os_free(ECS_CONST_CAST(char*, array[i].name)); } ecs_vec_fini_t(NULL, params, ecs_script_parameter_t); } static ECS_MOVE(EcsScriptConstVar, dst, src, { if (dst->type_info->hooks.dtor) { dst->type_info->hooks.dtor(dst->value.ptr, 1, dst->type_info); } ecs_os_free(dst->value.ptr); *dst = *src; src->value.ptr = NULL; src->value.type = 0; src->type_info = NULL; }) static ECS_DTOR(EcsScriptConstVar, ptr, { if (ptr->type_info->hooks.dtor) { ptr->type_info->hooks.dtor(ptr->value.ptr, 1, ptr->type_info); } ecs_os_free(ptr->value.ptr); }) static ECS_MOVE(EcsScriptFunction, dst, src, { ecs_script_params_free(&dst->params); *dst = *src; ecs_os_zeromem(src); }) static ECS_DTOR(EcsScriptFunction, ptr, { ecs_script_params_free(&ptr->params); }) static ECS_MOVE(EcsScriptMethod, dst, src, { ecs_script_params_free(&dst->params); *dst = *src; ecs_os_zeromem(src); }) static ECS_DTOR(EcsScriptMethod, ptr, { ecs_script_params_free(&ptr->params); }) ecs_entity_t ecs_const_var_init( ecs_world_t *world, ecs_const_var_desc_t *desc) { flecs_poly_assert(world, ecs_world_t); ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc->name != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc->type != 0, ECS_INVALID_PARAMETER, NULL); ecs_check(desc->value != NULL, ECS_INVALID_PARAMETER, NULL); const ecs_type_info_t *ti = ecs_get_type_info(world, desc->type); ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "ecs_const_var_desc_t::type is not a valid type"); ecs_entity_t result = ecs_entity(world, { .name = desc->name, .parent = desc->parent }); if (!result) { goto error; } EcsScriptConstVar *v = ecs_ensure(world, result, EcsScriptConstVar); v->value.ptr = ecs_os_malloc(ti->size); v->value.type = desc->type; v->type_info = ti; ecs_value_init(world, desc->type, v->value.ptr); ecs_value_copy(world, desc->type, v->value.ptr, desc->value); ecs_modified(world, result, EcsScriptConstVar); return result; error: return 0; } ecs_entity_t ecs_function_init( ecs_world_t *world, const ecs_function_desc_t *desc) { flecs_poly_assert(world, ecs_world_t); ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc->name != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc->callback != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc->return_type != 0, ECS_INVALID_PARAMETER, NULL); ecs_entity_t result = ecs_entity(world, { .name = desc->name, .parent = desc->parent }); if (!result) { goto error; } EcsScriptFunction *f = ecs_ensure(world, result, EcsScriptFunction); f->return_type = desc->return_type; f->callback = desc->callback; f->ctx = desc->ctx; int32_t i; for (i = 0; i < FLECS_SCRIPT_FUNCTION_ARGS_MAX; i ++) { if (!desc->params[i].name) { break; } if (!i) { ecs_vec_init_t(NULL, &f->params, ecs_script_parameter_t, 0); } ecs_script_parameter_t *p = ecs_vec_append_t( NULL, &f->params, ecs_script_parameter_t); p->type = desc->params[i].type; p->name = ecs_os_strdup(desc->params[i].name); } ecs_modified(world, result, EcsScriptFunction); return result; error: return 0; } ecs_entity_t ecs_method_init( ecs_world_t *world, const ecs_function_desc_t *desc) { flecs_poly_assert(world, ecs_world_t); ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc->name != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc->callback != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc->parent != 0, ECS_INVALID_PARAMETER, NULL); ecs_check(desc->return_type != 0, ECS_INVALID_PARAMETER, NULL); ecs_entity_t result = ecs_entity(world, { .name = desc->name, .parent = desc->parent }); if (!result) { goto error; } EcsScriptMethod *f = ecs_ensure(world, result, EcsScriptMethod); f->return_type = desc->return_type; f->callback = desc->callback; f->ctx = desc->ctx; int32_t i; for (i = 0; i < FLECS_SCRIPT_FUNCTION_ARGS_MAX; i ++) { if (!desc->params[i].name) { break; } if (!i) { ecs_vec_init_t(NULL, &f->params, ecs_script_parameter_t, 0); } ecs_script_parameter_t *p = ecs_vec_append_t( NULL, &f->params, ecs_script_parameter_t); p->type = desc->params[i].type; p->name = ecs_os_strdup(desc->params[i].name); } ecs_modified(world, result, EcsScriptMethod); return result; error: return 0; } void flecs_function_import( ecs_world_t *world) { ecs_set_name_prefix(world, "EcsScript"); ECS_COMPONENT_DEFINE(world, EcsScriptConstVar); ECS_COMPONENT_DEFINE(world, EcsScriptFunction); ECS_COMPONENT_DEFINE(world, EcsScriptMethod); ecs_struct(world, { .entity = ecs_id(EcsScriptFunction), .members = { { .name = "return_type", .type = ecs_id(ecs_entity_t) } } }); ecs_struct(world, { .entity = ecs_id(EcsScriptMethod), .members = { { .name = "return_type", .type = ecs_id(ecs_entity_t) } } }); ecs_set_hooks(world, EcsScriptConstVar, { .ctor = flecs_default_ctor, .dtor = ecs_dtor(EcsScriptConstVar), .move = ecs_move(EcsScriptConstVar), .flags = ECS_TYPE_HOOK_COPY_ILLEGAL }); ecs_set_hooks(world, EcsScriptFunction, { .ctor = flecs_default_ctor, .dtor = ecs_dtor(EcsScriptFunction), .move = ecs_move(EcsScriptFunction), .flags = ECS_TYPE_HOOK_COPY_ILLEGAL }); ecs_set_hooks(world, EcsScriptMethod, { .ctor = flecs_default_ctor, .dtor = ecs_dtor(EcsScriptMethod), .move = ecs_move(EcsScriptMethod), .flags = ECS_TYPE_HOOK_COPY_ILLEGAL }); flecs_script_register_builtin_functions(world); } #endif /** * @file addons/script/builtin_functions.c * @brief Flecs functions for flecs script. */ #ifdef FLECS_SCRIPT static void flecs_meta_entity_name( const ecs_function_ctx_t *ctx, int32_t argc, const ecs_value_t *argv, ecs_value_t *result) { (void)argc; ecs_entity_t entity = *(ecs_entity_t*)argv[0].ptr; *(char**)result->ptr = ecs_os_strdup(ecs_get_name(ctx->world, entity)); } static void flecs_meta_entity_path( const ecs_function_ctx_t *ctx, int32_t argc, const ecs_value_t *argv, ecs_value_t *result) { (void)argc; ecs_entity_t entity = *(ecs_entity_t*)argv[0].ptr; *(char**)result->ptr = ecs_get_path(ctx->world, entity); } static void flecs_meta_entity_parent( const ecs_function_ctx_t *ctx, int32_t argc, const ecs_value_t *argv, ecs_value_t *result) { (void)argc; ecs_entity_t entity = *(ecs_entity_t*)argv[0].ptr; *(ecs_entity_t*)result->ptr = ecs_get_parent(ctx->world, entity); } static void flecs_meta_entity_has( const ecs_function_ctx_t *ctx, int32_t argc, const ecs_value_t *argv, ecs_value_t *result) { (void)argc; ecs_entity_t entity = *(ecs_entity_t*)argv[0].ptr; ecs_id_t id = *(ecs_id_t*)argv[1].ptr; *(ecs_bool_t*)result->ptr = ecs_has_id(ctx->world, entity, id); } static void flecs_meta_core_pair( const ecs_function_ctx_t *ctx, int32_t argc, const ecs_value_t *argv, ecs_value_t *result) { (void)argc; (void)ctx; ecs_entity_t first = *(ecs_entity_t*)argv[0].ptr; ecs_entity_t second = *(ecs_entity_t*)argv[1].ptr; *(ecs_id_t*)result->ptr = ecs_pair(first, second); } #ifdef FLECS_DOC #define FLECS_DOC_FUNC(name)\ static\ void flecs_meta_entity_doc_##name(\ const ecs_function_ctx_t *ctx,\ int32_t argc,\ const ecs_value_t *argv,\ ecs_value_t *result)\ {\ (void)argc;\ ecs_entity_t entity = *(ecs_entity_t*)argv[0].ptr;\ *(char**)result->ptr = \ ecs_os_strdup(ecs_doc_get_##name(ctx->world, entity));\ } FLECS_DOC_FUNC(name) FLECS_DOC_FUNC(uuid) FLECS_DOC_FUNC(brief) FLECS_DOC_FUNC(detail) FLECS_DOC_FUNC(link) FLECS_DOC_FUNC(color) #undef FLECS_DOC_FUNC static void flecs_script_register_builtin_doc_functions( ecs_world_t *world) { { ecs_entity_t m = ecs_method(world, { .name = "doc_name", .parent = ecs_id(ecs_entity_t), .return_type = ecs_id(ecs_string_t), .callback = flecs_meta_entity_doc_name }); ecs_doc_set_brief(world, m, "Returns entity doc name"); } { ecs_entity_t m = ecs_method(world, { .name = "doc_uuid", .parent = ecs_id(ecs_entity_t), .return_type = ecs_id(ecs_string_t), .callback = flecs_meta_entity_doc_uuid }); ecs_doc_set_brief(world, m, "Returns entity doc uuid"); } { ecs_entity_t m = ecs_method(world, { .name = "doc_brief", .parent = ecs_id(ecs_entity_t), .return_type = ecs_id(ecs_string_t), .callback = flecs_meta_entity_doc_brief }); ecs_doc_set_brief(world, m, "Returns entity doc brief description"); } { ecs_entity_t m = ecs_method(world, { .name = "doc_detail", .parent = ecs_id(ecs_entity_t), .return_type = ecs_id(ecs_string_t), .callback = flecs_meta_entity_doc_detail }); ecs_doc_set_brief(world, m, "Returns entity doc detailed description"); } { ecs_entity_t m = ecs_method(world, { .name = "doc_link", .parent = ecs_id(ecs_entity_t), .return_type = ecs_id(ecs_string_t), .callback = flecs_meta_entity_doc_link }); ecs_doc_set_brief(world, m, "Returns entity doc link"); } { ecs_entity_t m = ecs_method(world, { .name = "doc_color", .parent = ecs_id(ecs_entity_t), .return_type = ecs_id(ecs_string_t), .callback = flecs_meta_entity_doc_color }); ecs_doc_set_brief(world, m, "Returns entity doc color"); } } #else static void flecs_script_register_builtin_doc_functions( ecs_world_t *world) { (void)world; } #endif void flecs_script_register_builtin_functions( ecs_world_t *world) { { ecs_entity_t m = ecs_method(world, { .name = "name", .parent = ecs_id(ecs_entity_t), .return_type = ecs_id(ecs_string_t), .callback = flecs_meta_entity_name }); ecs_doc_set_brief(world, m, "Returns entity name"); } { ecs_entity_t m = ecs_method(world, { .name = "path", .parent = ecs_id(ecs_entity_t), .return_type = ecs_id(ecs_string_t), .callback = flecs_meta_entity_path }); ecs_doc_set_brief(world, m, "Returns entity path"); } { ecs_entity_t m = ecs_method(world, { .name = "parent", .parent = ecs_id(ecs_entity_t), .return_type = ecs_id(ecs_entity_t), .callback = flecs_meta_entity_parent }); ecs_doc_set_brief(world, m, "Returns entity parent"); } { ecs_entity_t m = ecs_method(world, { .name = "has", .parent = ecs_id(ecs_entity_t), .params = { { .name = "component", .type = ecs_id(ecs_id_t) } }, .return_type = ecs_id(ecs_bool_t), .callback = flecs_meta_entity_has }); ecs_doc_set_brief(world, m, "Returns whether entity has component"); } { ecs_entity_t m = ecs_function(world, { .name = "pair", .parent = ecs_entity(world, { .name = "core"}), .params = { { .name = "first", .type = ecs_id(ecs_entity_t) }, { .name = "second", .type = ecs_id(ecs_entity_t) } }, .return_type = ecs_id(ecs_id_t), .callback = flecs_meta_core_pair }); ecs_doc_set_brief(world, m, "Returns a pair identifier"); } flecs_script_register_builtin_doc_functions(world); } #endif /** * @file addons/script/functions_math.c * @brief Math functions for flecs script. */ #ifdef FLECS_SCRIPT_MATH #include typedef struct ecs_script_rng_t { uint64_t x; /* Current state (initialize with seed) */ uint64_t w; /* Weyl sequence increment */ uint64_t s; /* Constant for Weyl sequence */ int32_t refcount; /* Necessary as flecs script doesn't have ref types */ bool initialized; } ecs_script_rng_t; static ecs_script_rng_t* flecs_script_rng_new(void) { ecs_script_rng_t *result = ecs_os_calloc_t(ecs_script_rng_t); result->x = 0; result->w = 0; result->s = 0xb5ad4eceda1ce2a9; /* Constant for the Weyl sequence */ result->refcount = 1; result->initialized = false; return result; } static void flecs_script_rng_keep(ecs_script_rng_t *rng) { if (!rng) { return; } rng->refcount ++; } static void flecs_script_rng_free(ecs_script_rng_t *rng) { if (!rng) { return; } ecs_assert(rng->refcount > 0, ECS_INTERNAL_ERROR, NULL); if (!--rng->refcount) { ecs_os_free(rng); } } static uint64_t flecs_script_rng_next(ecs_script_rng_t *rng) { rng->x *= rng->x; rng->x += (rng->w += rng->s); rng->x = (rng->x >> 32) | (rng->x << 32); return rng->x; } ECS_COMPONENT_DECLARE(EcsScriptRng); static ECS_CTOR(EcsScriptRng, ptr, { ptr->seed = 0; ptr->impl = flecs_script_rng_new(); }) static ECS_COPY(EcsScriptRng, dst, src, { flecs_script_rng_keep(src->impl); if (dst->impl != src->impl) { flecs_script_rng_free(dst->impl); } dst->seed = src->seed; dst->impl = src->impl; }) static ECS_MOVE(EcsScriptRng, dst, src, { flecs_script_rng_free(dst->impl); dst->seed = src->seed; dst->impl = src->impl; src->impl = NULL; }) static ECS_DTOR(EcsScriptRng, ptr, { flecs_script_rng_free(ptr->impl); }) void flecs_script_rng_get_float( const ecs_function_ctx_t *ctx, int32_t argc, const ecs_value_t *argv, ecs_value_t *result) { (void)ctx; (void)argc; EcsScriptRng *rng = argv[0].ptr; ecs_assert(rng->impl != NULL, ECS_INTERNAL_ERROR, NULL); ecs_script_rng_t *impl = rng->impl; if (!impl->initialized) { impl->x = rng->seed; impl->initialized = true; } uint64_t x = flecs_script_rng_next(rng->impl); double max = *(double*)argv[1].ptr; double *r = result->ptr; if (ECS_EQZERO(max)) { ecs_err("flecs.script.math.Rng.f(): invalid division by zero"); } else { *r = (double)x / ((double)UINT64_MAX / max); } } void flecs_script_rng_get_uint( const ecs_function_ctx_t *ctx, int32_t argc, const ecs_value_t *argv, ecs_value_t *result) { (void)ctx; (void)argc; EcsScriptRng *rng = argv[0].ptr; ecs_assert(rng->impl != NULL, ECS_INTERNAL_ERROR, NULL); ecs_script_rng_t *impl = rng->impl; if (!impl->initialized) { impl->x = rng->seed; impl->initialized = true; } uint64_t x = flecs_script_rng_next(rng->impl); uint64_t max = *(uint64_t*)argv[1].ptr; uint64_t *r = result->ptr; if (!max) { ecs_err("flecs.script.math.Rng.u(): invalid division by zero"); } else { *r = x % max; } } #define FLECS_MATH_FUNC_F64(name, ...)\ static\ void flecs_math_##name(\ const ecs_function_ctx_t *ctx,\ int32_t argc,\ const ecs_value_t *argv,\ ecs_value_t *result)\ {\ (void)ctx;\ (void)argc;\ ecs_assert(argc == 1, ECS_INTERNAL_ERROR, NULL);\ double x = *(double*)argv[0].ptr;\ *(double*)result->ptr = __VA_ARGS__;\ } #define FLECS_MATH_FUNC_F64_F64(name, ...)\ static\ void flecs_math_##name(\ const ecs_function_ctx_t *ctx,\ int32_t argc,\ const ecs_value_t *argv,\ ecs_value_t *result)\ {\ (void)ctx;\ (void)argc;\ ecs_assert(argc == 2, ECS_INTERNAL_ERROR, NULL);\ double x = *(double*)argv[0].ptr;\ double y = *(double*)argv[1].ptr;\ *(double*)result->ptr = __VA_ARGS__;\ } #define FLECS_MATH_FUNC_F64_I32(name, ...)\ static\ void flecs_math_##name(\ const ecs_function_ctx_t *ctx,\ int32_t argc,\ const ecs_value_t *argv,\ ecs_value_t *result)\ {\ (void)ctx;\ (void)argc;\ ecs_assert(argc == 2, ECS_INTERNAL_ERROR, NULL);\ double x = *(double*)argv[0].ptr;\ ecs_i32_t y = *(ecs_i32_t*)argv[1].ptr;\ *(double*)result->ptr = __VA_ARGS__;\ } #define FLECS_MATH_FUNC_DEF_F64(_name, brief)\ {\ ecs_entity_t f = ecs_function(world, {\ .name = #_name,\ .parent = ecs_id(FlecsScriptMath),\ .return_type = ecs_id(ecs_f64_t),\ .params = {{ .name = "x", .type = ecs_id(ecs_f64_t) }},\ .callback = flecs_math_##_name\ });\ ecs_doc_set_brief(world, f, brief);\ } #define FLECS_MATH_FUNC_DEF_F64_F64(_name, brief)\ {\ ecs_entity_t f = ecs_function(world, {\ .name = #_name,\ .parent = ecs_id(FlecsScriptMath),\ .return_type = ecs_id(ecs_f64_t),\ .params = {\ { .name = "x", .type = ecs_id(ecs_f64_t) },\ { .name = "y", .type = ecs_id(ecs_f64_t) }\ },\ .callback = flecs_math_##_name\ });\ ecs_doc_set_brief(world, f, brief);\ } #define FLECS_MATH_FUNC_DEF_F64_F32(_name, brief)\ {\ ecs_entity_t f = ecs_function(world, {\ .name = #_name,\ .parent = ecs_id(FlecsScriptMath),\ .return_type = ecs_id(ecs_f64_t),\ .params = {\ { .name = "x", .type = ecs_id(ecs_f64_t) },\ { .name = "y", .type = ecs_id(ecs_i32_t) }\ },\ .callback = flecs_math_##_name\ });\ ecs_doc_set_brief(world, f, brief);\ } /* Trigonometric functions */ FLECS_MATH_FUNC_F64(cos, cos(x)) FLECS_MATH_FUNC_F64(sin, sin(x)) FLECS_MATH_FUNC_F64(tan, tan(x)) FLECS_MATH_FUNC_F64(acos, acos(x)) FLECS_MATH_FUNC_F64(asin, asin(x)) FLECS_MATH_FUNC_F64(atan, atan(x)) FLECS_MATH_FUNC_F64_F64(atan2, atan2(x, y)) /* Hyperbolic functions */ FLECS_MATH_FUNC_F64(cosh, cosh(x)) FLECS_MATH_FUNC_F64(sinh, sinh(x)) FLECS_MATH_FUNC_F64(tanh, tanh(x)) FLECS_MATH_FUNC_F64(acosh, acosh(x)) FLECS_MATH_FUNC_F64(asinh, asinh(x)) FLECS_MATH_FUNC_F64(atanh, atanh(x)) /* Exponential and logarithmic functions */ FLECS_MATH_FUNC_F64(exp, exp(x)) FLECS_MATH_FUNC_F64_I32(ldexp, ldexp(x, y)) FLECS_MATH_FUNC_F64(log, log(x)) FLECS_MATH_FUNC_F64(log10, log10(x)) FLECS_MATH_FUNC_F64(exp2, exp2(x)) FLECS_MATH_FUNC_F64(log2, log2(x)) /* Power functions */ FLECS_MATH_FUNC_F64_F64(pow, pow(x, y)) FLECS_MATH_FUNC_F64(sqrt, sqrt(x)) FLECS_MATH_FUNC_F64(sqr, x * x) /* Rounding functions */ FLECS_MATH_FUNC_F64(ceil, ceil(x)) FLECS_MATH_FUNC_F64(floor, floor(x)) FLECS_MATH_FUNC_F64(round, round(x)) FLECS_MATH_FUNC_F64(abs, fabs(x)) FLECS_API void FlecsScriptMathImport( ecs_world_t *world) { ECS_MODULE(world, FlecsScriptMath); ECS_IMPORT(world, FlecsScript); /* Constants */ double E = 2.71828182845904523536028747135266250; ecs_const_var(world, { .name = "E", .parent = ecs_id(FlecsScriptMath), .type = ecs_id(ecs_f64_t), .value = &E }); double PI = 3.14159265358979323846264338327950288; ecs_const_var(world, { .name = "PI", .parent = ecs_id(FlecsScriptMath), .type = ecs_id(ecs_f64_t), .value = &PI }); /* Trigonometric functions */ FLECS_MATH_FUNC_DEF_F64(cos, "Compute cosine"); FLECS_MATH_FUNC_DEF_F64(sin, "Compute sine"); FLECS_MATH_FUNC_DEF_F64(tan, "Compute tangent"); FLECS_MATH_FUNC_DEF_F64(acos, "Compute arc cosine"); FLECS_MATH_FUNC_DEF_F64(asin, "Compute arc sine"); FLECS_MATH_FUNC_DEF_F64(atan, "Compute arc tangent"); FLECS_MATH_FUNC_DEF_F64_F64(atan2, "Compute arc tangent with two parameters"); /* Hyperbolic functions */ FLECS_MATH_FUNC_DEF_F64(cosh, "Compute hyperbolic cosine"); FLECS_MATH_FUNC_DEF_F64(sinh, "Compute hyperbolic sine"); FLECS_MATH_FUNC_DEF_F64(tanh, "Compute hyperbolic tangent"); FLECS_MATH_FUNC_DEF_F64(acosh, "Compute area hyperbolic cosine"); FLECS_MATH_FUNC_DEF_F64(asinh, "Compute area hyperbolic sine"); FLECS_MATH_FUNC_DEF_F64(atanh, "Compute area hyperbolic tangent"); /* Exponential and logarithmic functions */ FLECS_MATH_FUNC_DEF_F64(exp, "Compute exponential function"); FLECS_MATH_FUNC_DEF_F64_F32(ldexp, "Generate value from significant and exponent"); FLECS_MATH_FUNC_DEF_F64(log, "Compute natural logarithm"); FLECS_MATH_FUNC_DEF_F64(log10, "Compute common logarithm"); FLECS_MATH_FUNC_DEF_F64(exp2, "Compute binary exponential function"); FLECS_MATH_FUNC_DEF_F64(log2, "Compute binary logarithm"); /* Power functions */ FLECS_MATH_FUNC_DEF_F64_F64(pow, "Raise to power"); FLECS_MATH_FUNC_DEF_F64(sqrt, "Compute square root"); FLECS_MATH_FUNC_DEF_F64(sqr, "Compute square"); /* Rounding functions */ FLECS_MATH_FUNC_DEF_F64(ceil, "Round up value"); FLECS_MATH_FUNC_DEF_F64(floor, "Round down value"); FLECS_MATH_FUNC_DEF_F64(round, "Round to nearest"); FLECS_MATH_FUNC_DEF_F64(abs, "Compute absolute value"); ecs_set_name_prefix(world, "EcsScript"); ECS_COMPONENT_DEFINE(world, EcsScriptRng); ecs_set_hooks(world, EcsScriptRng, { .ctor = ecs_ctor(EcsScriptRng), .move = ecs_move(EcsScriptRng), .copy = ecs_copy(EcsScriptRng), .dtor = ecs_dtor(EcsScriptRng), }); ecs_struct(world, { .entity = ecs_id(EcsScriptRng), .members = { { .name = "seed", .type = ecs_id(ecs_u64_t) } } }); ecs_method(world, { .parent = ecs_id(EcsScriptRng), .name = "f", .return_type = ecs_id(ecs_f64_t), .params = { { .name = "max", .type = ecs_id(ecs_f64_t) } }, .callback = flecs_script_rng_get_float }); ecs_method(world, { .parent = ecs_id(EcsScriptRng), .name = "u", .return_type = ecs_id(ecs_u64_t), .params = { { .name = "max", .type = ecs_id(ecs_u64_t) } }, .callback = flecs_script_rng_get_uint }); } #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_tokenizer_t _tokenizer;\ ecs_os_zeromem(&_tokenizer);\ _tokenizer.tokens = _tokenizer.stack.tokens;\ ecs_script_tokenizer_t *tokenizer = &_tokenizer; #define ParserEnd\ Error("unexpected end of rule (parser error)");\ error:\ return NULL /* Get token */ #define Token(n) (tokenizer->tokens[n].value) /* Push/pop token frame (allows token stack reuse in recursive functions) */ #define TokenFramePush() \ tokenizer->tokens = &tokenizer->stack.tokens[tokenizer->stack.count]; #define TokenFramePop() \ tokenizer->tokens = tokenizer->stack.tokens; /* Error */ #define Error(...)\ ecs_parser_error(parser->script->pub.name, parser->script->pub.code,\ (pos - parser->script->pub.code) - 1, __VA_ARGS__);\ goto error /* Warning */ #define Warning(...)\ ecs_parser_warning(parser->script->pub.name, parser->script->pub.code,\ (pos - parser->script->pub.code) - 1, __VA_ARGS__);\ /* Parse expression */ #define Expr(until, ...)\ {\ ecs_expr_node_t *EXPR = NULL;\ if (until == '}' || until == ']') {\ pos --;\ if (until == '}') {\ ecs_assert(pos[0] == '{', ECS_INTERNAL_ERROR, NULL);\ } else if (until == ']') {\ ecs_assert(pos[0] == '[', ECS_INTERNAL_ERROR, NULL);\ }\ }\ parser->significant_newline = false;\ if (!(pos = flecs_script_parse_expr(parser, pos, 0, &EXPR))) {\ goto error;\ }\ parser->significant_newline = true;\ __VA_ARGS__\ } /* Parse initializer */ #define Initializer(until, ...)\ {\ ecs_expr_node_t *INITIALIZER = NULL;\ ecs_expr_initializer_t *_initializer = NULL;\ if (until != '\n') {\ parser->significant_newline = false;\ }\ if (!(pos = flecs_script_parse_initializer(\ parser, pos, until, &_initializer))) \ {\ flecs_expr_visit_free(\ &parser->script->pub, (ecs_expr_node_t*)_initializer);\ goto error;\ }\ parser->significant_newline = true;\ if (pos[0] != until) {\ Error("expected '%c'", until);\ }\ INITIALIZER = (ecs_expr_node_t*)_initializer;\ pos ++;\ __VA_ARGS__\ } /* Parse token until character */ #define Until(until, ...)\ {\ ecs_assert(tokenizer->stack.count < 256, ECS_INTERNAL_ERROR, NULL);\ ecs_script_token_t *t = &tokenizer->stack.tokens[tokenizer->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(tokenizer->stack.count < 256, ECS_INTERNAL_ERROR, NULL);\ ecs_script_token_t *t = &tokenizer->stack.tokens[tokenizer->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))) {\ tokenizer->stack.tokens[tokenizer->stack.count ++] = lookahead_token;\ switch(lookahead_token.kind) {\ __VA_ARGS__\ default:\ tokenizer->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__\ }\ )\ if (pos != lookahead) {\ 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__\ }\ )\ if (pos != lookahead) {\ 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 = tokenizer->stack.count;\ do {\ tokenizer->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), false); } 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 ) Initializer(')', ecs_script_component_t *component = flecs_script_insert_component(parser, Token(0)); component->node.kind = EcsAstWithComponent; component->expr = INITIALIZER; 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 ) Initializer(')', ecs_script_component_t *component = flecs_script_insert_pair_component(parser, Token(1), Token(3)); component->node.kind = EcsAstWithComponent; component->expr = INITIALIZER; 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; Initializer(')', entity->kind_w_expr = true; Scope(entity->scope, ecs_script_component_t *component = flecs_script_insert_component(parser, kind); component->expr = INITIALIZER; ) 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_if_stmt( ecs_script_parser_t *parser, const char *pos) { ParserBegin; // if expr Expr('\0', // if expr { Parse_1('{', { ecs_script_if_t *stmt = flecs_script_insert_if(parser); stmt->expr = EXPR; pos = flecs_script_scope(parser, stmt->if_true, pos); if (!pos) { goto error; } // if expr { } else LookAhead_1(EcsTokKeywordElse, pos = lookahead; Parse( // if expr { } else if case EcsTokKeywordIf: { Scope(stmt->if_false, return flecs_script_if_stmt(parser, pos); ) } // if expr { } else\n if case EcsTokNewline: { Parse_1(EcsTokKeywordIf, Scope(stmt->if_false, return flecs_script_if_stmt(parser, pos); ) ) } // if expr { } else { case '{': { return flecs_script_scope(parser, stmt->if_false, pos); } ) ) EndOfRule; }); ) ParserEnd; } static const char* flecs_script_parse_var( ecs_script_parser_t *parser, const char *pos, ecs_script_tokenizer_t *tokenizer, bool is_prop) { Parse_1(EcsTokIdentifier, ecs_script_var_node_t *var = flecs_script_insert_var( parser, Token(1)); var->node.kind = is_prop ? EcsAstProp : 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; Initializer('}', var->expr = INITIALIZER; EndOfRule; ) ) // const color = Color: expr\n Initializer('\n', var->expr = INITIALIZER; EndOfRule; ) ) // const PI = expr\n Expr('\n', Warning("'%s var = expr' syntax is deprecated" ", use '%s var: expr' instead", is_prop ? "prop" : "const", is_prop ? "prop" : "const"); var->expr = EXPR; EndOfRule; ) } case ':': { // const PI: expr\n Expr('\n', var->expr = EXPR; EndOfRule; ) } ) ) error: return NULL; } /* Parse a single statement */ static const char* flecs_script_stmt( ecs_script_parser_t *parser, const char *pos) { ParserBegin; bool name_is_expr_0 = false; bool name_is_expr_1 = false; Parse( case EcsTokIdentifier: goto identifier; case EcsTokString: goto string_name; case '{': goto anonymous_entity; case '(': goto paren; case '@': goto annotation; case EcsTokKeywordWith: goto with_stmt; case EcsTokKeywordModule: goto module_stmt; case EcsTokKeywordUsing: goto using_stmt; case EcsTokKeywordTemplate: goto template_stmt; case EcsTokKeywordProp: goto prop_var; case EcsTokKeywordConst: goto const_var; case EcsTokKeywordIf: goto if_stmt; case EcsTokKeywordFor: goto for_stmt; EcsTokEndOfStatement: EndOfRule; ); anonymous_entity: { return flecs_script_scope(parser, flecs_script_insert_entity(parser, "_", false)->scope, pos); } string_name: /* If this is an interpolated string, we need to evaluate it as expression * at evaluation time. Otherwise we can just use the string as name. The * latter is useful if an entity name contains special characters that are * not allowed in identifier tokens. */ if (flecs_string_is_interpolated(Token(0))) { name_is_expr_0 = true; } 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), name_is_expr_0)->scope, pos); } // Red, case ',': { if (name_is_expr_0) { Error("expression not allowed as entity name here"); } flecs_script_insert_entity(parser, Token(0), false); 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), name_is_expr_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; } // Spaceship "enterprise" case EcsTokString: { goto identifier_string; } ) } 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", tokenizer->tokens[0].value); } } else { if (!flecs_script_insert_tag(parser, Token(0))) { Error( "invalid context for tag '%s': must be part of entity", tokenizer->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_1(EcsTokIdentifier, flecs_script_insert_using(parser, Token(1)); Parse( EcsTokEndOfStatement: 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: return flecs_script_parse_var(parser, pos, tokenizer, true); } // const const_var: { // const color return flecs_script_parse_var(parser, pos, tokenizer, false); } // if if_stmt: { return flecs_script_if_stmt(parser, pos); } // for for_stmt: { // for i Parse_2(EcsTokIdentifier, EcsTokKeywordIn, { Expr(0, { ecs_expr_node_t *from = EXPR; Parse_1(EcsTokRange, { Expr(0, { ecs_expr_node_t *to = EXPR; ecs_script_for_range_t *stmt = flecs_script_insert_for_range(parser); stmt->loop_var = Token(1); stmt->from = from; stmt->to = to; Parse_1('{', { return flecs_script_scope(parser, stmt->scope, pos); }); }); }); }); }); } // ( 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 ':': { // Use lookahead so that expression parser starts at "match" LookAhead_1(EcsTokKeywordMatch, { // (Eats, Apples): match expr Expr('\n', { ecs_script_component_t *comp = flecs_script_insert_pair_component( parser, Token(1), Token(3)); comp->expr = EXPR; EndOfRule; }) }) // (Eats, Apples): { Parse_1('{', { // (Eats, Apples): { expr } Initializer('}', ecs_script_component_t *comp = flecs_script_insert_pair_component( parser, Token(1), Token(3)); comp->expr = INITIALIZER; 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 = EXPR; 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 = EXPR; EndOfRule; }) ) } ) } ) } // Position: identifier_colon: { { // Position: { LookAhead_1('{', pos = lookahead; goto component_expr_scope; ) } { // Position: [ LookAhead_1('[', pos = lookahead; goto component_expr_collection; ) } { // Position: match LookAhead_1(EcsTokKeywordMatch, goto component_expr_match; ) } // enterprise : SpaceShip Parse_1(EcsTokIdentifier, { ecs_script_entity_t *entity = flecs_script_insert_entity( parser, Token(0), name_is_expr_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), name_is_expr_0); // x = Position: LookAhead_2(EcsTokIdentifier, ':', pos = lookahead; // Use lookahead so that expression parser starts at "match" LookAhead_1(EcsTokKeywordMatch, { // (Eats, Apples): match expr Expr('\n', { ecs_script_component_t *comp = flecs_script_insert_pair_component( parser, Token(1), Token(3)); comp->expr = EXPR; EndOfRule; }) }) // 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 = EXPR; ) // x = Position: {expr}\n Parse( EcsTokEndOfStatement: EndOfRule; ) ) }) ) // x = f32\n Initializer('\n', Scope(entity->scope, ecs_script_default_component_t *comp = flecs_script_insert_default_component(parser); comp->expr = INITIALIZER; ) EndOfRule; ) } // Spaceship enterprise identifier_string: { if (flecs_string_is_interpolated(Token(1))) { name_is_expr_1 = true; } } identifier_identifier: { ecs_script_entity_t *entity = flecs_script_insert_entity( parser, Token(1), name_is_expr_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() Initializer(')', Parse( // SpaceShip(expr)\n EcsTokEndOfStatement: { ecs_script_entity_t *entity = flecs_script_insert_entity( parser, NULL, false); Scope(entity->scope, ecs_script_component_t *comp = flecs_script_insert_component(parser, Token(0)); comp->expr = INITIALIZER; ) EndOfRule; } // SpaceShip(expr) { case '{': { ecs_script_entity_t *entity = flecs_script_insert_entity( parser, NULL, false); Scope(entity->scope, ecs_script_component_t *comp = flecs_script_insert_component(parser, Token(0)); comp->expr = INITIALIZER; ) 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 = EXPR; EndOfRule; }) } // Points: [ component_expr_collection: { // Position: [expr] Expr(']', { ecs_script_component_t *comp = flecs_script_insert_component( parser, Token(0)); comp->expr = EXPR; comp->is_collection = true; EndOfRule; }) } // Position: match component_expr_match: { // Position: match expr Expr('\n', { ecs_script_component_t *comp = flecs_script_insert_component( parser, Token(0)); comp->expr = EXPR; EndOfRule; }) } ParserEnd; } /* Parse script */ ecs_script_t* ecs_script_parse( ecs_world_t *world, const char *name, const char *code, const ecs_script_eval_desc_t *desc) { (void)desc; /* Will be used in future to expand type checking features */ 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_w_dbg_info( &impl->allocator, impl->token_buffer_size, "token buffer"); 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); impl->token_remaining = parser.token_cur; 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, .merge_variable_members = true }; 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 (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); ECS_COMPONENT_DECLARE(EcsScriptConstVar); ECS_COMPONENT_DECLARE(EcsScriptFunction); ECS_COMPONENT_DECLARE(EcsScriptMethod); 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) { if (!instance) { ecs_delete_with(world, ecs_pair_t(EcsScript, script)); } else { ecs_defer_begin(world); ecs_iter_t it = ecs_children(world, instance); while (ecs_children_next(&it)) { if (ecs_table_has_id(world, it.table, ecs_pair(EcsScriptTemplate, script))) { int32_t i, count = it.count; for (i = 0; i < count; i ++) { ecs_delete(world, it.entities[i]); } } } ecs_defer_end(world); } } int ecs_script_run( ecs_world_t *world, const char *name, const char *code) { ecs_script_t *script = ecs_script_parse(world, name, code, NULL); if (!script) { goto error; } ecs_entity_t prev_scope = ecs_set_scope(world, 0); if (ecs_script_eval(script, NULL)) { 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_expr_visit_free(script, impl->expr); 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, NULL); 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, NULL)) { ecs_script_free(s->script); s->script = NULL; 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; } ecs_script_runtime_t* ecs_script_runtime_new(void) { ecs_script_runtime_t *r = ecs_os_calloc_t(ecs_script_runtime_t); flecs_expr_stack_init(&r->expr_stack); flecs_allocator_init(&r->allocator); flecs_stack_init(&r->stack); ecs_vec_init_t(&r->allocator, &r->using, ecs_entity_t, 0); ecs_vec_init_t(&r->allocator, &r->with, ecs_value_t, 0); ecs_vec_init_t(&r->allocator, &r->with_type_info, ecs_type_info_t*, 0); ecs_vec_init_t(&r->allocator, &r->annot, ecs_script_annot_t*, 0); return r; } void ecs_script_runtime_free( ecs_script_runtime_t *r) { flecs_expr_stack_fini(&r->expr_stack); ecs_vec_fini_t(&r->allocator, &r->annot, ecs_script_annot_t*); ecs_vec_fini_t(&r->allocator, &r->with, ecs_value_t); ecs_vec_fini_t(&r->allocator, &r->with_type_info, ecs_type_info_t*); ecs_vec_fini_t(&r->allocator, &r->using, ecs_entity_t); flecs_allocator_fini(&r->allocator); flecs_stack_fini(&r->stack); ecs_os_free(r); } ecs_script_runtime_t* flecs_script_runtime_get( ecs_world_t *world) { ecs_stage_t *stage; if (flecs_poly_is(world, ecs_stage_t)) { stage = (ecs_stage_t*)world; } else { stage = world->stages[0]; } ecs_assert(stage != NULL, ECS_INTERNAL_ERROR, NULL); if (!stage->runtime) { stage->runtime = ecs_script_runtime_new(); } return stage->runtime; } 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, true); ser->member(ser, "ast"); ser->value(ser, ecs_id(ecs_string_t), &ast); ecs_os_free(ast); } else { char *nullString = NULL; ser->member(ser, "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), .flags = ECS_TYPE_HOOK_COPY_ILLEGAL }); 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); flecs_script_template_import(world); flecs_function_import(world); } #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) { ecs_assert(ptr != NULL, ECS_INVALID_PARAMETER, NULL); 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 ECS_COMPONENT_DECLARE(EcsScriptTemplateSetEvent); ECS_DECLARE(EcsScriptTemplate); static void flecs_template_set_event_free(EcsScriptTemplateSetEvent *ptr) { if (ptr->entities != &ptr->entity_storage) { ecs_os_free(ptr->entities); } if (ptr->data != ptr->data_storage) { ecs_os_free(ptr->data); } } static ECS_MOVE(EcsScriptTemplateSetEvent, dst, src, { flecs_template_set_event_free(dst); *dst = *src; if (src->entities == &src->entity_storage) { dst->entities = &dst->entity_storage; } if (src->data == src->data_storage) { dst->data = &dst->data_storage; } src->entities = NULL; src->data = NULL; }) static ECS_DTOR(EcsScriptTemplateSetEvent, ptr, { flecs_template_set_event_free(ptr); }) /* 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); } } } /* Defer template instantiation if we're in deferred mode. */ static void flecs_script_template_defer_on_set( ecs_iter_t *it, ecs_entity_t template_entity, const ecs_type_info_t *ti, void *data) { EcsScriptTemplateSetEvent evt; if ((it->count == 1) && ti->size <= ECS_TEMPLATE_SMALL_SIZE && !ti->hooks.dtor) { /* This should be true for the vast majority of templates */ evt.entities = &evt.entity_storage; evt.data = evt.data_storage; evt.entity_storage = it->entities[0]; ecs_os_memcpy(evt.data, data, ti->size); } else { evt.entities = ecs_os_memdup_n(it->entities, ecs_entity_t, it->count); evt.data = ecs_os_memdup(data, ti->size * it->count); } evt.count = it->count; evt.template_entity = template_entity; ecs_enqueue(it->world, &(ecs_event_desc_t){ .event = ecs_id(EcsScriptTemplateSetEvent), .entity = EcsAny, .param = &evt }); } static void flecs_script_template_instantiate( ecs_world_t *world, ecs_entity_t template_entity, const ecs_entity_t *entities, void *data, int32_t count) { ecs_assert(!ecs_is_deferred(world), ECS_INTERNAL_ERROR, NULL); 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); ecs_script_eval_visitor_t v; ecs_script_eval_desc_t desc = { .runtime = flecs_script_runtime_get(world) }; flecs_script_eval_visit_init(flecs_script_impl(script->script), &v, &desc); ecs_vec_t prev_using = v.r->using; ecs_vec_t prev_with = desc.runtime->with; ecs_vec_t prev_with_type_info = desc.runtime->with_type_info; v.r->using = template->using_; v.template_entity = template_entity; ecs_vec_init_t(NULL, &desc.runtime->with, ecs_value_t, 0); ecs_vec_init_t(NULL, &desc.runtime->with_type_info, ecs_type_info_t*, 0); 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 < count; i ++) { v.parent = entities[i]; ecs_assert(ecs_is_alive(world, v.parent), ECS_INTERNAL_ERROR, NULL); instance_node.eval = entities[i]; /* Create variables to hold template properties */ ecs_script_vars_t *vars = flecs_script_vars_push( NULL, &v.r->stack, &v.r->allocator); vars->parent = template->vars; /* Include hoisted variables */ vars->sp = ecs_vec_count(&template->vars->vars); /* Allocate enough space for variables */ ecs_script_vars_set_size(vars, (st ? st->members.count : 0) + 1); /* Populate $this variable with instance entity */ ecs_entity_t instance = entities[i]; ecs_script_var_t *this_var = ecs_script_vars_declare( vars, NULL /* $this */); this_var->value.type = ecs_id(ecs_entity_t); this_var->value.ptr = &instance; /* 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. Don't * set name as variables will be resolved by frame offset. */ ecs_script_var_t *var = ecs_script_vars_declare( vars, NULL /* member->name */); var->value.type = member->type; var->value.ptr = ECS_OFFSET(data, member->offset); } } ecs_script_clear(world, template_entity, instance); /* Run template code */ v.vars = vars; flecs_script_eval_scope(&v, scope); /* Pop variable scope */ ecs_script_vars_pop(vars); data = ECS_OFFSET(data, ti->size); } ecs_vec_fini_t(&desc.runtime->allocator, &desc.runtime->with, ecs_value_t); ecs_vec_fini_t(&desc.runtime->allocator, &desc.runtime->with_type_info, ecs_type_info_t*); v.r->with = prev_with; v.r->with_type_info = prev_with_type_info; v.r->using = prev_using; flecs_script_eval_visit_fini(&v, &desc); } static void flecs_on_template_set_event( ecs_iter_t *it) { ecs_assert(ecs_is_deferred(it->world), ECS_INTERNAL_ERROR, NULL); EcsScriptTemplateSetEvent *evt = it->param; ecs_world_t *world = it->real_world; ecs_assert(flecs_poly_is(world, ecs_world_t), ECS_INTERNAL_ERROR, NULL); ecs_defer_suspend(world); flecs_script_template_instantiate( world, evt->template_entity, evt->entities, evt->data, evt->count); ecs_defer_resume(world); } /* 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); void *data = ecs_field_w_size(it, flecs_ito(size_t, ti->size), 0); if (ecs_is_deferred(it->world)) { flecs_script_template_defer_on_set(it, template_entity, ti, data); return; } flecs_script_template_instantiate( world, template_entity, it->entities, data, it->count); return; } static int flecs_script_template_eval_prop( ecs_script_eval_visitor_t *v, ecs_script_var_node_t *node) { ecs_script_template_t *template = v->template; if (ecs_vec_count(&v->vars->vars) > ecs_vec_count(&template->prop_defaults)) { flecs_script_eval_error(v, node, "const variables declared before prop '%s' (props must come first)", node->name); return -1; } ecs_script_var_t *var = ecs_script_vars_declare(v->vars, node->name); if (!var) { flecs_script_eval_error(v, node, "variable '%s' redeclared", node->name); return -1; } ecs_entity_t type; const ecs_type_info_t *ti; if (node->type) { if (flecs_script_find_entity(v, 0, node->type, NULL, &type) || !type) { flecs_script_eval_error(v, node, "unresolved type '%s' for prop '%s'", node->type, node->name); return -1; } ti = flecs_script_get_type_info(v, node, type); if (!ti) { return -1; } var->value.type = type; var->value.ptr = flecs_stack_alloc( &v->r->stack, ti->size, ti->alignment); var->type_info = ti; if (flecs_script_eval_expr(v, &node->expr, &var->value)) { 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); ti = ecs_get_type_info(v->world, value.type); ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); var->value.ptr = flecs_stack_calloc( &v->r->stack, ti->size, ti->alignment); type = var->value.type = value.type; var->type_info = ti; if (ti->hooks.ctor) { ti->hooks.ctor(var->value.ptr, 1, 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); } 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_w_dbg_info( &v->base.script->allocator, ti->size, ti->name); 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 EcsAstTag: case EcsAstComponent: case EcsAstVarComponent: case EcsAstEntity: case EcsAstScope: case EcsAstDefaultComponent: case EcsAstWithVar: case EcsAstWithTag: case EcsAstWithComponent: case EcsAstUsing: case EcsAstModule: case EcsAstAnnotation: case EcsAstConst: case EcsAstPairScope: case EcsAstWith: case EcsAstIf: case EcsAstFor: break; case EcsAstTemplate: flecs_script_eval_error(v, node, "nested templates are not allowed"); return -1; case EcsAstProp: return flecs_script_template_eval_prop(v, (ecs_script_var_node_t*)node); } return flecs_script_check_node(v, node); } static int flecs_script_template_preprocess( ecs_script_eval_visitor_t *v, ecs_script_template_t *template) { ecs_visit_action_t prev_visit = v->base.visit; v->template = template; /* Dummy entity node for instance */ ecs_script_entity_t instance_node = { .node = { .kind = EcsAstEntity, .pos = template->node->node.pos } }; v->entity = &instance_node; v->base.visit = (ecs_visit_action_t)flecs_script_template_eval; v->vars = flecs_script_vars_push(v->vars, &v->r->stack, &v->r->allocator); ecs_script_var_t *var = ecs_script_vars_declare(v->vars, "this"); var->value.type = ecs_id(ecs_entity_t); int result = flecs_script_check_scope(v, template->node->scope); v->vars = ecs_script_vars_pop(v->vars); v->base.visit = prev_visit; v->template = NULL; v->entity = NULL; return result; } static int flecs_script_template_hoist_using( ecs_script_eval_visitor_t *v, ecs_script_template_t *template) { ecs_allocator_t *a = &v->base.script->allocator; if (v->module) { ecs_vec_append_t(a, &template->using_, ecs_entity_t)[0] = v->module; } int i, count = ecs_vec_count(&v->r->using); for (i = 0; i < count; i ++) { ecs_vec_append_t(a, &template->using_, ecs_entity_t)[0] = ecs_vec_get_t(&v->r->using, ecs_entity_t, i)[0]; } 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) { int32_t i, count = ecs_vec_count(&vars->vars); ecs_script_var_t *src_vars = ecs_vec_first(&vars->vars); for (i = 0; i < count; i ++) { ecs_script_var_t *src = &src_vars[i]; if (ecs_script_vars_lookup(template->vars, src->name)) { /* If variable is masked, don't declare it twice */ continue; } ecs_script_var_t *dst = ecs_script_vars_define_id( template->vars, src->name, src->value.type); ecs_assert(dst != NULL, ECS_INTERNAL_ERROR, NULL); ecs_value_copy(v->world, src->value.type, dst->value.ptr, src->value.ptr); } if (vars->parent) { flecs_script_template_hoist_vars(v, template, vars->parent); } 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; /* Variables are always presented to a template in a well defined order, so * we don't need dynamic variable binding. */ bool old_dynamic_variable_binding = v->dynamic_variable_binding; v->dynamic_variable_binding = false; 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; } v->dynamic_variable_binding = old_dynamic_variable_binding; /* 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; } void flecs_script_template_import( ecs_world_t *world) { ECS_COMPONENT_DEFINE(world, EcsScriptTemplateSetEvent); ECS_TAG_DEFINE(world, EcsScriptTemplate); ecs_add_id(world, EcsScriptTemplate, EcsPairIsTag); ecs_set_hooks(world, EcsScriptTemplateSetEvent, { .ctor = flecs_default_ctor, .move = ecs_move(EcsScriptTemplateSetEvent), .dtor = ecs_dtor(EcsScriptTemplateSetEvent), .flags = ECS_TYPE_HOOK_COPY_ILLEGAL }); ecs_observer(world, { .entity = ecs_entity(world, { .name = "TemplateSetObserver" }), .query.terms = {{ .id = EcsAny }}, .events = { ecs_id(EcsScriptTemplateSetEvent) }, .callback = flecs_on_template_set_event }); } #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);\ } else if (!ecs_os_strncmp(pos, keyword "\n", 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 EcsTokAssign: case EcsTokAdd: case EcsTokSub: case EcsTokMul: case EcsTokDiv: case EcsTokMod: case EcsTokBitwiseOr: case EcsTokBitwiseAnd: case EcsTokNot: case EcsTokOptional: case EcsTokEq: case EcsTokNeq: case EcsTokGt: case EcsTokGtEq: case EcsTokLt: case EcsTokLtEq: case EcsTokAnd: case EcsTokOr: case EcsTokMatch: case EcsTokRange: case EcsTokShiftLeft: case EcsTokShiftRight: case EcsTokAddAssign: case EcsTokMulAssign: return ""; case EcsTokKeywordWith: case EcsTokKeywordUsing: case EcsTokKeywordProp: case EcsTokKeywordConst: case EcsTokKeywordIf: case EcsTokKeywordElse: case EcsTokKeywordFor: case EcsTokKeywordIn: case EcsTokKeywordTemplate: case EcsTokKeywordModule: case EcsTokKeywordMatch: return "keyword "; case EcsTokIdentifier: return "identifier "; case EcsTokString: return "string "; case EcsTokNumber: return "number "; case EcsTokNewline: return "newline"; case EcsTokMember: return "member"; case EcsTokEnd: return "end of script"; default: return ""; } } const char* flecs_script_token_str( ecs_script_token_kind_t kind) { switch(kind) { case EcsTokUnknown: return "unknown token"; case EcsTokColon: return ":"; case EcsTokScopeOpen: return "{"; case EcsTokScopeClose: return "}"; case EcsTokParenOpen: return "("; case EcsTokParenClose: return ")"; case EcsTokBracketOpen: return "["; case EcsTokBracketClose: return "]"; case EcsTokAnnotation: return "@"; case EcsTokComma: return ","; case EcsTokSemiColon: return ";"; case EcsTokAssign: return "="; case EcsTokAdd: return "+"; case EcsTokSub: return "-"; case EcsTokMul: return "*"; case EcsTokDiv: return "/"; case EcsTokMod: return "%%"; case EcsTokBitwiseOr: return "|"; case EcsTokBitwiseAnd: return "&"; case EcsTokNot: return "!"; case EcsTokOptional: return "?"; case EcsTokEq: return "=="; case EcsTokNeq: return "!="; case EcsTokGt: return ">"; case EcsTokGtEq: return ">="; case EcsTokLt: return "<"; case EcsTokLtEq: return "<="; case EcsTokAnd: return "&&"; case EcsTokOr: return "||"; case EcsTokMatch: return "~="; case EcsTokRange: return ".."; case EcsTokShiftLeft: return "<<"; case EcsTokShiftRight: return ">>"; case EcsTokAddAssign: return "+="; case EcsTokMulAssign: return "*="; case EcsTokKeywordWith: return "with"; case EcsTokKeywordUsing: return "using"; case EcsTokKeywordProp: return "prop"; case EcsTokKeywordConst: return "const"; case EcsTokKeywordMatch: return "match"; case EcsTokKeywordIf: return "if"; case EcsTokKeywordElse: return "else"; case EcsTokKeywordFor: return "for"; case EcsTokKeywordIn: return "in"; case EcsTokKeywordTemplate: return "template"; case EcsTokKeywordModule: return "module"; case EcsTokIdentifier: return "identifier"; case EcsTokString: return "string"; case EcsTokNumber: return "number"; case EcsTokNewline: return "newline"; case EcsTokMember: return "member"; 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 == '#'); } const char* flecs_script_identifier( ecs_script_parser_t *parser, const char *pos, ecs_script_token_t *out) { if (out) { out->kind = EcsTokIdentifier; out->value = parser->token_cur; } ecs_assert(flecs_script_is_identifier(pos[0]), ECS_INTERNAL_ERROR, NULL); bool is_var = pos[0] == '$'; char *outpos = NULL; const char *start = pos; if (parser) { outpos = parser->token_cur; if (parser->merge_variable_members) { is_var = false; } } do { char c = pos[0]; bool is_ident = flecs_script_is_identifier(c) || isdigit(c); if (!is_var) { is_ident = is_ident || (c == '.'); } /* Retain \. for name lookup operation */ if (!is_ident && c == '\\' && pos[1] == '.') { is_ident = true; } /* Retain .* for using wildcard expressions */ if (!is_ident && c == '*') { if (pos != start && pos[-1] == '.') { is_ident = true; } } if (!is_ident) { if (c == '\\') { pos ++; } 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; } if (outpos) { *outpos = c; outpos ++; } pos ++; if (!indent) { break; } } while (true); if (outpos && parser) { *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 { if (outpos && parser) { *outpos = '\0'; parser->token_cur = outpos + 1; } return pos; } } if (outpos) { *outpos = *pos; outpos ++; } pos ++; } while (true); } // Number token static static bool flecs_script_is_number( const char *c) { return isdigit(c[0]) || ((c[0] == '-') && isdigit(c[1])); } 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; bool dot_parsed = false; bool e_parsed = false; ecs_assert(flecs_script_is_number(pos), ECS_INTERNAL_ERROR, NULL); char *outpos = parser->token_cur; if (pos[0] == '-') { outpos[0] = pos[0]; pos ++; outpos ++; } do { char c = pos[0]; bool valid_number = false; if (c == '.') { if (!dot_parsed && !e_parsed) { if (isdigit(pos[1])) { dot_parsed = true; valid_number = true; } } } else if (c == 'e') { if (!e_parsed) { if (isdigit(pos[1])) { e_parsed = true; valid_number = true; } } } else if (isdigit(c)) { valid_number = true; } if (!valid_number) { *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; } static const char* flecs_script_multiline_string( ecs_script_parser_t *parser, const char *pos, ecs_script_token_t *out) { char ch; const char *end = pos + 1; while ((ch = end[0]) && (ch != '`')) { if (ch == '\\' && end[1] == '`') { end ++; } end ++; } if (ch != '`') { return 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_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; } else if (flecs_script_is_number(pos)) { return flecs_script_number(parser, pos, out); OperatorMultiChar ("+=", EcsTokAddAssign) OperatorMultiChar ("*=", EcsTokMulAssign) Operator (":", EcsTokColon) Operator ("{", EcsTokScopeOpen) Operator ("}", EcsTokScopeClose) Operator ("(", EcsTokParenOpen) Operator (")", EcsTokParenClose) Operator ("[", EcsTokBracketOpen) Operator ("]", EcsTokBracketClose) Operator ("@", EcsTokAnnotation) Operator (",", EcsTokComma) Operator (";", EcsTokSemiColon) Operator ("+", EcsTokAdd) Operator ("-", EcsTokSub) Operator ("*", EcsTokMul) Operator ("/", EcsTokDiv) Operator ("%%", EcsTokMod) Operator ("?", EcsTokOptional) OperatorMultiChar ("..", EcsTokRange) Operator (".", EcsTokMember) OperatorMultiChar ("==", EcsTokEq) OperatorMultiChar ("!=", EcsTokNeq) OperatorMultiChar ("<<", EcsTokShiftLeft) OperatorMultiChar (">>", EcsTokShiftRight) OperatorMultiChar (">=", EcsTokGtEq) OperatorMultiChar ("<=", EcsTokLtEq) OperatorMultiChar ("&&", EcsTokAnd) OperatorMultiChar ("||", EcsTokOr) OperatorMultiChar ("~=", EcsTokMatch) Operator ("!", EcsTokNot) Operator ("=", EcsTokAssign) Operator ("&", EcsTokBitwiseAnd) Operator ("|", EcsTokBitwiseOr) Operator (">", EcsTokGt) Operator ("<", EcsTokLt) Keyword ("with", EcsTokKeywordWith) Keyword ("using", EcsTokKeywordUsing) Keyword ("template", EcsTokKeywordTemplate) Keyword ("prop", EcsTokKeywordProp) Keyword ("const", EcsTokKeywordConst) Keyword ("if", EcsTokKeywordIf) Keyword ("else", EcsTokKeywordElse) Keyword ("for", EcsTokKeywordFor) Keyword ("in", EcsTokKeywordIn) Keyword ("match", EcsTokKeywordMatch) Keyword ("module", EcsTokKeywordModule) } else if (pos[0] == '"') { return flecs_script_string(parser, pos, out); } else if (pos[0] == '`') { return flecs_script_multiline_string(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->sp = parent->sp + ecs_vec_count(&parent->vars); } else { result->sp = 0; } result->stack = stack; result->allocator = allocator; result->cursor = cursor; return result; error: return NULL; } ecs_script_vars_t* ecs_script_vars_init( ecs_world_t *world) { ecs_script_vars_t *result = flecs_script_vars_push(NULL, flecs_stage_get_stack_allocator(world), flecs_stage_get_allocator(world)); result->world = ecs_get_world(world); /* Provided world can be stage */ return result; } void ecs_script_vars_fini( ecs_script_vars_t *vars) { ecs_check(vars->parent == NULL, ECS_INVALID_PARAMETER, "ecs_script_vars_fini can only be called on the 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 (name) { if (flecs_name_index_is_init(&vars->var_index)) { if (flecs_name_index_find(&vars->var_index, name, 0, 0) != 0) { goto error; } } else { flecs_name_index_init(&vars->var_index, vars->allocator); } } ecs_script_var_t *var = ecs_vec_append_t( vars->allocator, &vars->vars, ecs_script_var_t); var->name = name; var->value.ptr = NULL; var->value.type = 0; var->type_info = NULL; var->sp = ecs_vec_count(&vars->vars) + vars->sp - 1; var->is_const = false; if (name) { flecs_name_index_ensure(&vars->var_index, flecs_ito(uint64_t, ecs_vec_count(&vars->vars)), name, 0, 0); } return var; error: return NULL; } void ecs_script_vars_set_size( ecs_script_vars_t *vars, int32_t count) { ecs_assert(!ecs_vec_count(&vars->vars), ECS_INVALID_OPERATION, "variable scope must be empty for resize operation"); ecs_vec_set_size_t(vars->allocator, &vars->vars, ecs_script_var_t, count); } ecs_script_var_t* ecs_script_vars_define_id( ecs_script_vars_t *vars, const char *name, ecs_entity_t type) { ecs_check(vars->world != NULL, ECS_INVALID_OPERATION, "variable scope is " "not associated with world, create scope with ecs_script_vars_init"); const ecs_type_info_t *ti = ecs_get_type_info(vars->world, type); ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "the entity provided for the type parameter is not a type"); ecs_script_var_t *result = ecs_script_vars_declare(vars, name); if (!result) { return NULL; } result->value.type = type; result->value.ptr = flecs_stack_alloc(vars->stack, ti->size, ti->alignment); result->type_info = ti; 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) { if (!vars) { return NULL; } uint64_t var_id = 0; if (ecs_vec_count(&vars->vars)) { if (flecs_name_index_is_init(&vars->var_index)) { var_id = flecs_name_index_find(&vars->var_index, name, 0, 0); } } if (!var_id) { if (vars->parent) { return ecs_script_vars_lookup(vars->parent, name); } return NULL; } return ecs_vec_get_t(&vars->vars, ecs_script_var_t, flecs_uto(int32_t, var_id - 1)); } ecs_script_var_t* ecs_script_vars_from_sp( const ecs_script_vars_t *vars, int32_t sp) { ecs_check(sp >= 0, ECS_INVALID_PARAMETER, NULL); if (sp < vars->sp) { ecs_assert(vars->parent != NULL, ECS_INTERNAL_ERROR, NULL); return ecs_script_vars_from_sp(vars->parent, sp); } sp -= vars->sp; ecs_check(sp < ecs_vec_count(&vars->vars), ECS_INVALID_PARAMETER, NULL); return ecs_vec_get_t(&vars->vars, ecs_script_var_t, sp); error: return NULL; } void ecs_script_vars_print( const ecs_script_vars_t *vars) { if (vars->parent) { ecs_script_vars_print(vars->parent); } int32_t i, count = ecs_vec_count(&vars->vars); ecs_script_var_t *array = ecs_vec_first(&vars->vars); for (i = 0; i < count; i ++) { ecs_script_var_t *var = &array[i]; if (!i) { printf("FRAME "); } else { printf(" "); } printf("%2d: %s\n", var->sp, var->name); } } /* Static names for iterator fields */ static const char* flecs_script_iter_field_names[] = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15" }; 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); } /* Safe, variable value will never be written */ var->value.ptr = ECS_CONST_CAST(ecs_entity_t*, &it->entities[offset]); } /* Set variables for fields */ { int8_t i, field_count = it->field_count; for (i = 0; i < field_count; i ++) { ecs_size_t size = it->sizes[i]; if (!size) { continue; } void *ptr = ecs_field_w_size(it, flecs_itosize(size), 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 ++) { const 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) { const ecs_entity_t *entities = ecs_table_entities(range->table); 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); } /* Safe, variable value will never be written */ var->value.ptr = ECS_CONST_CAST(ecs_entity_t*, 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_validate.c * @brief Script AST validation. */ #ifdef FLECS_SCRIPT static int flecs_script_check_expr( ecs_script_eval_visitor_t *v, ecs_expr_node_t **expr_ptr, ecs_entity_t *type) { ecs_expr_node_t *expr = *expr_ptr; ecs_script_impl_t *impl = v->base.script; ecs_script_t *script = &impl->pub; ecs_expr_eval_desc_t desc = { .name = script->name, .lookup_action = flecs_script_find_entity_action, .lookup_ctx = v, .vars = v->vars, .type = type ? type[0] : 0, .runtime = v->r, .allow_unresolved_identifiers = true }; ecs_assert(expr->type_info == NULL, ECS_INTERNAL_ERROR, NULL); if (flecs_expr_visit_type(script, expr, &desc)) { goto error; } if (flecs_expr_visit_fold(script, expr_ptr, &desc)) { goto error; } if (type) { type[0] = expr_ptr[0]->type; } return 0; error: return -1; } int flecs_script_check_scope( ecs_script_eval_visitor_t *v, ecs_script_scope_t *node) { int ret = flecs_script_eval_scope(v, node); if (ret) { return -1; } /* Gather all resolved components in scope so we can add them in one bulk * operation to entities. */ ecs_allocator_t *a = &v->base.script->allocator; int32_t i, count = ecs_vec_count(&node->stmts); ecs_script_node_t **stmts = ecs_vec_first(&node->stmts); for (i = 0; i < count; i ++) { ecs_script_node_t *stmt = stmts[i]; ecs_id_t id = 0; if (stmt->kind == EcsAstComponent) { ecs_script_component_t *cmp = (ecs_script_component_t*)stmt; id = cmp->id.eval; } else if (stmt->kind == EcsAstTag) { ecs_script_tag_t *cmp = (ecs_script_tag_t*)stmt; id = cmp->id.eval; } if (id) { ecs_vec_append_t(a, &node->components, ecs_id_t)[0] = id; } } return 0; } static int flecs_script_check_entity( ecs_script_eval_visitor_t *v, ecs_script_entity_t *node) { 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")) { } 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; } } ecs_script_entity_t *old_entity = v->entity; v->entity = node; bool old_is_with_scope = v->is_with_scope; v->is_with_scope = false; if (ecs_script_visit_node(v, node->scope)) { return -1; } v->is_with_scope = old_is_with_scope; v->entity = old_entity; return 0; } static int flecs_script_check_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->is_with_scope) { flecs_script_eval_error(v, node, "invalid component in with scope"); return -1; } 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; } return 0; } static int flecs_script_check_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->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; } if (v->is_with_scope) { flecs_script_eval_error(v, node, "invalid component in with scope"); return -1; } if (node->expr) { const ecs_type_info_t *ti = ecs_get_type_info(v->world, node->id.eval); if (!ti) { return 0; } 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_entity_t expr_type = ti->component; if (flecs_script_check_expr(v, &node->expr, &expr_type)) { return -1; } } return 0; } static int flecs_script_check_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; } node->sp = var->sp; return 0; } static int flecs_script_check_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; } return 0; } static int flecs_script_check_with_var( 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; } node->sp = var->sp; return 0; } static int flecs_script_check_with_tag( ecs_script_eval_visitor_t *v, ecs_script_tag_t *node) { if (flecs_script_eval_id(v, node, &node->id)) { return -1; } return 0; } static int flecs_script_check_with_component( ecs_script_eval_visitor_t *v, ecs_script_component_t *node) { if (flecs_script_eval_id(v, node, &node->id)) { return -1; } if (node->expr) { ecs_entity_t type = node->id.eval; if (flecs_script_check_expr(v, &node->expr, &type)) { return -1; } } return 0; } static int flecs_script_check_with( ecs_script_eval_visitor_t *v, ecs_script_with_t *node) { if (ecs_script_visit_scope(v, ((ecs_script_with_t*)node)->expressions)) { return -1; } bool old_is_with_scope = v->is_with_scope; v->is_with_scope = true; if (ecs_script_visit_scope(v, ((ecs_script_with_t*)node)->scope)) { return -1; } v->is_with_scope = old_is_with_scope; return 0; } static int flecs_script_check_using( ecs_script_eval_visitor_t *v, ecs_script_using_t *node) { return flecs_script_eval_using(v, node); } static int flecs_script_check_const( ecs_script_eval_visitor_t *v, ecs_script_var_node_t *node) { return flecs_script_eval_const(v, node); } static int flecs_script_check_pair_scope( ecs_script_eval_visitor_t *v, ecs_script_pair_scope_t *node) { ecs_entity_t dummy; if (flecs_script_find_entity( v, 0, node->id.first, &node->id.first_sp, &dummy)) { return -1; } if (flecs_script_find_entity( v, 0, node->id.second, &node->id.second_sp, &dummy)) { return -1; } if (ecs_script_visit_scope(v, node->scope)) { return -1; } return 0; } static int flecs_script_check_if( ecs_script_eval_visitor_t *v, ecs_script_if_t *node) { if (flecs_script_check_expr(v, &node->expr, NULL)) { return -1; } if (flecs_script_check_scope(v, node->if_true)) { return -1; } if (flecs_script_check_scope(v, node->if_false)) { return -1; } return 0; } static int flecs_script_check_for_range( ecs_script_eval_visitor_t *v, ecs_script_for_range_t *node) { ecs_entity_t type = ecs_id(ecs_i32_t); if (flecs_script_check_expr(v, &node->from, &type)) { return -1; } type = ecs_id(ecs_i32_t); if (flecs_script_check_expr(v, &node->to, &type)) { return -1; } v->vars = flecs_script_vars_push(v->vars, &v->r->stack, &v->r->allocator); ecs_script_var_t *var = ecs_script_vars_declare(v->vars, node->loop_var); const ecs_type_info_t *ti = ecs_get_type_info(v->world, ecs_id(ecs_i32_t)); int32_t dummy = 0; var->value.ptr = &dummy; var->value.type = ecs_id(ecs_i32_t); var->type_info = ti; if (flecs_script_eval_scope(v, node->scope)) { return -1; } var->value.ptr = NULL; v->vars = ecs_script_vars_pop(v->vars); return 0; } static int flecs_script_check_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; } return 0; } int flecs_script_check_node( ecs_script_eval_visitor_t *v, ecs_script_node_t *node) { switch(node->kind) { case EcsAstScope: return flecs_script_check_scope( v, (ecs_script_scope_t*)node); case EcsAstTag: return flecs_script_check_tag( v, (ecs_script_tag_t*)node); case EcsAstComponent: return flecs_script_check_component( v, (ecs_script_component_t*)node); case EcsAstVarComponent: return flecs_script_check_var_component( v, (ecs_script_var_component_t*)node); case EcsAstDefaultComponent: return flecs_script_check_default_component( v, (ecs_script_default_component_t*)node); case EcsAstWithVar: return flecs_script_check_with_var( v, (ecs_script_var_component_t*)node); case EcsAstWithTag: return flecs_script_check_with_tag( v, (ecs_script_tag_t*)node); case EcsAstWithComponent: return flecs_script_check_with_component( v, (ecs_script_component_t*)node); case EcsAstWith: return flecs_script_check_with( v, (ecs_script_with_t*)node); case EcsAstUsing: return flecs_script_check_using( v, (ecs_script_using_t*)node); case EcsAstModule: return 0; case EcsAstAnnotation: return flecs_script_check_annot( v, (ecs_script_annot_t*)node); case EcsAstTemplate: return 0; case EcsAstProp: return 0; case EcsAstConst: return flecs_script_check_const( v, (ecs_script_var_node_t*)node); case EcsAstEntity: return flecs_script_check_entity( v, (ecs_script_entity_t*)node); case EcsAstPairScope: return flecs_script_check_pair_scope( v, (ecs_script_pair_scope_t*)node); case EcsAstIf: return flecs_script_check_if( v, (ecs_script_if_t*)node); case EcsAstFor: return flecs_script_check_for_range( v, (ecs_script_for_range_t*)node); } ecs_abort(ECS_INTERNAL_ERROR, "corrupt AST node kind"); } int flecs_script_check( const ecs_script_t *script, const ecs_script_eval_desc_t *desc) { ecs_script_eval_visitor_t v; ecs_script_impl_t *impl = flecs_script_impl( /* Safe, script will only be used for reading by visitor */ ECS_CONST_CAST(ecs_script_t*, script)); ecs_script_eval_desc_t priv_desc = {0}; if (desc) { priv_desc = *desc; } if (!priv_desc.runtime) { priv_desc.runtime = flecs_script_runtime_get(script->world); } flecs_script_eval_visit_init(impl, &v, &priv_desc); int result = ecs_script_visit(impl, &v, flecs_script_check_node); flecs_script_eval_visit_fini(&v, &priv_desc); return result; } #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, const ecs_type_info_t *ti) { if (ecs_vec_count(&v->r->with)) { ecs_assert(ecs_vec_last_t(&v->r->with, ecs_value_t)->type == 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(ecs_vec_last_t(&v->r->with, ecs_value_t)->ptr == NULL, ECS_INTERNAL_ERROR, NULL); ecs_vec_remove_last(&v->r->with); } ecs_vec_append_t(a, &v->r->with_type_info, const ecs_type_info_t*)[0] = ti; ecs_vec_append_t(a, &v->r->with, ecs_value_t); ecs_value_t *last = ecs_vec_append_t(a, &v->r->with, ecs_value_t); ecs_os_memset_t(last, 0, ecs_value_t); return ecs_vec_get_t(&v->r->with, ecs_value_t, ecs_vec_count(&v->r->with) - 2); } static void flecs_script_with_set_count( ecs_allocator_t *a, ecs_script_eval_visitor_t *v, int32_t count) { int32_t i = count, until = ecs_vec_count(&v->r->with) - 1; for (; i < until; i ++) { ecs_value_t *val = ecs_vec_get_t(&v->r->with, ecs_value_t, i); ecs_type_info_t *ti = ecs_vec_get_t( &v->r->with_type_info, ecs_type_info_t*, i)[0]; if (ti && ti->hooks.dtor) { ti->hooks.dtor(val->ptr, 1, ti); } } if (count) { ecs_value_t *last = ecs_vec_get_t(&v->r->with, ecs_value_t, count); ecs_os_memset_t(last, 0, ecs_value_t); ecs_vec_set_count_t(a, &v->r->with, ecs_value_t, count + 1); } else { ecs_vec_set_count_t(a, &v->r->with, ecs_value_t, 0); } ecs_vec_set_count_t(a, &v->r->with_type_info, ecs_type_info_t*, count); } static ecs_value_t* flecs_script_with_last( ecs_script_eval_visitor_t *v) { int32_t count = ecs_vec_count(&v->r->with); if (count) { return ecs_vec_get_t(&v->r->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->r->with)) { ecs_assert(ecs_vec_last_t(&v->r->with, ecs_value_t)->type == 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(ecs_vec_last_t(&v->r->with, ecs_value_t)->ptr == NULL, ECS_INTERNAL_ERROR, NULL); return ecs_vec_count(&v->r->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); flecs_dump_backtrace(stdout); ecs_os_free(idstr); } return NULL; } ecs_script_var_t* flecs_script_find_var( const ecs_script_vars_t *vars, const char *name, int32_t *sp) { if (sp && sp[0] != -1) { return ecs_script_vars_from_sp(vars, sp[0]); } else { ecs_script_var_t *var = ecs_script_vars_lookup(vars, name); if (var && sp) { sp[0] = var->sp; } return var; } } int flecs_script_find_entity( ecs_script_eval_visitor_t *v, ecs_entity_t from, const char *path, int32_t *sp, ecs_entity_t *out) { if (!path) { goto error; } if (path[0] == '$') { if (!sp) { flecs_script_eval_error(v, NULL, "variable identifier '%s' not allowed here", path); goto error; } const ecs_script_var_t *var = flecs_script_find_var( v->vars, &path[1], v->dynamic_variable_binding ? NULL : sp); if (!var) { goto error; } 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); goto error; } if (v->template) { return 0; } if (var->value.ptr == NULL) { flecs_script_eval_error(v, NULL, "variable '%s' is not initialized", path); goto error; } 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); goto error; } *out = result; return 0; } if (from) { *out = ecs_lookup_path_w_sep(v->world, from, path, NULL, NULL, false); } else { int32_t i, using_count = ecs_vec_count(&v->r->using); if (using_count) { ecs_entity_t *using = ecs_vec_first(&v->r->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) { *out = e; return 0; } } } *out = ecs_lookup_path_w_sep( v->world, v->parent, path, NULL, NULL, true); } return 0; error: return -1; } 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->r->with, ecs_value_t); } ecs_entity_desc_t desc = {0}; desc.name = name; desc.parent = v->parent; desc.set = with; return ecs_entity_init(v->world, &desc); } 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; ecs_entity_t result; if (!flecs_script_find_entity(v, 0, path, NULL, &result)) { return result; } return 0; } static int flecs_script_find_template_entity( ecs_script_eval_visitor_t *v, void *node, const char *name) { ecs_assert(name != NULL, ECS_INTERNAL_ERROR, NULL); /* Loop template scope to see if it declares an entity with requested name */ ecs_script_template_t *t = v->template; ecs_assert(t != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(t->node != NULL, ECS_INTERNAL_ERROR, NULL); ecs_script_scope_t *scope = t->node->scope; ecs_script_node_t **nodes = ecs_vec_first_t( &scope->stmts, ecs_script_node_t*); int32_t i, count = ecs_vec_count(&scope->stmts); for (i = 0; i < count; i ++) { ecs_script_node_t *elem = nodes[i]; if (elem->kind == EcsAstEntity) { ecs_script_entity_t *entity_node = (ecs_script_entity_t*)elem; if (!entity_node->name) { continue; } if (!ecs_os_strcmp(entity_node->name, name)) { return 0; } } } flecs_script_eval_error(v, node, "unresolved reference to '%s'", name); return -1; } 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->eval && !id->dynamic) { /* Already resolved */ return 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] == '$') { if (flecs_script_find_var(v->vars, &id->first[1], v->dynamic_variable_binding ? NULL : &id->first_sp)) { return 0; } else { flecs_script_eval_error(v, node, "unresolved variable '%s'", &id->first[1]); return -1; } } if (id->second && id->second[0] == '$') { if (flecs_script_find_var(v->vars, &id->second[1], v->dynamic_variable_binding ? NULL : &id->second_sp)) { return 0; } else { flecs_script_eval_error(v, node, "unresolved variable '%s'", &id->second[1]); return -1; } } } ecs_entity_t first = 0; if (flecs_script_find_entity( v, 0, id->first, &id->first_sp, &first) || !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 = 0; if (flecs_script_find_entity( v, second_from, id->second, &id->second_sp, &second) || !second) { if (id->second[0] == '$') { flecs_script_eval_error(v, node, "unresolved variable '%s'", id->second); return -1; } /* Targets may be defined by the template */ if (v->template) { if (!flecs_script_find_template_entity(v, node, id->second)) { id->dynamic = true; return 0; } else { 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, ecs_expr_node_t **expr_ptr, ecs_value_t *value) { ecs_expr_node_t *expr = *expr_ptr; ecs_assert(expr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_script_impl_t *impl = v->base.script; ecs_script_t *script = &impl->pub; ecs_expr_eval_desc_t desc = { .name = script->name, .lookup_action = flecs_script_find_entity_action, .lookup_ctx = v, .vars = v->vars, .type = value->type, .runtime = v->r, .disable_dynamic_variable_binding = !v->dynamic_variable_binding }; if (expr->type_info == NULL) { if (flecs_expr_visit_type(script, expr, &desc)) { goto error; } if (flecs_expr_visit_fold(script, expr_ptr, &desc)) { goto error; } } if (flecs_expr_visit_eval(script, *expr_ptr, &desc, value)) { goto error; } return 0; error: return -1; } 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->r->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->r->allocator; v->vars = flecs_script_vars_push(v->vars, &v->r->stack, a); if (scope_parent && (scope_parent->kind == EcsAstEntity)) { if (!v->template) { v->parent = ecs_script_node(entity, scope_parent)->eval; } } if (v->entity) { ecs_entity_t src = v->entity->eval; int32_t count = ecs_vec_count(&node->components); if (src != EcsSingleton && count) { flecs_add_ids( v->world, src, ecs_vec_first(&node->components), count); } } int result = ecs_script_visit_scope(v, node); ecs_vec_set_count_t(a, &v->r->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; } } ecs_expr_node_t *name_expr = node->name_expr; if (name_expr) { ecs_script_t *script = &v->base.script->pub; ecs_expr_eval_desc_t desc = { .name = script->name, .lookup_action = flecs_script_find_entity_action, .lookup_ctx = v, .vars = v->vars, .type = ecs_id(ecs_string_t), .runtime = v->r }; if (!name_expr->type_info) { if (flecs_expr_visit_type(script, name_expr, &desc)) { return -1; } if (flecs_expr_visit_fold(script, &node->name_expr, &desc)) { return -1; } name_expr = node->name_expr; } ecs_value_t value = { .type = ecs_id(ecs_string_t) }; if (flecs_expr_visit_eval(script, name_expr, &desc, &value)) { return -1; } char *name = *(char**)value.ptr; if (!name) { flecs_script_eval_error(v, node, "failed to evaluate entity name"); return -1; } node->eval = flecs_script_create_entity(v, name); ecs_value_free(script->world, value.type, value.ptr); } else { node->eval = flecs_script_create_entity(v, node->name); } node->parent = v->entity; if (v->template_entity) { ecs_add_pair( v->world, node->eval, EcsScriptTemplate, v->template_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->r->annot); if (count) { ecs_script_annot_t **annots = ecs_vec_first(&v->r->annot); for (i = 0; i < count ; i ++) { flecs_script_apply_annot(v, node->eval, annots[i]); } ecs_vec_clear(&v->r->annot); } bool old_is_with_scope = v->is_with_scope; ecs_entity_t old_template_entity = v->template_entity; v->is_with_scope = false; v->template_entity = 0; if (ecs_script_visit_node(v, node->scope)) { return -1; } v->template_entity = old_template_entity; v->is_with_scope = old_is_with_scope; 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->is_with_scope) { flecs_script_eval_error(v, node, "invalid component in with scope"); return -1; } 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->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; } if (v->is_with_scope) { flecs_script_eval_error(v, node, "invalid component in with scope"); return -1; } ecs_entity_t src = flecs_script_get_src(v, v->entity->eval, node->id.eval); if (node->expr) { const ecs_type_info_t *ti = flecs_script_get_type_info( v, node, node->id.eval); if (!ti) { return -1; } 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_record_t *r = flecs_entities_get(v->world, src); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_t *table = r->table; ecs_value_t value = { .ptr = ecs_ensure_id(v->world, src, node->id.eval), .type = ti->component }; /* Assign entire value, including members not set by expression. This * prevents uninitialized or unexpected values. */ if (r->table != table) { if (!ti->hooks.ctor) { ecs_os_memset(value.ptr, 0, ti->size); } else if (ti->hooks.ctor) { if (ti->hooks.dtor) { ti->hooks.dtor(value.ptr, 1, ti); } ti->hooks.ctor(value.ptr, 1, ti); } } 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 = flecs_script_find_var( v->vars, node->name, v->dynamic_variable_binding ? NULL : &node->sp); if (!var) { flecs_script_eval_error(v, node, "unresolved variable '%s'", node->name); return -1; } if (v->is_with_scope) { flecs_script_eval_error(v, node, "invalid component in with scope"); 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_component_t *node) { ecs_script_var_t *var = flecs_script_find_var( v->vars, node->name, v->dynamic_variable_binding ? NULL : &node->sp); if (!var) { flecs_script_eval_error(v, node, "unresolved variable '%s'", node->name); return -1; } ecs_allocator_t *a = &v->r->allocator; ecs_value_t *value = flecs_script_with_append(a, v, NULL); // TODO: vars of non trivial types *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->r->allocator; ecs_value_t *value = flecs_script_with_append(a, v, NULL); 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->r->allocator; const ecs_type_info_t *ti = flecs_script_get_type_info( v, node, node->id.eval); ecs_value_t *value = flecs_script_with_append(a, v, ti); value->type = node->id.eval; value->ptr = NULL; if (node->expr) { if (!ti) { return -1; } value->ptr = flecs_stack_alloc(&v->r->stack, ti->size, ti->alignment); value->type = ti->component; // Expression parser needs actual type if (ti->hooks.ctor) { ti->hooks.ctor(value->ptr, 1, ti); } 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->r->allocator; int32_t prev_with_count = flecs_script_with_count(v); ecs_stack_cursor_t *prev_stack_cursor = flecs_stack_get_cursor(&v->r->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; } } bool old_is_with_scope = v->is_with_scope; v->is_with_scope = true; if (ecs_script_visit_scope(v, node->scope)) { result = -1; goto error; } v->is_with_scope = old_is_with_scope; error: flecs_script_with_set_count(a, v, prev_with_count); flecs_stack_restore_cursor(&v->r->stack, prev_stack_cursor); return result; } int flecs_script_eval_using( ecs_script_eval_visitor_t *v, ecs_script_using_t *node) { ecs_allocator_t *a = &v->r->allocator; int32_t len = ecs_os_strlen(node->name); 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->r->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->r->using, ecs_entity_t)[0] = from; } return 0; } static int flecs_script_eval_module( ecs_script_eval_visitor_t *v, ecs_script_module_t *node) { ecs_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; } int flecs_script_eval_const( ecs_script_eval_visitor_t *v, ecs_script_var_node_t *node) { /* Declare variable. If this variable is declared while instantiating a * template, the variable sp has already been resolved in all expressions * that used it, so we don't need to create the variable with a name. */ ecs_script_var_t *var = ecs_script_vars_declare(v->vars, v->template_entity ? NULL : node->name); if (!var) { flecs_script_eval_error(v, node, "variable '%s' redeclared", node->name); return -1; } ecs_entity_t type = 0; const ecs_type_info_t *ti = NULL; if (node->expr) { type = node->expr->type; ti = node->expr->type_info; } if (!type && node->type) { if (flecs_script_find_entity(v, 0, node->type, NULL, &type) || !type) { flecs_script_eval_error(v, node, "unresolved type '%s' for const variable '%s'", node->type, node->name); return -1; } 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; } } if (type && ti) { ecs_assert(type != 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); var->value.ptr = flecs_stack_calloc( &v->r->stack, ti->size, ti->alignment); var->value.type = type; var->type_info = ti; if (ti->hooks.ctor) { ti->hooks.ctor(var->value.ptr, 1, 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); ti = ecs_get_type_info(v->world, value.type); ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); var->value.ptr = flecs_stack_calloc( &v->r->stack, ti->size, ti->alignment); var->value.type = value.type; var->type_info = ti; if (ti->hooks.ctor) { ti->hooks.ctor(var->value.ptr, 1, 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); } /* If variable resolves to a constant expression, mark it as const so that * its value can be folded. */ if (node->expr->kind == EcsExprValue) { var->is_const = true; } 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; if (flecs_script_find_entity( v, 0, node->id.first, &node->id.first_sp, &first) || !first) { first = flecs_script_create_entity(v, node->id.first); if (!first) { return -1; } } ecs_entity_t second = 0; if (node->id.second) { if (node->id.second[0] == '$') { if (flecs_script_find_entity( v, 0, node->id.second, &node->id.second_sp, &second)) { return -1; } } else { second = flecs_script_create_entity(v, node->id.second); } } if (!second) { return -1; } ecs_allocator_t *a = &v->r->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, NULL); 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->r->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->r->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_for_range( ecs_script_eval_visitor_t *v, ecs_script_for_range_t *node) { int32_t from; int32_t to; ecs_value_t from_val = { .type = ecs_id(ecs_i32_t), .ptr = &from }; ecs_value_t to_val = { .type = ecs_id(ecs_i32_t), .ptr = &to }; if (flecs_script_eval_expr(v, &node->from, &from_val)) { return -1; } if (flecs_script_eval_expr(v, &node->to, &to_val)) { return -1; } v->vars = flecs_script_vars_push(v->vars, &v->r->stack, &v->r->allocator); ecs_script_var_t *var = ecs_script_vars_declare(v->vars, node->loop_var); var->value.ptr = flecs_stack_calloc(&v->r->stack, 4, 4); var->value.type = ecs_id(ecs_i32_t); var->type_info = ecs_get_type_info(v->world, ecs_id(ecs_i32_t)); int32_t i; for (i = from; i < to; i ++) { *(int32_t*)var->value.ptr = i; if (flecs_script_eval_scope(v, node->scope)) { return -1; } } v->vars = ecs_script_vars_pop(v->vars); return 0; } static int flecs_script_eval_annot( ecs_script_eval_visitor_t *v, ecs_script_annot_t *node) { ecs_assert(v->base.next != NULL, ECS_INTERNAL_ERROR, NULL); ecs_allocator_t *a = &v->r->allocator; ecs_vec_append_t(a, &v->r->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) { ecs_assert(v->template == NULL, ECS_INTERNAL_ERROR, NULL); 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_component_t*)node); case EcsAstWithTag: return flecs_script_eval_with_tag( v, (ecs_script_tag_t*)node); case EcsAstWithComponent: return flecs_script_eval_with_component( v, (ecs_script_component_t*)node); case 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); case EcsAstFor: return flecs_script_eval_for_range( v, (ecs_script_for_range_t*)node); } ecs_abort(ECS_INTERNAL_ERROR, "corrupt AST node kind"); } void flecs_script_eval_visit_init( const ecs_script_impl_t *script, ecs_script_eval_visitor_t *v, const ecs_script_eval_desc_t *desc) { *v = (ecs_script_eval_visitor_t){ .base = { .visit = (ecs_visit_action_t)flecs_script_eval_node, .script = ECS_CONST_CAST(ecs_script_impl_t*, script) }, .world = script->pub.world, .r = desc ? desc->runtime : NULL }; if (!v->r) { v->r = ecs_script_runtime_new(); } if (desc && desc->vars) { ecs_allocator_t *a = &v->r->allocator; v->vars = flecs_script_vars_push(v->vars, &v->r->stack, a); v->vars->parent = desc->vars; v->vars->sp = ecs_vec_count(&desc->vars->vars); /* When variables are provided to script, don't use cached variable * stack pointers, as the order in which the application provides * variables may not be the same across evaluations. */ v->dynamic_variable_binding = true; } /* Always include flecs.meta */ ecs_vec_append_t(&v->r->allocator, &v->r->using, ecs_entity_t)[0] = ecs_lookup(v->world, "flecs.meta"); } void flecs_script_eval_visit_fini( ecs_script_eval_visitor_t *v, const ecs_script_eval_desc_t *desc) { if (desc && desc->vars) { v->vars = ecs_script_vars_pop(v->vars); } if (!desc || (v->r != desc->runtime)) { ecs_script_runtime_free(v->r); } } int ecs_script_eval( const ecs_script_t *script, const ecs_script_eval_desc_t *desc) { ecs_script_eval_visitor_t v; ecs_script_impl_t *impl = flecs_script_impl( /* Safe, script will only be used for reading by visitor */ ECS_CONST_CAST(ecs_script_t*, script)); ecs_script_eval_desc_t priv_desc = {0}; if (desc) { priv_desc = *desc; } if (!priv_desc.runtime) { priv_desc.runtime = flecs_script_runtime_get(script->world); } flecs_script_eval_visit_init(impl, &v, &priv_desc); int result = ecs_script_visit(impl, &v, flecs_script_eval_node); flecs_script_eval_visit_fini(&v, &priv_desc); 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*); ecs_vec_fini_t(&v->script->allocator, &node->components, ecs_id_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); if (node->name_expr) { flecs_expr_visit_free(&v->script->pub, node->name_expr); } } 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); flecs_expr_visit_free(&v->script->pub, node->expr); } static void flecs_script_for_range_free( ecs_script_visit_t *v, ecs_script_for_range_t *node) { flecs_expr_visit_free(&v->script->pub, node->from); flecs_expr_visit_free(&v->script->pub, node->to); flecs_script_scope_free(v, node->scope); } static void flecs_script_component_free( ecs_script_visit_t *v, ecs_script_component_t *node) { flecs_expr_visit_free(&v->script->pub, node->expr); } static void flecs_script_default_component_free( ecs_script_visit_t *v, ecs_script_default_component_t *node) { flecs_expr_visit_free(&v->script->pub, node->expr); } static void flecs_script_var_node_free( ecs_script_visit_t *v, ecs_script_var_node_t *node) { flecs_expr_visit_free(&v->script->pub, node->expr); } 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 EcsAstFor: flecs_script_for_range_free(v, (ecs_script_for_range_t*)node); flecs_free_t(a, ecs_script_for_range_t, node); break; case EcsAstTag: flecs_free_t(a, ecs_script_tag_t, node); break; case EcsAstComponent: case EcsAstWithComponent: flecs_script_component_free(v, (ecs_script_component_t*)node); flecs_free_t(a, ecs_script_component_t, node); break; case EcsAstDefaultComponent: flecs_script_default_component_free(v, (ecs_script_default_component_t*)node); 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 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_script_var_node_free(v, (ecs_script_var_node_t*)node); 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; bool colors; } 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_script_color_to_str( ecs_script_str_visitor_t *v, const char *color) { if (v->colors) ecs_strbuf_appendstr(v->buf, color); } static void flecs_scriptbuf_append( ecs_script_str_visitor_t *v, const char *fmt, ...) { if (v->newline) { ecs_strbuf_append(v->buf, "%*s", v->depth * 2, ""); v->newline = false; } va_list args; va_start(args, fmt); ecs_strbuf_vappend(v->buf, fmt, args); va_end(args); if (fmt[strlen(fmt) - 1] == '\n') { v->newline = true; } } static void flecs_scriptbuf_appendstr( ecs_script_str_visitor_t *v, const char *str) { if (v->newline) { ecs_strbuf_append(v->buf, "%*s", v->depth * 2, ""); v->newline = false; } ecs_strbuf_appendstr(v->buf, str); if (str[strlen(str) - 1] == '\n') { v->newline = true; } } static void flecs_script_id_to_str( ecs_script_str_visitor_t *v, ecs_script_id_t *id) { if (id->flag) { if (id->flag == ECS_AUTO_OVERRIDE) { flecs_scriptbuf_appendstr(v, "auto_override | "); } else { flecs_scriptbuf_appendstr(v, "??? | "); } } if (id->second) { flecs_scriptbuf_append(v, "(%s, %s)", id->first, id->second); } else { flecs_scriptbuf_appendstr(v, id->first); } } static void flecs_expr_to_str( ecs_script_str_visitor_t *v, const ecs_expr_node_t *expr) { if (expr) { flecs_expr_to_str_buf( v->base.script->pub.world, expr, v->buf, v->colors); } 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"; case EcsAstFor: return "for"; } return "???"; } static void flecs_scriptbuf_node( ecs_script_str_visitor_t *v, ecs_script_node_t *node) { flecs_script_color_to_str(v, ECS_BLUE); flecs_scriptbuf_append(v, "%s: ", flecs_script_node_to_str(node)); flecs_script_color_to_str(v, 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_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_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_script_color_to_str(v, ECS_CYAN); flecs_scriptbuf_appendstr(v, "expressions: "); flecs_script_color_to_str(v, ECS_NORMAL); flecs_script_scope_to_str(v, node->expressions); flecs_script_color_to_str(v, ECS_CYAN); flecs_scriptbuf_append(v, "scope: "); flecs_script_color_to_str(v, 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_script_color_to_str(v, ECS_GREEN); flecs_scriptbuf_append(v, "%s = \"%s\"", node->name, node->expr); flecs_script_color_to_str(v, 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_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_expr_to_str(v, node->expr); flecs_scriptbuf_appendstr(v, " {\n"); v->depth ++; flecs_script_color_to_str(v, ECS_CYAN); flecs_scriptbuf_appendstr(v, "true: "); flecs_script_color_to_str(v, ECS_NORMAL); flecs_script_scope_to_str(v, node->if_true); flecs_script_color_to_str(v, ECS_CYAN); flecs_scriptbuf_appendstr(v, "false: "); flecs_script_color_to_str(v, ECS_NORMAL); flecs_script_scope_to_str(v, node->if_false); v->depth --; flecs_scriptbuf_appendstr(v, "}\n"); } static void flecs_script_for_range_to_str( ecs_script_str_visitor_t *v, ecs_script_for_range_t *node) { flecs_scriptbuf_node(v, &node->node); flecs_scriptbuf_appendstr(v, node->loop_var); flecs_scriptbuf_appendstr(v, " "); flecs_expr_to_str(v, node->from); flecs_scriptbuf_appendstr(v, " .. "); flecs_expr_to_str(v, node->to); flecs_scriptbuf_appendstr(v, " {\n"); v->depth ++; flecs_script_scope_to_str(v, node->scope); 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; case EcsAstFor: flecs_script_for_range_to_str(v, (ecs_script_for_range_t*)node); break; } return 0; } int ecs_script_ast_to_buf( ecs_script_t *script, ecs_strbuf_t *buf, bool colors) { ecs_check(script != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(buf != NULL, ECS_INVALID_PARAMETER, NULL); ecs_script_str_visitor_t v = { .buf = buf, .colors = colors }; 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, bool colors) { ecs_check(script != NULL, ECS_INVALID_PARAMETER, NULL); ecs_strbuf_t buf = ECS_STRBUF_INIT; if (flecs_script_impl(script)->expr) { flecs_expr_to_str_buf( script->world, flecs_script_impl(script)->expr, &buf, colors); } else { if (ecs_script_ast_to_buf(script, &buf, colors)) { goto error; } } return ecs_strbuf_get(&buf); error: return NULL; } #endif /** * @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; if (!count) { cur = -1; continue; } } else { cur ++; } res = qit.entities[cur]; stats = ecs_map_ensure_alloc(stats_map, ctx->api.stats_size, res); ctx->api.set_t(stats, t_last % ECS_STAT_WINDOW); } if (!dif) { /* Copy last value so we can pass it to reduce_last */ last = stats_storage; ecs_os_memset(last, 0, ctx->api.stats_size); ctx->api.copy_last(last, stats); } ctx->api.get(world, res, stats); if (!dif) { /* Still in same interval, combine with last measurement */ ctx->api.reduce_last(stats, last, hdr->reduce_count); } else if (dif > 1) { /* More than 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_map_count(&world->type_info)); ECS_COUNTER_RECORD(&s->components.create_count, t, world->info.id_create_total); ECS_COUNTER_RECORD(&s->components.delete_count, t, world->info.id_delete_total); ECS_GAUGE_RECORD(&s->queries.query_count, t, ecs_count_id(world, EcsQuery)); ECS_GAUGE_RECORD(&s->queries.observer_count, t, ecs_count_id(world, EcsObserver)); if (ecs_is_alive(world, EcsSystem)) { ECS_GAUGE_RECORD(&s->queries.system_count, t, ecs_count_id(world, EcsSystem)); } ECS_COUNTER_RECORD(&s->tables.create_count, t, world->info.table_create_total); ECS_COUNTER_RECORD(&s->tables.delete_count, t, world->info.table_delete_total); ECS_GAUGE_RECORD(&s->tables.count, t, world->info.table_count); ECS_COUNTER_RECORD(&s->commands.add_count, t, world->info.cmd.add_count); ECS_COUNTER_RECORD(&s->commands.remove_count, t, world->info.cmd.remove_count); ECS_COUNTER_RECORD(&s->commands.delete_count, t, world->info.cmd.delete_count); ECS_COUNTER_RECORD(&s->commands.clear_count, t, world->info.cmd.clear_count); ECS_COUNTER_RECORD(&s->commands.set_count, t, world->info.cmd.set_count); ECS_COUNTER_RECORD(&s->commands.ensure_count, t, world->info.cmd.ensure_count); ECS_COUNTER_RECORD(&s->commands.modified_count, t, world->info.cmd.modified_count); ECS_COUNTER_RECORD(&s->commands.other_count, t, world->info.cmd.other_count); ECS_COUNTER_RECORD(&s->commands.discard_count, t, world->info.cmd.discard_count); ECS_COUNTER_RECORD(&s->commands.batched_entity_count, t, world->info.cmd.batched_entity_count); ECS_COUNTER_RECORD(&s->commands.batched_count, t, world->info.cmd.batched_command_count); int64_t outstanding_allocs = ecs_os_api_malloc_count + ecs_os_api_calloc_count - ecs_os_api_free_count; ECS_COUNTER_RECORD(&s->memory.alloc_count, t, ecs_os_api_malloc_count + ecs_os_api_calloc_count); ECS_COUNTER_RECORD(&s->memory.realloc_count, t, ecs_os_api_realloc_count); ECS_COUNTER_RECORD(&s->memory.free_count, t, ecs_os_api_free_count); ECS_GAUGE_RECORD(&s->memory.outstanding_alloc_count, t, outstanding_allocs); outstanding_allocs = ecs_block_allocator_alloc_count - ecs_block_allocator_free_count; ECS_COUNTER_RECORD(&s->memory.block_alloc_count, t, ecs_block_allocator_alloc_count); ECS_COUNTER_RECORD(&s->memory.block_free_count, t, ecs_block_allocator_free_count); ECS_GAUGE_RECORD(&s->memory.block_outstanding_alloc_count, t, outstanding_allocs); outstanding_allocs = ecs_stack_allocator_alloc_count - ecs_stack_allocator_free_count; ECS_COUNTER_RECORD(&s->memory.stack_alloc_count, t, ecs_stack_allocator_alloc_count); ECS_COUNTER_RECORD(&s->memory.stack_free_count, t, ecs_stack_allocator_free_count); ECS_GAUGE_RECORD(&s->memory.stack_outstanding_alloc_count, t, outstanding_allocs); #ifdef FLECS_HTTP ECS_COUNTER_RECORD(&s->http.request_received_count, t, ecs_http_request_received_count); ECS_COUNTER_RECORD(&s->http.request_invalid_count, t, ecs_http_request_invalid_count); ECS_COUNTER_RECORD(&s->http.request_handled_ok_count, t, ecs_http_request_handled_ok_count); ECS_COUNTER_RECORD(&s->http.request_handled_error_count, t, ecs_http_request_handled_error_count); ECS_COUNTER_RECORD(&s->http.request_not_handled_count, t, ecs_http_request_not_handled_count); ECS_COUNTER_RECORD(&s->http.request_preflight_count, t, ecs_http_request_preflight_count); ECS_COUNTER_RECORD(&s->http.send_ok_count, t, ecs_http_send_ok_count); ECS_COUNTER_RECORD(&s->http.send_error_count, t, ecs_http_send_error_count); ECS_COUNTER_RECORD(&s->http.busy_count, t, ecs_http_busy_count); #endif error: return; } void ecs_world_stats_reduce( ecs_world_stats_t *dst, const ecs_world_stats_t *src) { flecs_stats_reduce(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), ECS_METRIC_FIRST(src), (dst->t = t_next(dst->t)), src->t); } void ecs_world_stats_reduce_last( ecs_world_stats_t *dst, const ecs_world_stats_t *src, int32_t count) { flecs_stats_reduce_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), ECS_METRIC_FIRST(src), (dst->t = t_prev(dst->t)), src->t, count); } void ecs_world_stats_repeat_last( ecs_world_stats_t *stats) { flecs_stats_repeat_last(ECS_METRIC_FIRST(stats), ECS_METRIC_LAST(stats), (stats->t = t_next(stats->t))); } void ecs_world_stats_copy_last( ecs_world_stats_t *dst, const ecs_world_stats_t *src) { flecs_stats_copy_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), ECS_METRIC_FIRST(src), dst->t, t_next(src->t)); } void ecs_query_stats_get( const ecs_world_t *world, const ecs_query_t *query, ecs_query_stats_t *s) { ecs_check(query != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); (void)world; int32_t t = s->t = t_next(s->t); ecs_query_count_t counts = ecs_query_count(query); ECS_GAUGE_RECORD(&s->result_count, t, counts.results); ECS_GAUGE_RECORD(&s->matched_table_count, t, counts.tables); ECS_GAUGE_RECORD(&s->matched_entity_count, t, counts.entities); error: return; } void ecs_query_cache_stats_reduce( ecs_query_stats_t *dst, const ecs_query_stats_t *src) { flecs_stats_reduce(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), ECS_METRIC_FIRST(src), (dst->t = t_next(dst->t)), src->t); } void ecs_query_cache_stats_reduce_last( ecs_query_stats_t *dst, const ecs_query_stats_t *src, int32_t count) { flecs_stats_reduce_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), ECS_METRIC_FIRST(src), (dst->t = t_prev(dst->t)), src->t, count); } void ecs_query_cache_stats_repeat_last( ecs_query_stats_t *stats) { flecs_stats_repeat_last(ECS_METRIC_FIRST(stats), ECS_METRIC_LAST(stats), (stats->t = t_next(stats->t))); } void ecs_query_cache_stats_copy_last( ecs_query_stats_t *dst, const ecs_query_stats_t *src) { flecs_stats_copy_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), ECS_METRIC_FIRST(src), dst->t, t_next(src->t)); } #ifdef FLECS_SYSTEM bool ecs_system_stats_get( const ecs_world_t *world, ecs_entity_t system, ecs_system_stats_t *s) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(system != 0, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); const ecs_system_t *ptr = flecs_poly_get(world, system, ecs_system_t); if (!ptr) { return false; } ecs_query_stats_get(world, ptr->query, &s->query); int32_t t = s->query.t; ECS_COUNTER_RECORD(&s->time_spent, t, ptr->time_spent); s->task = !(ptr->query->flags & EcsQueryMatchThis); return true; error: return false; } void ecs_system_stats_reduce( ecs_system_stats_t *dst, const ecs_system_stats_t *src) { ecs_query_cache_stats_reduce(&dst->query, &src->query); dst->task = src->task; flecs_stats_reduce(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), ECS_METRIC_FIRST(src), dst->query.t, src->query.t); } void ecs_system_stats_reduce_last( ecs_system_stats_t *dst, const ecs_system_stats_t *src, int32_t count) { ecs_query_cache_stats_reduce_last(&dst->query, &src->query, count); dst->task = src->task; flecs_stats_reduce_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), ECS_METRIC_FIRST(src), dst->query.t, src->query.t, count); } void ecs_system_stats_repeat_last( ecs_system_stats_t *stats) { ecs_query_cache_stats_repeat_last(&stats->query); flecs_stats_repeat_last(ECS_METRIC_FIRST(stats), ECS_METRIC_LAST(stats), (stats->query.t)); } void ecs_system_stats_copy_last( ecs_system_stats_t *dst, const ecs_system_stats_t *src) { ecs_query_cache_stats_copy_last(&dst->query, &src->query); dst->task = src->task; flecs_stats_copy_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), ECS_METRIC_FIRST(src), dst->query.t, t_next(src->query.t)); } #endif #ifdef FLECS_PIPELINE bool ecs_pipeline_stats_get( ecs_world_t *stage, ecs_entity_t pipeline, ecs_pipeline_stats_t *s) { ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(pipeline != 0, ECS_INVALID_PARAMETER, NULL); const ecs_world_t *world = ecs_get_world(stage); const EcsPipeline *pqc = ecs_get(world, pipeline, EcsPipeline); if (!pqc) { return false; } ecs_pipeline_state_t *pq = pqc->state; ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); int32_t sys_count = 0, active_sys_count = 0; /* 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 flecs_copy_world_summary( ecs_world_t *world, EcsWorldSummary *dst) { const ecs_world_info_t *info = ecs_get_world_info(world); dst->target_fps = (double)info->target_fps; dst->time_scale = (double)info->time_scale; dst->frame_time_last = (double)info->frame_time_total - dst->frame_time_total; dst->system_time_last = (double)info->system_time_total - dst->system_time_total; dst->merge_time_last = (double)info->merge_time_total - dst->merge_time_total; dst->frame_time_total = (double)info->frame_time_total; dst->system_time_total = (double)info->system_time_total; dst->merge_time_total = (double)info->merge_time_total; dst->frame_count ++; dst->command_count += info->cmd.add_count + info->cmd.remove_count + info->cmd.delete_count + info->cmd.clear_count + info->cmd.set_count + info->cmd.ensure_count + info->cmd.modified_count + info->cmd.discard_count + info->cmd.event_count + info->cmd.other_count; dst->build_info = *ecs_get_build_info(); } static void UpdateWorldSummary(ecs_iter_t *it) { EcsWorldSummary *summary = ecs_field(it, EcsWorldSummary, 0); int32_t i, count = it->count; for (i = 0; i < count; i ++) { flecs_copy_world_summary(it->world, &summary[i]); } } static void OnSetWorldSummary(ecs_iter_t *it) { EcsWorldSummary *summary = ecs_field(it, EcsWorldSummary, 0); int32_t i, count = it->count; for (i = 0; i < count; i ++) { ecs_set_target_fps(it->world, (ecs_ftime_t)summary[i].target_fps); ecs_set_time_scale(it->world, (ecs_ftime_t)summary[i].time_scale); } } void FlecsWorldSummaryImport( ecs_world_t *world) { ECS_COMPONENT_DEFINE(world, EcsWorldSummary); #if defined(FLECS_META) && defined(FLECS_UNITS) ecs_entity_t build_info = ecs_lookup(world, "flecs.core.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 }); EcsWorldSummary *summary = ecs_ensure(world, EcsWorld, EcsWorldSummary); flecs_copy_world_summary(world, summary); ecs_modified(world, EcsWorld, EcsWorldSummary); } #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) { flecs_poly_assert(world, ecs_world_t); ecs_ftime_t time_elapsed = delta_time; ecs_entity_t tick_source = system_data->tick_source; /* Support legacy behavior */ if (!param) { param = system_data->ctx; } if (tick_source) { const EcsTickSource *tick = ecs_get(world, tick_source, EcsTickSource); if (tick) { time_elapsed = tick->time_elapsed; /* If timer hasn't fired we shouldn't run the system */ if (!tick->tick) { return 0; } } else { /* If a timer has been set but the timer entity does not have the * EcsTimer component, don't run the system. This can be the result * of a single-shot timer that has fired already. Not resetting the * timer field of the system will ensure that the system won't be * ran after the timer has fired. */ return 0; } } ecs_os_perf_trace_push(system_data->name); if (ecs_should_log_3()) { char *path = ecs_get_path(world, system); ecs_dbg_3("worker %d: %s", stage_index, path); ecs_os_free(path); } ecs_time_t time_start; bool measure_time = ECS_BIT_IS_SET(world->flags, EcsWorldMeasureSystemTime); if (measure_time) { ecs_os_get_time(&time_start); } ecs_world_t *thread_ctx = world; if (stage) { thread_ctx = stage->thread_ctx; } else { stage = world->stages[0]; } flecs_poly_assert(stage, ecs_stage_t); /* 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 query matches nothing, the system run callback doesn't have * anything to iterate, so the iterator resources don't get cleaned up * automatically, so clean it up here. */ if (system_data->query->flags & EcsQueryMatchNothing) { it->next = flecs_default_next_callback; /* Return once */ run(it); ecs_iter_fini(&qit); } else { 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); ecs_os_perf_trace_pop(system_data->name); return it->interrupted_by; } /* -- Public API -- */ ecs_entity_t ecs_run_worker( ecs_world_t *world, ecs_entity_t system, int32_t stage_index, int32_t stage_count, ecs_ftime_t delta_time, void *param) { 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); } /* Safe cast, type owns name */ ecs_os_free(ECS_CONST_CAST(char*, sys->name)); flecs_poly_free(sys, ecs_system_t); } /* ecs_poly_dtor_t-compatible wrapper */ static void flecs_system_poly_fini(void *sys) { flecs_system_fini(sys); } static int flecs_system_init_timer( ecs_world_t *world, ecs_entity_t entity, const ecs_system_desc_t *desc) { if (ECS_NEQZERO(desc->interval) && ECS_NEQZERO(desc->rate)) { char *name = ecs_get_path(world, entity); ecs_err("system %s cannot have both interval and rate set", name); ecs_os_free(name); return -1; } if (ECS_NEQZERO(desc->interval) || ECS_NEQZERO(desc->rate) || ECS_NEQZERO(desc->tick_source)) { #ifdef FLECS_TIMER if (ECS_NEQZERO(desc->interval)) { ecs_set_interval(world, entity, desc->interval); } if (desc->rate) { ecs_entity_t tick_source = desc->tick_source; ecs_set_rate(world, entity, desc->rate, tick_source); } else if (desc->tick_source) { ecs_set_tick_source(world, entity, desc->tick_source); } #else (void)world; (void)entity; ecs_abort(ECS_UNSUPPORTED, "timer module not available"); #endif } return 0; } ecs_entity_t ecs_system_init( ecs_world_t *world, const ecs_system_desc_t *desc) { flecs_poly_assert(world, ecs_world_t); ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, "ecs_system_desc_t was not initialized to zero"); ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INVALID_WHILE_READONLY, NULL); ecs_entity_t entity = desc->entity; if (!entity) { entity = ecs_entity(world, {0}); } EcsPoly *poly = flecs_poly_bind(world, entity, ecs_system_t); 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; system->name = ecs_get_path(world, entity); if (flecs_system_init_timer(world, entity, desc)) { ecs_delete(world, entity); ecs_defer_end(world); goto error; } 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->multi_threaded) { system->multi_threaded = desc->multi_threaded; } if (desc->immediate) { system->immediate = desc->immediate; } if (flecs_system_init_timer(world, entity, desc)) { return 0; } } flecs_poly_modified(world, entity, ecs_system_t); return entity; error: return 0; } const ecs_system_t* ecs_system_get( const ecs_world_t *world, ecs_entity_t entity) { return flecs_poly_get(world, entity, ecs_system_t); } void 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 (((1llu << term->field_index) & query->pub.data_fields)) { /* Can't have an anonymous variable as source of a term * that returns a component. We need to return each * instance of the component, whereas anonymous * variables are not guaranteed to be resolved to * individual entities. */ if (var->anonymous) { ecs_err( "can't use anonymous variable '%s' as source of " "data term", var->name); goto error; } } /* Track which variable ids are used as field source */ if (!query->src_vars) { query->src_vars = flecs_calloc_n(&stage->allocator, ecs_var_id_t, query->pub.field_count); } query->src_vars[term->field_index] = var_id; } } else { if (flecs_term_ref_is_wildcard(src)) { anonymous_count ++; } } } else if ((src->id & EcsIsVariable) && (ECS_TERM_REF_ID(src) == EcsThis)) { if (flecs_term_is_builtin_pred(term) && term->oper == EcsOr) { flecs_query_add_var(query, EcsThisName, vars, EcsVarEntity); } } if (flecs_query_add_var_for_term_id( query, second, vars, EcsVarEntity) == EcsVarNone) { /* If second is a wildcard, insert anonymous variable */ if (flecs_term_ref_is_wildcard(second)) { anonymous_count ++; } } if (src->id & EcsIsVariable && second->id & EcsIsVariable) { if (term->flags_ & EcsTermTransitive) { /* Anonymous variable to store temporary id for finding * targets for transitive relationship, see compile_term. */ anonymous_count ++; } } /* If member term, make sure source is available as entity */ if (term->flags_ & EcsTermIsMember) { flecs_query_add_var_for_term_id(query, src, vars, EcsVarEntity); } /* Track if a This entity variable is used before a potential This table * variable. If this happens, the query has no This table variable */ if (ECS_TERM_REF_ID(src) == EcsThis) { table_this = true; } bool first_is_this = (ECS_TERM_REF_ID(first) == EcsThis) && (first->id & EcsIsVariable); bool second_is_this = (ECS_TERM_REF_ID(first) == EcsThis) && (first->id & EcsIsVariable); if (first_is_this || second_is_this) { if (!table_this) { entity_before_table_this = true; } } } int32_t var_count = ecs_vec_count(vars); 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 = &flecs_this_array; if ((var_count + anonymous_count) > 1) { query_vars = flecs_alloc(&stage->allocator, (ECS_SIZEOF(ecs_query_var_t) + ECS_SIZEOF(char*)) * (var_count + anonymous_count)); } query->vars = query_vars; query->var_count = var_count; query->pub.var_count = flecs_ito(int8_t, var_count); ECS_BIT_COND(query->pub.flags, EcsQueryHasTableThisVar, !entity_before_table_this); query->var_size = var_count + anonymous_count; char **var_names; if (query_vars != &flecs_this_array) { query_vars[0].kind = EcsVarTable; query_vars[0].name = NULL; flecs_set_var_label(&query_vars[0], NULL); query_vars[0].id = 0; query_vars[0].table_id = EcsVarNone; query_vars[0].lookup = NULL; var_names = ECS_ELEM(query_vars, ECS_SIZEOF(ecs_query_var_t), var_count + anonymous_count); var_names[0] = ECS_CONST_CAST(char*, query_vars[0].name); } else { var_names = &flecs_this_name_array; } query->pub.vars = (char**)var_names; query_vars ++; var_names ++; var_count --; if (var_count) { ecs_query_var_t *user_vars = ecs_vec_first_t(vars, ecs_query_var_t); ecs_os_memcpy_n(query_vars, user_vars, ecs_query_var_t, var_count); for (i = 0; i < var_count; i ++) { ecs_assert(&var_names[i] != &(&flecs_this_name_array)[i], ECS_INTERNAL_ERROR, NULL); var_names[i] = ECS_CONST_CAST(char*, query_vars[i].name); } } /* Hide anonymous table variables from application */ query->pub.var_count = flecs_ito(int8_t, query->pub.var_count - anonymous_table_count); /* Sanity check to make sure that the public part of the variable array only * contains entity variables. */ #ifdef FLECS_DEBUG for (i = 1 /* first element = $this */; i < query->pub.var_count; i ++) { ecs_assert(query->vars[i].kind == EcsVarEntity, ECS_INTERNAL_ERROR, NULL); } #endif return 0; error: return -1; } static bool flecs_query_var_is_unknown( ecs_query_impl_t *query, ecs_var_id_t var_id, ecs_query_compile_ctx_t *ctx) { ecs_query_var_t *vars = query->vars; if (ctx->written & (1ull << var_id)) { return false; } else { ecs_var_id_t table_var = vars[var_id].table_id; if (table_var != EcsVarNone) { return flecs_query_var_is_unknown(query, table_var, ctx); } } return true; } /* Returns whether term is unknown. A term is unknown when it has variable * elements (first, second, src) that are all unknown. */ static bool flecs_query_term_is_unknown( ecs_query_impl_t *query, ecs_term_t *term, ecs_query_compile_ctx_t *ctx) { ecs_query_op_t dummy = {0}; flecs_query_compile_term_ref(NULL, query, &dummy, &term->first, &dummy.first, EcsQueryFirst, EcsVarEntity, ctx, false); flecs_query_compile_term_ref(NULL, query, &dummy, &term->second, &dummy.second, EcsQuerySecond, EcsVarEntity, ctx, false); flecs_query_compile_term_ref(NULL, query, &dummy, &term->src, &dummy.src, EcsQuerySrc, EcsVarAny, ctx, false); bool has_vars = dummy.flags & ((EcsQueryIsVar << EcsQueryFirst) | (EcsQueryIsVar << EcsQuerySecond) | (EcsQueryIsVar << EcsQuerySrc)); if (!has_vars) { /* If term has no variables (typically terms with a static src) there * can't be anything that's unknown. */ return false; } if (dummy.flags & (EcsQueryIsVar << EcsQueryFirst)) { if (!flecs_query_var_is_unknown(query, dummy.first.var, ctx)) { return false; } } if (dummy.flags & (EcsQueryIsVar << EcsQuerySecond)) { if (!flecs_query_var_is_unknown(query, dummy.second.var, ctx)) { return false; } } if (dummy.flags & (EcsQueryIsVar << EcsQuerySrc)) { if (!flecs_query_var_is_unknown(query, dummy.src.var, ctx)) { return false; } } return true; } /* Find the next known term from specified offset. This function is used to find * a term that can be evaluated before a term that is unknown. Evaluating known * before unknown terms can significantly decrease the search space. */ static int32_t flecs_query_term_next_known( ecs_query_impl_t *query, ecs_query_compile_ctx_t *ctx, int32_t offset, ecs_flags64_t compiled) { ecs_query_t *q = &query->pub; ecs_term_t *terms = q->terms; int32_t i, count = q->term_count; for (i = offset; i < count; i ++) { ecs_term_t *term = &terms[i]; if (compiled & (1ull << i)) { continue; } /* Only evaluate And terms */ if (term->oper != EcsAnd || flecs_term_is_or(q, term)){ continue; } /* Don't reorder terms in scopes */ if (term->flags_ & EcsTermIsScope) { continue; } if (flecs_query_term_is_unknown(query, term, ctx)) { continue; } return i; } return -1; } /* If the first part of a query contains more than one trivial term, insert a * special instruction which batch-evaluates multiple terms. */ static void flecs_query_insert_trivial_search( ecs_query_impl_t *query, ecs_flags64_t *compiled, ecs_query_compile_ctx_t *ctx) { ecs_query_t *q = &query->pub; ecs_term_t *terms = q->terms; int32_t i, term_count = q->term_count; ecs_flags64_t trivial_set = 0; /* Trivial search always ignores prefabs and disabled entities */ if (query->pub.flags & (EcsQueryMatchPrefab|EcsQueryMatchDisabled)) { return; } /* Find trivial terms, which can be handled in single instruction */ int32_t trivial_wildcard_terms = 0; int32_t trivial_terms = 0; for (i = 0; i < term_count; i ++) { /* Term is already compiled */ if (*compiled & (1ull << i)) { continue; } ecs_term_t *term = &terms[i]; if (!(term->flags_ & EcsTermIsTrivial)) { continue; } /* We can only add trivial terms to plan if they no up traversal */ if ((term->src.id & EcsTraverseFlags) != EcsSelf) { continue; } /* Wildcards are not supported for trivial queries */ if (ecs_id_is_wildcard(term->id)) { continue; } trivial_set |= (1llu << i); trivial_terms ++; } 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 there's more than 1 trivial term, batch them in trivial search */ ecs_query_op_t trivial = {0}; if (!trivial_wildcard_terms) { trivial.kind = EcsQueryTriv; } /* Store the bitset with trivial terms on the instruction */ trivial.src.entity = trivial_set; flecs_query_op_insert(&trivial, ctx); /* Mark $this as written */ ctx->written |= (1llu << 0); } } static void flecs_query_insert_cache_search( ecs_query_impl_t *query, ecs_flags64_t *compiled, ecs_query_compile_ctx_t *ctx) { if (!query->cache) { return; } ecs_query_t *q = &query->pub; if (q->cache_kind == EcsQueryCacheAll) { /* If all terms are cacheable, make sure no other terms are compiled */ *compiled = 0xFFFFFFFFFFFFFFFF; } 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 ++) { if ((*compiled) & (1ull << i)) { continue; } ecs_term_t *term = &terms[i]; if (!(term->flags_ & EcsTermIsCacheable)) { continue; } *compiled |= (1ull << i); } } /* Insert the operation for cache traversal */ ecs_query_op_t op = {0}; if (q->flags & EcsQueryIsCacheable) { op.kind = EcsQueryIsCache; } else { op.kind = EcsQueryCache; } flecs_query_write(0, &op.written); flecs_query_write_ctx(0, ctx, false); flecs_query_op_insert(&op, ctx); } static bool flecs_term_ref_match_multiple( ecs_term_ref_t *ref) { return (ref->id & EcsIsVariable) && (ECS_TERM_REF_ID(ref) != EcsAny); } static bool flecs_term_match_multiple( ecs_term_t *term) { return flecs_term_ref_match_multiple(&term->first) || flecs_term_ref_match_multiple(&term->second); } static int flecs_query_insert_toggle( ecs_query_impl_t *impl, ecs_query_compile_ctx_t *ctx) { ecs_query_t *q = &impl->pub; int32_t i, j, term_count = q->term_count; ecs_term_t *terms = q->terms; ecs_flags64_t fields_done = 0; for (i = 0; i < term_count; i ++) { if (fields_done & (1llu << i)) { continue; } ecs_term_t *term = &terms[i]; if (term->flags_ & EcsTermIsToggle) { ecs_query_op_t cur = {0}; flecs_query_compile_term_ref(NULL, impl, &cur, &term->src, &cur.src, EcsQuerySrc, EcsVarAny, ctx, false); ecs_flags64_t and_toggles = 0; ecs_flags64_t not_toggles = 0; ecs_flags64_t optional_toggles = 0; for (j = i; j < term_count; j ++) { if (fields_done & (1llu << j)) { continue; } /* Also includes term[i], so flags get set correctly */ term = &terms[j]; /* If term is not for the same src, skip */ ecs_query_op_t next = {0}; flecs_query_compile_term_ref(NULL, impl, &next, &term->src, &next.src, EcsQuerySrc, EcsVarAny, ctx, false); if (next.src.entity != cur.src.entity || next.flags != cur.flags) { continue; } /* Source matches, set flag */ if (term->oper == EcsNot) { not_toggles |= (1llu << 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_query_compile_ctx_t *ctx) { ecs_query_t *q = &impl->pub; int32_t i, term_count = q->term_count; ecs_term_t *terms = q->terms; for (i = 0; i < term_count; i ++) { ecs_term_t *term = &terms[i]; if (term->oper == EcsNot) { /* If term has not operator and variables for first/second, we can't * put the term first as this could prevent us from getting back * valid results. For example: * !$var(e), Tag($var) * * Here, the first term would evaluate to false (and cause the * entire query not to match) if 'e' has any components. * * However, when reordering we get results: * Tag($var), !$var(e) * * Now the query returns all entities with Tag, that 'e' does not * have as component. For this reason, queries should never use * unwritten variables in not terms- and we should also not reorder * terms in a way that results in doing this. */ if (flecs_term_match_multiple(term)) { continue; } } /* Don't reorder terms in scopes */ if (term->flags_ & EcsTermIsScope) { continue; } if (term->src.id & EcsIsEntity && ECS_TERM_REF_ID(&term->src)) { if (flecs_query_compile_term(world, impl, term, ctx)) { return -1; } *compiled |= (1llu << i); } } return 0; } int flecs_query_compile( ecs_world_t *world, ecs_stage_t *stage, ecs_query_impl_t *query) { /* Compile query to operations. Only necessary for non-trivial queries, as * trivial queries use trivial iterators that don't use query ops. */ bool needs_plan = true; ecs_flags32_t flags = query->pub.flags; ecs_flags32_t trivial_flags = EcsQueryIsTrivial|EcsQueryMatchOnlySelf; if ((flags & trivial_flags) == trivial_flags) { if (query->cache) { if (flags & EcsQueryIsCacheable) { needs_plan = false; } } else { if (!(flags & EcsQueryMatchWildcards)) { needs_plan = false; } } } if (!needs_plan) { /* Initialize space for $this variable */ query->pub.var_count = 1; query->var_count = 1; query->var_size = 1; query->vars = &flecs_this_array; query->pub.vars = &flecs_this_name_array; query->pub.flags |= EcsQueryHasTableThisVar; return 0; } ecs_query_t *q = &query->pub; ecs_term_t *terms = q->terms; ecs_query_compile_ctx_t ctx = {0}; ecs_vec_reset_t(NULL, &stage->operations, ecs_query_op_t); ctx.ops = &stage->operations; ctx.cur = ctx.ctrlflow; ctx.cur->lbl_begin = -1; 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; /* Always evaluate terms with fixed source before other terms */ flecs_query_insert_fixed_src_terms( world, query, &compiled, &ctx); /* Compile cacheable terms */ flecs_query_insert_cache_search(query, &compiled, &ctx); /* Insert trivial term search if query allows for it */ flecs_query_insert_trivial_search(query, &compiled, &ctx); /* If a query starts with one or more optional terms, first compile the non * optional terms. This prevents having to insert an instruction that * matches the query against every entity in the storage. * Only skip optional terms at the start of the query so that any * short-circuiting behavior isn't affected (a non-optional term can become * optional if it uses a variable set in an optional term). */ int32_t start_term = 0; for (; start_term < term_count; start_term ++) { if (terms[start_term].oper != EcsOptional) { break; } } do { /* Compile remaining query terms to instructions */ for (i = start_term; i < term_count; i ++) { ecs_term_t *term = &terms[i]; int32_t compile = i; if (compiled & (1ull << i)) { continue; /* Already compiled */ } if (term->oper == EcsOptional && start_term) { /* Don't reorder past the first optional term that's not in the * initial list of optional terms. This protects short * circuiting branching in the query. * A future algorithm could look at which variables are * accessed by optional terms, and continue reordering terms * that don't access those variables. */ break; } bool can_reorder = true; if (term->oper != EcsAnd || flecs_term_is_or(q, term)){ can_reorder = false; } /* If variables have been written, but this term has no known variables, * first try to resolve terms that have known variables. This can * significantly reduce the search space. * Only perform this optimization after at least one variable has been * written to, as all terms are unknown otherwise. */ if (can_reorder && ctx.written && flecs_query_term_is_unknown(query, term, &ctx)) { int32_t term_index = flecs_query_term_next_known( query, &ctx, i + 1, compiled); if (term_index != -1) { term = &q->terms[term_index]; compile = term_index; i --; /* Repeat current term */ } } if (flecs_query_compile_term(world, query, term, &ctx)) { return -1; } compiled |= (1ull << compile); } if (start_term) { start_term = 0; /* Repeat, now also insert optional terms */ } else { break; } } while (true); ecs_var_id_t this_id = flecs_query_find_var_id(query, "this", EcsVarEntity); if (this_id != EcsVarNone) { /* If This variable has been written as entity, insert an operation to * assign it to it.entities for consistency. */ if (ctx.written & (1ull << this_id)) { ecs_query_op_t set_this = {0}; set_this.kind = EcsQuerySetThis; set_this.flags |= (EcsQueryIsVar << EcsQueryFirst); set_this.first.var = this_id; flecs_query_op_insert(&set_this, &ctx); } } /* Make sure non-This variables are written as entities */ if (query->vars) { for (i = 0; i < query->var_count; i ++) { ecs_query_var_t *var = &query->vars[i]; if (var->id && var->kind == EcsVarTable && var->name) { ecs_var_id_t var_id = flecs_query_find_var_id(query, var->name, EcsVarEntity); if (!flecs_query_is_written(var_id, ctx.written)) { /* Skip anonymous variables */ if (!flecs_query_var_is_anonymous(query, var_id)) { flecs_query_insert_each(var->id, var_id, &ctx, false); } } } } } /* If query contains non-This variables as term source, build lookup array */ if (query->src_vars) { ecs_assert(query->vars != NULL, ECS_INTERNAL_ERROR, NULL); bool only_anonymous = true; for (i = 0; i < q->field_count; i ++) { ecs_var_id_t var_id = query->src_vars[i]; if (!var_id) { continue; } if (!flecs_query_var_is_anonymous(query, var_id)) { only_anonymous = false; break; } else { /* Don't fetch component data for anonymous variables. Because * not all metadata (such as it.sources) is initialized for * anonymous variables, and because they may only be available * as table variables (each is not guaranteed to be inserted for * anonymous variables) the iterator may not have sufficient * information to resolve component data. */ for (int32_t t = 0; t < q->term_count; t ++) { ecs_term_t *term = &q->terms[t]; if (term->field_index == i) { term->inout = EcsInOutNone; } } } } /* Don't insert setvar instruction if all vars are anonymous */ if (!only_anonymous) { ecs_query_op_t set_vars = {0}; set_vars.kind = EcsQuerySetVars; flecs_query_op_insert(&set_vars, &ctx); } for (i = 0; i < q->field_count; i ++) { ecs_var_id_t var_id = query->src_vars[i]; if (!var_id) { continue; } if (query->vars[var_id].kind == EcsVarTable) { var_id = flecs_query_find_var_id(query, query->vars[var_id].name, EcsVarEntity); /* Variables used as source that aren't This must be entities */ ecs_assert(var_id != EcsVarNone, ECS_INTERNAL_ERROR, NULL); } query->src_vars[i] = var_id; } } ecs_assert((term_count - ctx.skipped) >= 0, ECS_INTERNAL_ERROR, NULL); /* If query is empty, insert Nothing instruction */ if (!(term_count - ctx.skipped)) { ecs_vec_clear(ctx.ops); ecs_query_op_t nothing = {0}; nothing.kind = EcsQueryNothing; flecs_query_op_insert(¬hing, &ctx); } else { /* If query contains terms for toggleable components, insert toggle */ if (!(q->flags & EcsQueryTableOnly)) { flecs_query_insert_toggle(query, &ctx); } /* Insert yield. If program reaches this operation, a result was found */ ecs_query_op_t yield = {0}; yield.kind = EcsQueryYield; flecs_query_op_insert(&yield, &ctx); } int32_t op_count = ecs_vec_count(ctx.ops); if (op_count) { query->op_count = op_count; query->ops = flecs_alloc_n(&stage->allocator, ecs_query_op_t, op_count); ecs_query_op_t *query_ops = ecs_vec_first_t(ctx.ops, ecs_query_op_t); ecs_os_memcpy_n(query->ops, query_ops, ecs_query_op_t, op_count); } 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 { printf("VARNONE\n"); flecs_dump_backtrace(stdout); 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; or_op->field_index = term->field_index; /* 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) { /* Should've been caught by query validator */ ecs_assert(ctx->scope > 0, ECS_INTERNAL_ERROR, NULL); ctx->cur = &ctx->ctrlflow[-- ctx->scope]; } static int flecs_query_compile_0_src( ecs_world_t *world, ecs_query_impl_t *impl, ecs_term_t *term, ecs_query_compile_ctx_t *ctx) { /* If the term has a 0 source, check if it's a scope open/close */ if (ECS_TERM_REF_ID(&term->first) == EcsScopeOpen) { if (flecs_query_ensure_scope_vars(world, impl, ctx, term)) { goto error; } if (term->oper == EcsNot) { ctx->scope_is_not |= (ecs_flags32_t)(1ull << ctx->scope); flecs_query_begin_block(EcsQueryNot, ctx); } else { ctx->scope_is_not &= (ecs_flags32_t)~(1ull << ctx->scope); } flecs_query_compile_push(ctx); } else if (ECS_TERM_REF_ID(&term->first) == EcsScopeClose) { flecs_query_compile_pop(ctx); if (ctx->scope_is_not & (ecs_flags32_t)(1ull << (ctx->scope))) { flecs_query_end_block(ctx, false); } } else { /* Noop */ } return 0; error: return -1; } static ecs_flags32_t flecs_query_to_table_flags( const ecs_query_t *q) { ecs_flags32_t query_flags = q->flags; if (!(query_flags & EcsQueryMatchDisabled) || !(query_flags & EcsQueryMatchPrefab)) { ecs_flags32_t table_flags = EcsTableNotQueryable; if (!(query_flags & EcsQueryMatchDisabled)) { table_flags |= EcsTableIsDisabled; } if (!(query_flags & EcsQueryMatchPrefab)) { table_flags |= EcsTableIsPrefab; } return table_flags; } return 0; } static bool flecs_query_select_all( const ecs_query_t *q, ecs_term_t *term, ecs_query_op_t *op, ecs_var_id_t src_var, ecs_query_compile_ctx_t *ctx) { bool builtin_pred = flecs_term_is_builtin_pred(term); bool pred_match = builtin_pred && ECS_TERM_REF_ID(&term->first) == EcsPredMatch; if (term->oper == EcsNot || term->oper == EcsOptional || term->oper == EcsNotFrom || pred_match) { ecs_query_op_t match_any = {0}; match_any.kind = 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); match_any.other = flecs_itolbl(flecs_query_to_table_flags(q)); flecs_query_op_insert(&match_any, ctx); flecs_query_write_ctx(op->src.var, ctx, false); /* Update write administration */ return true; } return false; } #ifdef FLECS_META static int flecs_query_compile_begin_member_term( ecs_world_t *world, ecs_term_t *term, ecs_query_compile_ctx_t *ctx, ecs_entity_t first_id) { ecs_assert(first_id != 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(first_id & EcsIsEntity, ECS_INTERNAL_ERROR, NULL); first_id = ECS_TERM_REF_ID(&term->first); /* First compile as if it's a regular term, to match the component */ term->flags_ &= (uint16_t)~EcsTermIsMember; /* Replace term id with member parent (the component) */ ecs_entity_t component = ecs_get_parent(world, first_id); if (!component) { ecs_err("member without parent in query"); return -1; } if (!ecs_has(world, component, EcsComponent)) { ecs_err("parent of member is not a component"); return -1; } bool second_wildcard = (ECS_TERM_REF_ID(&term->second) == EcsWildcard || ECS_TERM_REF_ID(&term->second) == EcsAny) && (term->second.id & EcsIsVariable) && !term->second.name; term->first.id = component | ECS_TERM_REF_FLAGS(&term->first); term->second.id = 0; term->id = component; ctx->oper = (ecs_oper_kind_t)term->oper; if (term->oper == EcsNot && !second_wildcard) { /* When matching a member term with not operator, we need to cover both * the case where an entity doesn't have the component, and where it * does have the component, but doesn't match the member. */ term->oper = EcsOptional; } return 0; } static int flecs_query_compile_end_member_term( ecs_world_t *world, ecs_query_impl_t *impl, ecs_query_op_t *op, ecs_term_t *term, ecs_query_compile_ctx_t *ctx, ecs_id_t term_id, ecs_entity_t first_id, ecs_entity_t second_id, bool cond_write) { ecs_entity_t component = ECS_TERM_REF_ID(&term->first); const EcsComponent *comp = ecs_get(world, component, EcsComponent); ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL); /* Restore term values */ term->id = term_id; term->first.id = first_id; term->second.id = second_id; term->flags_ |= EcsTermIsMember; term->oper = flecs_ito(int16_t, ctx->oper); first_id = ECS_TERM_REF_ID(&term->first); const EcsMember *member = ecs_get(world, first_id, EcsMember); ecs_assert(member != NULL, ECS_INTERNAL_ERROR, NULL); ecs_query_var_t *var = &impl->vars[op->src.var]; const char *var_name = flecs_term_ref_var_name(&term->src); ecs_var_id_t evar = flecs_query_find_var_id( impl, var_name, EcsVarEntity); bool second_wildcard = (ECS_TERM_REF_ID(&term->second) == EcsWildcard || ECS_TERM_REF_ID(&term->second) == EcsAny) && (term->second.id & EcsIsVariable) && !term->second.name; if (term->oper == EcsOptional) { second_wildcard = true; } ecs_query_op_t mbr_op = *op; mbr_op.kind = EcsQueryMemberEq; mbr_op.first.entity = /* Encode type size and member offset */ flecs_ito(uint32_t, member->offset) | (flecs_ito(uint64_t, comp->size) << 32); /* If this is a term with a Not operator, conditionally evaluate member on * whether term was set by previous operation (see begin_member_term). */ if (ctx->oper == EcsNot || ctx->oper == EcsOptional) { if (second_wildcard && ctx->oper == EcsNot) { /* A !(T.value, *) term doesn't need special operations */ return 0; } /* Resolve to entity variable before entering if block, so that we * don't have different branches of the query working with different * versions of the same variable. */ if (var->kind == EcsVarTable) { flecs_query_insert_each(op->src.var, evar, ctx, cond_write); var = &impl->vars[evar]; } ecs_query_op_t *if_op = flecs_query_begin_block(EcsQueryIfSet, ctx); if_op->other = term->field_index; if (ctx->oper == EcsNot) { mbr_op.kind = EcsQueryMemberNeq; } } if (var->kind == EcsVarTable) { /* If MemberEq is called on table variable, store it on .other member. * This causes MemberEq to do double duty as 'each' instruction, * which is faster than having to go back & forth between instructions * while finding matching values. */ mbr_op.other = flecs_itolbl(op->src.var + 1); /* Mark entity variable as written */ flecs_query_write_ctx(evar, ctx, cond_write); flecs_query_write(evar, &mbr_op.written); } flecs_query_compile_term_ref(world, impl, &mbr_op, &term->src, &mbr_op.src, EcsQuerySrc, EcsVarEntity, ctx, true); if (second_wildcard) { mbr_op.flags |= (EcsQueryIsEntity << EcsQuerySecond); mbr_op.second.entity = EcsWildcard; } else { flecs_query_compile_term_ref(world, impl, &mbr_op, &term->second, &mbr_op.second, EcsQuerySecond, EcsVarEntity, ctx, true); if (term->second.id & EcsIsVariable) { if (flecs_query_compile_ensure_vars(impl, &mbr_op, &mbr_op.second, EcsQuerySecond, ctx, cond_write, NULL)) { goto error; } flecs_query_write_ctx(mbr_op.second.var, ctx, cond_write); flecs_query_write(mbr_op.second.var, &mbr_op.written); } } flecs_query_op_insert(&mbr_op, ctx); if (ctx->oper == EcsNot || ctx->oper == EcsOptional) { flecs_query_end_block(ctx, false); } return 0; error: return -1; } #else static int flecs_query_compile_begin_member_term( ecs_world_t *world, ecs_term_t *term, ecs_query_compile_ctx_t *ctx, ecs_entity_t first_id) { (void)world; (void)term; (void)ctx; (void)first_id; return 0; } static int flecs_query_compile_end_member_term( ecs_world_t *world, ecs_query_impl_t *impl, ecs_query_op_t *op, ecs_term_t *term, ecs_query_compile_ctx_t *ctx, ecs_id_t term_id, ecs_entity_t first_id, ecs_entity_t second_id, bool cond_write) { (void)world; (void)impl; (void)op; (void)term; (void)ctx; (void)term_id; (void)first_id; (void)second_id; (void)cond_write; return 0; } #endif static void flecs_query_mark_last_or_op( ecs_query_compile_ctx_t *ctx) { ecs_query_op_t *op_ptr = ecs_vec_last_t(ctx->ops, ecs_query_op_t); op_ptr->next = FlecsRuleOrMarker; } static void flecs_query_set_op_kind( ecs_query_op_t *op, ecs_term_t *term, bool src_is_var) { /* 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; } } } int flecs_query_compile_term( ecs_world_t *world, ecs_query_impl_t *query, ecs_term_t *term, ecs_query_compile_ctx_t *ctx) { ecs_id_t term_id = term->id; ecs_entity_t first_id = term->first.id; ecs_entity_t second_id = term->second.id; bool toggle_term = (term->flags_ & EcsTermIsToggle) != 0; bool member_term = (term->flags_ & EcsTermIsMember) != 0; if (member_term) { flecs_query_compile_begin_member_term(world, term, ctx, first_id); } ecs_query_t *q = &query->pub; bool first_term = term == q->terms; bool first_is_var = term->first.id & EcsIsVariable; bool second_is_var = term->second.id & EcsIsVariable; bool src_is_var = term->src.id & EcsIsVariable; bool src_is_wildcard = src_is_var && (ECS_TERM_REF_ID(&term->src) == EcsWildcard || ECS_TERM_REF_ID(&term->src) == EcsAny); bool src_is_lookup = false; bool builtin_pred = flecs_term_is_builtin_pred(term); bool is_optional = (term->oper == EcsOptional); bool is_or = flecs_term_is_or(q, term); bool first_or = false, last_or = false; bool cond_write = term->oper == EcsOptional || is_or; ecs_query_op_t op = {0}; if (is_or) { first_or = first_term || (term[-1].oper != EcsOr); last_or = term->oper != EcsOr; } if (term->oper == EcsAndFrom || term->oper == EcsOrFrom || term->oper == EcsNotFrom) { const ecs_type_t *type = ecs_get_type(world, term->id); if (!type) { /* Empty type for id in *From operation is a noop */ ctx->skipped ++; return 0; } int32_t i, count = type->count; ecs_id_t *ti_ids = type->array; for (i = 0; i < count; i ++) { ecs_id_t ti_id = ti_ids[i]; ecs_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(&op, term, src_is_var); 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(q, term, &op, op.src.var, ctx); } /* A bit of special logic for OR expressions and equality predicates. If the * left-hand of an equality operator is a table, and there are multiple * operators in an Or expression, the Or chain should match all entities in * the table that match the right hand sides of the operator expressions. * For this to work, the src variable needs to be resolved as entity, as an * Or chain would otherwise only yield the first match from a table. */ if (src_is_var && src_written && (builtin_pred || member_term) && term->oper == EcsOr) { if (query->vars[op.src.var].kind == EcsVarTable) { flecs_query_compile_term_ref(world, query, &op, &term->src, &op.src, EcsQuerySrc, EcsVarEntity, ctx, true); ctx->ctrlflow->written_or |= (1llu << op.src.var); } } if (flecs_query_compile_ensure_vars( query, &op, &op.src, EcsQuerySrc, ctx, cond_write, NULL)) { goto error; } /* If source is Any (_) and first and/or second are unconstrained, insert an * ids instruction instead of an And */ if (term->flags_ & EcsTermMatchAnySrc) { op.kind = EcsQueryIds; /* Use up-to-date written values after potentially inserting each */ if (!first_written || !second_written) { if (!first_written) { /* 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) { op.other = flecs_itolbl(flecs_query_to_table_flags(q)); } /* After evaluating a term, a used variable is always written */ if (src_is_var) { flecs_query_write(op.src.var, &op.written); flecs_query_write_ctx(op.src.var, ctx, cond_write); } if (first_is_var) { flecs_query_write(op.first.var, &op.written); flecs_query_write_ctx(op.first.var, ctx, cond_write); } if (second_is_var) { flecs_query_write(op.second.var, &op.written); flecs_query_write_ctx(op.second.var, ctx, cond_write); } flecs_query_op_insert(&op, ctx); ctx->cur->lbl_query = flecs_itolbl(ecs_vec_count(ctx->ops) - 1); if (is_or && !member_term) { flecs_query_mark_last_or_op(ctx); } /* Handle self-references between src and first/second variables */ if (src_is_var) { if (first_is_var) { flecs_query_insert_contains(query, op.src.var, op.first.var, ctx); } if (second_is_var && 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) { return cache->cache.tables.count; } int32_t flecs_query_cache_entity_count( const ecs_query_cache_t *cache) { 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; if (match != insert_after) { 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 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->trs = flecs_balloc(&cache->allocators.trs); /* Insert match to iteration list if table is not empty */ flecs_query_cache_insert_table_node(cache, qm); return qm; } static void flecs_query_cache_set_table_match( ecs_query_cache_t *cache, ecs_query_cache_table_match_t *qm, ecs_iter_t *it) { ecs_query_t *query = cache->query; int8_t i, field_count = query->field_count; ecs_assert(field_count > 0, ECS_INTERNAL_ERROR, NULL); /* Reset resources in case this is an existing record */ ecs_os_memcpy_n(ECS_CONST_CAST(ecs_table_record_t**, qm->trs), it->trs, ecs_table_record_t*, field_count); /* Find out whether to store result-specific ids array or fixed array */ ecs_id_t *ids = cache->query->ids; for (i = 0; i < field_count; i ++) { if (it->ids[i] != ids[i]) { break; } } if (i != field_count) { if (qm->ids == ids || !qm->ids) { qm->ids = flecs_balloc(&cache->allocators.ids); } ecs_os_memcpy_n(qm->ids, it->ids, ecs_id_t, field_count); } else { if (qm->ids != ids) { flecs_bfree(&cache->allocators.ids, qm->ids); qm->ids = ids; } } /* Find out whether to store result-specific sources array or fixed array */ for (i = 0; i < field_count; i ++) { if (it->sources[i]) { break; } } if (i != field_count) { if (qm->sources == cache->sources || !qm->sources) { qm->sources = flecs_balloc(&cache->allocators.sources); } ecs_os_memcpy_n(qm->sources, it->sources, ecs_entity_t, field_count); } else { if (qm->sources != cache->sources) { flecs_bfree(&cache->allocators.sources, qm->sources); qm->sources = cache->sources; } } qm->set_fields = it->set_fields; qm->up_fields = it->up_fields; } 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; } 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_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, 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_dbg_3("query cache matched existing table [%s]", NULL); } ecs_query_cache_table_match_t *qm = flecs_query_cache_add_table_match(cache, qt, table); flecs_query_cache_set_table_match(cache, qm, &it); } } static bool flecs_query_cache_match_table( ecs_world_t *world, ecs_query_cache_t *cache, ecs_table_t *table) { if (!ecs_map_is_init(&cache->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 |= 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(cache, qm, &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; } /* Remove table */ static void flecs_query_cache_table_match_free( ecs_query_cache_t *cache, 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.trs, ECS_CONST_CAST(void*, cur->trs)); if (cur->ids != cache->query->ids) { flecs_bfree(&cache->allocators.ids, cur->ids); } if (cur->sources != cache->sources) { flecs_bfree(&cache->allocators.sources, cur->sources); } if (cur->monitor) { flecs_bfree(&cache->allocators.monitors, cur->monitor); } flecs_query_cache_remove_table_node(cache, cur); 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->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) { flecs_poly_assert(world, ecs_world_t); 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; } ecs_os_perf_trace_push("flecs.query.rematch"); cache->monitor_generation = world->monitor_generation; it = ecs_query_iter(world, cache->query); 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, 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(cache, qm, &it); if (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, 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); } ecs_os_perf_trace_pop("flecs.query.rematch"); } /* -- 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, 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, 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 == 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.trs, field_count * ECS_SIZEOF(ecs_table_record_t*)); flecs_ballocator_init(&cache->allocators.ids, field_count * ECS_SIZEOF(ecs_id_t)); flecs_ballocator_init(&cache->allocators.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.trs); 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); if (cache->query->term_count) { flecs_bfree(&cache->allocators.sources, cache->sources); } 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; flecs_poly_assert(world, ecs_world_t); ecs_stage_t *stage = impl->stage; flecs_poly_assert(stage, ecs_stage_t); ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); ecs_check(const_desc != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(const_desc->_canary == 0, ECS_INVALID_PARAMETER, "ecs_query_desc_t was not initialized to zero"); ecs_check(!(world->flags & EcsWorldFini), ECS_INVALID_OPERATION, "cannot create query during world fini"); /* Create private version of desc to create the uncached query that will * populate the query cache. */ ecs_query_desc_t desc = *const_desc; ecs_entity_t entity = desc.entity; desc.cache_kind = EcsQueryCacheNone; /* Don't create caches recursively */ desc.group_by_callback = NULL; desc.group_by = 0; desc.order_by_callback = NULL; desc.order_by = 0; desc.entity = 0; /* Don't pass ctx/binding_ctx to uncached query */ desc.ctx = NULL; desc.binding_ctx = NULL; desc.ctx_free = NULL; desc.binding_ctx_free = NULL; ecs_query_cache_t *result = flecs_bcalloc(&stage->allocators.query_cache); result->entity = entity; impl->cache = result; ecs_observer_desc_t observer_desc = { .query = desc }; observer_desc.query.flags |= EcsQueryNested; ecs_flags32_t query_flags = const_desc->flags | world->default_query_flags; desc.flags |= EcsQueryMatchEmptyTables | EcsQueryTableOnly | EcsQueryNested; /* order_by is not compatible with matching empty tables, as it causes * a query to return table slices, not entire tables. */ if (const_desc->order_by_callback) { query_flags &= ~EcsQueryMatchEmptyTables; } ecs_query_t *q = result->query = ecs_query_init(world, &desc); if (!q) { goto error; } /* 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); /* Zero'd out sources array that's used for results that only match $this. * This reduces the amount of memory used by the cache, and improves CPU * cache locality during iteration when doing source checks. */ if (result->query->term_count) { result->sources = flecs_bcalloc(&result->allocators.sources); } if (q->term_count) { observer_desc.run = flecs_query_cache_on_event; observer_desc.ctx = impl; int32_t event_index = 0; observer_desc.events[event_index ++] = EcsOnTableCreate; observer_desc.events[event_index ++] = EcsOnTableDelete; observer_desc.flags_ = EcsObserverBypassQuery; /* ecs_query_init could have moved away resources from the terms array * in the descriptor, so use the terms array from the query. */ ecs_os_memcpy_n(observer_desc.query.terms, q->terms, ecs_term_t, 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; } ecs_table_cache_init(world, &result->cache); flecs_query_cache_match_tables(world, result); if (const_desc->order_by_callback) { if (flecs_query_cache_order_by(world, impl, const_desc->order_by, const_desc->order_by_callback, const_desc->order_by_table_callback)) { goto error; } } if (entity) { if (!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 void flecs_query_update_node_up_trs( const ecs_query_run_ctx_t *ctx, ecs_query_cache_table_match_t *node) { ecs_termset_t fields = node->up_fields & node->set_fields; if (fields) { const ecs_query_impl_t *impl = ctx->query; const ecs_query_t *q = &impl->pub; ecs_query_cache_t *cache = impl->cache; int8_t *field_map = cache->field_map; int32_t i, field_count = q->field_count; for (i = 0; i < field_count; i ++) { if (!(fields & (1llu << i))) { continue; } ecs_entity_t src = node->sources[i]; if (src) { const ecs_table_record_t *tr = node->trs[i]; ecs_record_t *r = flecs_entities_get(ctx->world, src); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(r->table != NULL, ECS_INTERNAL_ERROR, NULL); if (r->table != tr->hdr.table) { ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; ecs_assert(idr->id == q->ids[field_map ? field_map[i] : i], ECS_INTERNAL_ERROR, NULL); tr = node->trs[i] = flecs_id_record_get_table(idr, r->table); if (field_map) { ctx->it->trs[field_map[i]] = tr; } } } } } } static ecs_query_cache_table_match_t* flecs_query_cache_next( const ecs_query_run_ctx_t *ctx, bool match_empty) { ecs_iter_t *it = ctx->it; ecs_query_iter_t *qit = &it->priv_.iter.query; repeat: { 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; qit->node = node->next; qit->prev = node; if (node) { if (!ecs_table_count(node->table)) { if (!match_empty) { if (ctx->query->pub.flags & EcsQueryHasMonitor) { flecs_query_sync_match_monitor( flecs_query_impl(qit->query), node); } goto repeat; } } } 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_cache_next(ctx, true); } 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 = cache->field_map; for (i = 0; i < field_count; i ++) { int8_t field_index = field_map[i]; it->trs[field_index] = node->trs[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_cache_next(ctx, ctx->query->pub.flags & EcsQueryMatchEmptyTables); 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; flecs_query_update_node_up_trs(ctx, node); 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_cache_next(ctx, ctx->query->pub.flags & EcsQueryMatchEmptyTables); if (!node) { return false; } ecs_iter_t *it = ctx->it; it->trs = node->trs; it->ids = node->ids; it->sources = node->sources; it->set_fields = node->set_fields; it->up_fields = node->up_fields; flecs_query_update_node_up_trs(ctx, node); 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); flecs_query_update_node_up_trs(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->trs = node->trs; it->ids = node->ids; it->sources = node->sources; flecs_query_update_node_up_trs(ctx, node); 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) { int32_t count = ecs_table_count(table); if (!count) { /* Nothing to sort */ return; } if (count < 2) { return; } ecs_entity_t *entities = table->data.entities; void *ptr = NULL; int32_t size = 0; if (column_index != -1) { ecs_column_t *column = &table->data.columns[column_index]; ecs_type_info_t *ti = column->ti; size = ti->size; ptr = column->data; } if (sort) { sort(world, table, entities, ptr, size, 0, count - 1, compare); } else { flecs_query_cache_sort_table_generic( world, table, entities, ptr, size, 0, count - 1, compare); } } /* Helper struct for building sorted table ranges */ typedef struct sort_helper_t { ecs_query_cache_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; flecs_poly_assert(world, ecs_world_t); ecs_assert(!(world->flags & EcsWorldMultiThreaded), ECS_UNSUPPORTED, "cannot sort query in multithreaded mode"); ecs_entity_t id = cache->order_by; ecs_order_by_action_t compare = cache->order_by_callback; int32_t 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; if (ecs_table_count(table) == 0) { continue; } if (id) { const ecs_term_t *term = &cache->query->terms[order_by_term]; int32_t field = term->field_index; ecs_size_t size = cache->query->sizes[field]; ecs_entity_t src = cur->sources[field]; if (src == 0) { int32_t column_index = cur->trs[field]->column; ecs_column_t *column = &table->data.columns[column_index]; helper[to_sort].ptr = column->data; helper[to_sort].elem_size = size; helper[to_sort].shared = false; } else { ecs_record_t *r = flecs_entities_get(world, src); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(r->table != NULL, ECS_INTERNAL_ERROR, NULL); if (term->src.id & EcsUp) { ecs_entity_t base = 0; ecs_search_relation(world, r->table, 0, id, EcsIsA, term->src.id & EcsTraverseFlags, &base, 0, 0); if (base && base != src) { /* Component could be inherited */ r = flecs_entities_get(world, base); } } helper[to_sort].ptr = ecs_table_get_id( world, r->table, id, ECS_RECORD_TO_ROW(r->row)); helper[to_sort].elem_size = size; helper[to_sort].shared = true; } ecs_assert(helper[to_sort].ptr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(helper[to_sort].elem_size != 0, ECS_INTERNAL_ERROR, NULL); } else { helper[to_sort].ptr = NULL; helper[to_sort].elem_size = 0; helper[to_sort].shared = false; } helper[to_sort].match = cur; helper[to_sort].entities = table->data.entities; helper[to_sort].row = 0; helper[to_sort].count = ecs_table_count(table); to_sort ++; } if (!to_sort) { goto done; } 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->trs != cur_helper->match->trs) { 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]; } if (nodes) { nodes[0].prev = NULL; nodes[i - 1].next = NULL; } done: flecs_free_n(&world->allocator, sort_helper_t, table_count, helper); } void flecs_query_cache_build_sorted_tables( ecs_query_cache_t *cache) { ecs_vec_clear(&cache->table_slices); 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_all_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; if (!ecs_table_count(table)) { /* If table is empty, there's a chance the query won't iterate it * so update the match monitor here. */ ecs_query_cache_table_match_t *cur, *next; for (cur = qt->first; cur != NULL; cur = next) { flecs_query_sync_match_monitor(impl, cur); next = cur->next_match; } } } 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); (void)q; const ecs_table_record_t *tr = match->trs[field]; ecs_table_t *table = tr->hdr.table; int32_t column = 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; } 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->real_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_all_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; } } const ecs_query_t *query = cache->query; ecs_world_t *world = query->world; int32_t i, field_count = query->field_count; ecs_entity_t *sources = match->sources; const ecs_table_record_t **trs = it ? it->trs : match->trs; ecs_flags64_t set_fields = it ? it->set_fields : match->set_fields; ecs_assert(trs != NULL, ECS_INTERNAL_ERROR, NULL); 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 = trs[i]->column; 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 */ } } /* Component from non-this 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); 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->real_world; int16_t i, field_count = q->field_count; for (i = 0; i < field_count; i ++) { ecs_termset_t field_bit = (ecs_termset_t)(1u << i); if (!(write_fields & field_bit)) { continue; /* If term doesn't write data there's nothing to track */ } ecs_entity_t src = it->sources[i]; ecs_table_t *table; if (!src) { table = it->table; } else { ecs_record_t *r = flecs_entities_get(world, src); if (!r || !(table = r->table)) { continue; } if (q->shared_readonly_fields & flecs_ito(uint32_t, 1 << i)) { /* Shared fields that aren't marked explicitly as out/inout * default to readonly */ continue; } } int32_t type_index = it->trs[i]->index; ecs_assert(type_index >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); int32_t *dirty_state = table->dirty_state; if (!dirty_state) { continue; } ecs_assert(type_index < table->type.count, ECS_INTERNAL_ERROR, NULL); int32_t column = table->column_map[type_index]; dirty_state[column + 1] ++; } } 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->real_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->trs[i]->column >= 0, ECS_INTERNAL_ERROR, NULL); int32_t column = table->column_map[it->trs[i]->column]; 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); /* Query for cache should never point to stage */ ecs_assert(q->world == q->real_world, ECS_INTERNAL_ERROR, NULL); 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 (!(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); 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)); } 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; 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); op_ctx->it.cur = &tr->hdr; } 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); } } 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_tr(it, field, tr); return true; } 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_tr(it, field, tr); return true; } 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_and_any( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { ecs_flags16_t match_flags = op->match_flags; if (redo) { if (match_flags & EcsTermMatchAnySrc) { return false; } } uint64_t written = ctx->written[ctx->op_index]; int32_t remaining = 1; bool result; if (flecs_ref_is_written(op, &op->src, EcsQuerySrc, written)) { result = flecs_query_with(op, redo, ctx); } else { result = flecs_query_select(op, redo, ctx); remaining = 0; } ecs_query_and_ctx_t *op_ctx = flecs_op_ctx(ctx, and); if (match_flags & EcsTermMatchAny && op_ctx->remaining) { op_ctx->remaining = flecs_ito(int16_t, remaining); } int32_t field = op->field_index; if (field != -1) { ctx->it->ids[field] = flecs_query_op_get_id(op, ctx); } ctx->it->trs[field] = (ecs_table_record_t*)op_ctx->it.cur; 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_w_id(op, redo, ctx, EcsAny, (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled)); } } static bool flecs_query_triv( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { ecs_query_trivial_ctx_t *op_ctx = flecs_op_ctx(ctx, trivial); ecs_flags64_t termset = op->src.entity; uint64_t written = ctx->written[ctx->op_index]; ctx->written[ctx->op_index + 1] |= 1ull; if (written & 1ull) { flecs_query_set_iter_this(ctx->it, ctx); return flecs_query_trivial_test(ctx, redo, termset); } else { return flecs_query_trivial_search(ctx, op_ctx, redo, termset); } } 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_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 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; ecs_entity_t type_id; int32_t i; if (!redo) { /* Find entity that acts as the template from which we match the ids */ type_id = flecs_query_op_get_id(op, ctx); op_ctx->type_id = type_id; ecs_assert(ecs_is_alive(world, type_id), ECS_INTERNAL_ERROR, NULL); ecs_record_t *r = flecs_entities_get(world, type_id); ecs_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_id = op_ctx->type_id; type = op_ctx->type; } ecs_id_t *ids = type->array; /* Check if source is variable, and if it's already written */ bool src_written = true; if (op->flags & (EcsQueryIsVar << EcsQuerySrc)) { uint64_t written = ctx->written[ctx->op_index]; src_written = written & (1ull << op->src.var); } do { int32_t id_index = op_ctx->cur_id_index; /* If source is not yet written, find tables with first id */ if (!src_written) { ecs_entity_t first_id = ids[id_index]; if (!flecs_query_select_w_id(op, redo, ctx, first_id, (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled))) { if (oper == EcsOrFrom) { id_index = flecs_query_next_inheritable_id( world, type, id_index + 1); if (id_index != -1) { op_ctx->cur_id_index = id_index; redo = false; continue; } } return false; } id_index ++; /* First id got matched */ } else if (redo && src_written) { return false; } ecs_table_t *src_table = flecs_query_get_table( op, &op->src, EcsQuerySrc, ctx); if (!src_table) { continue; } redo = true; if (!src_written && oper == EcsOrFrom) { /* Eliminate duplicate matches from tables that have multiple * components from the type list */ if (op_ctx->cur_id_index != op_ctx->first_id_index) { for (i = op_ctx->first_id_index; i < op_ctx->cur_id_index; i ++) { ecs_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; } } goto match; } 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) { goto match; /* Single match is enough */ } } } if (i == type->count) { if (oper == EcsAndFrom || oper == EcsNotFrom) { break; /* All ids matched */ } } } } while (true); match: ctx->it->ids[op->field_index] = type_id; return true; } static bool flecs_query_and_from( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { return flecs_query_x_from(op, redo, ctx, EcsAndFrom); } static bool flecs_query_not_from( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { return flecs_query_x_from(op, redo, ctx, EcsNotFrom); } static bool flecs_query_or_from( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { return flecs_query_x_from(op, redo, ctx, EcsOrFrom); } static bool flecs_query_ids( 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->trs[op->field_index] = NULL; /* Mark field as set */ } return true; } static bool flecs_query_idsright( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { ecs_query_ids_ctx_t *op_ctx = flecs_op_ctx(ctx, ids); ecs_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) { if (!ecs_table_count(table)) { return false; } row = op_ctx->row = range.offset; } else { int32_t end = range.count; if (end) { end += range.offset; } else { end = ecs_table_count(table); } row = ++ op_ctx->row; if (op_ctx->row >= end) { return false; } } ecs_assert(row < ecs_table_count(table), ECS_INTERNAL_ERROR, NULL); const ecs_entity_t *entities = ecs_table_entities(table); flecs_query_var_set_entity(op, op->src.var, entities[row], ctx); return true; } static bool flecs_query_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; } it->trs[field] = NULL; /* Reset variables */ if (flags_1st & EcsQueryIsVar) { if (!flecs_ref_is_written(op, &op->first, EcsQueryFirst, written_cur)){ flecs_query_var_reset(op->first.var, ctx); } } if (flags_2nd & EcsQueryIsVar) { if (!flecs_ref_is_written(op, &op->second, EcsQuerySecond, written_cur)){ flecs_query_var_reset(op->second.var, ctx); } } /* If term has entity src, set it because no other instruction might */ if (op->flags & (EcsQueryIsEntity << EcsQuerySrc)) { it->sources[field] = op->src.entity; } 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]; } } int16_t field_index = op->field_index; ecs_id_t prev_id = it->ids[field_index]; const ecs_table_record_t *prev_tr = it->trs[field_index]; 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; } /* Restore id in case op set it */ it->ids[field_index] = prev_id; it->trs[field_index] = prev_tr; 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; int8_t field_index = flecs_ito(int8_t, op->other); ecs_query_ctrl_ctx_t *op_ctx = flecs_op_ctx(ctx, ctrl); if (!redo) { op_ctx->is_set = ecs_field_is_set(it, field_index); } if (!op_ctx->is_set) { return !redo; } return flecs_query_run_block(redo, ctx, op_ctx); } static bool flecs_query_end( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { (void)op; (void)ctx; return !redo; } static bool flecs_query_dispatch( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { switch(op->kind) { case EcsQueryAnd: return flecs_query_and(op, redo, ctx); case EcsQueryAndAny: return flecs_query_and_any(op, redo, ctx); case EcsQueryTriv: return flecs_query_triv(op, redo, ctx); case EcsQueryCache: return flecs_query_cache(op, redo, ctx); case EcsQueryIsCache: return flecs_query_is_cache(op, redo, ctx); case EcsQueryOnlyAny: return flecs_query_only_any(op, redo, ctx); case EcsQueryUp: return flecs_query_up(op, redo, ctx); case EcsQuerySelfUp: return flecs_query_self_up(op, redo, ctx); case EcsQueryWith: return flecs_query_with(op, redo, ctx); case 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 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 &= ~(EcsIterTrivialTest|EcsIterTrivialCached| 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; 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 |= EcsIterTrivialTest; } } else if (flags & EcsQueryIsCacheable) { it->flags |= EcsIterTrivialTest|EcsIterTrivialCached; } } else { if (!cache) { if (!(flags & EcsQueryMatchWildcards)) { it->flags |= EcsIterTrivialSearch; } } else if (flags & EcsQueryIsCacheable) { if (!cache->order_by_callback) { 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(NULL, false, &ctx); } } } bool ecs_query_next( 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_search(&ctx)) { goto trivial_search_yield; } } else if (it->flags & EcsIterTrivialTest) { if (flecs_query_is_cache_test(&ctx, redo)) { goto yield; } } } else { /* Uncached iterator modes */ if (it->flags & EcsIterTrivialSearch) { ecs_query_trivial_ctx_t *op_ctx = &ctx.op_ctx[0].is.trivial; if (flecs_query_is_trivial_search(&ctx, op_ctx, redo)) { goto yield; } } else if (it->flags & EcsIterTrivialTest) { int32_t fields = ctx.query->pub.term_count; ecs_flags64_t mask = (2llu << (fields - 1)) - 1; if (flecs_query_trivial_test(&ctx, redo, mask)) { goto yield; } } else { /* 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 = ecs_table_entities(it->table); yield: return true; } 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 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; int32_t op_count = impl->op_count ? impl->op_count : 1; 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.ref_fields = q->fixed_fields | q->row_fields | q->var_fields; it.row_fields = q->row_fields; it.up_fields = 0; flecs_query_apply_iter_flags(&it, q); flecs_iter_init(it.world, &it, flecs_iter_cache_ids | flecs_iter_cache_trs | 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); if (ecs_vec_count(&cache->table_slices)) { 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); /* Ok, only for stats */ ecs_os_linc(&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 (!(ecs_world_get_flags(world) & 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. */ data = op_ctx->data = ecs_table_get_column(range.table, it->trs[field_index]->column, 0); it->ids[field_index] = ctx->query->pub.terms[op->term_index].id; } else { row = ++ op_ctx->each.row; if (op_ctx->each.row >= end) { return false; } data = op_ctx->data; } int32_t offset = (int32_t)op->first.entity; int32_t size = (int32_t)(op->first.entity >> 32); const ecs_entity_t *entities = ecs_table_entities(table); ecs_entity_t e = 0; ecs_entity_t *val; ecs_assert(row < ecs_table_count(table), ECS_INTERNAL_ERROR, NULL); ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); /* Must be written */ ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL); bool second_written = true; if (op->flags & (EcsQueryIsVar << EcsQuerySecond)) { uint64_t written = ctx->written[ctx->op_index]; second_written = written & (1ull << op->second.var); } if (second_written) { ecs_flags16_t second_flags = flecs_query_ref_flags( op->flags, EcsQuerySecond); ecs_entity_t second = flecs_get_ref_entity( &op->second, second_flags, ctx); do { e = entities[row]; val = ECS_OFFSET(ECS_ELEM(data, size, row), offset); if (val[0] == second || second == EcsWildcard) { if (!neq) { goto match; } } else { if (neq) { goto match; } } row ++; } while (row < end); return false; } else { e = entities[row]; val = ECS_OFFSET(ECS_ELEM(data, size, row), offset); flecs_query_var_set_entity(op, op->second.var, val[0], ctx); } match: if (op->other) { ecs_assert(e != 0, ECS_INTERNAL_ERROR, NULL); flecs_query_var_set_entity(op, op->src.var, e, ctx); } ecs_entity_t mbr = ECS_PAIR_FIRST(it->ids[field_index]); it->ids[field_index] = ecs_pair(mbr, val[0]); op_ctx->each.row = row; return true; } bool flecs_query_member_eq( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { return flecs_query_member_cmp(op, redo, ctx, false); } bool flecs_query_member_neq( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { return flecs_query_member_cmp(op, redo, ctx, true); } /** * @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_table_entities(r.table)[r.offset]; } return true; } else { ecs_table_range_t l = flecs_query_get_range( op, &op->src, EcsQuerySrc, ctx); if (!flecs_query_compare_range(&l, &r)) { return false; } ctx->vars[src_var].range.offset = r.offset; ctx->vars[src_var].range.count = r.count; return true; } } bool flecs_query_pred_eq( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { uint64_t written = ctx->written[ctx->op_index]; (void)written; ecs_assert(flecs_ref_is_written(op, &op->second, EcsQuerySecond, written), ECS_INTERNAL_ERROR, "invalid instruction sequence: uninitialized eq operand"); ecs_table_range_t r = flecs_query_get_range( op, &op->second, EcsQuerySecond, ctx); return flecs_query_pred_eq_w_range(op, redo, ctx, r); } bool flecs_query_pred_eq_name( const ecs_query_op_t *op, bool redo, ecs_query_run_ctx_t *ctx) { const char *name = flecs_query_name_arg(op, ctx); ecs_entity_t e = ecs_lookup(ctx->world, name); if (!e) { /* Entity doesn't exist */ return false; } ecs_table_range_t r = flecs_range_from_entity(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; 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); const ecs_entity_t *entities = ecs_table_entities(table); int32_t count = range->count; if (!count) { count = ecs_table_count(table); } int32_t i = range->offset, end = i + count; for (; i < end; i ++) { if (entities[i] == second) { /* Even though table doesn't have the specific relationship * pair, the relationship is reflexive and the target entity * is stored in the table. */ break; } } if (i == end) { /* Table didn't contain target entity */ return false; } if (count > 1) { /* If the range contains more than one entity, set the range to * return only the entity matched by the reflexive property. */ ecs_assert(flecs_query_ref_flags(op->flags, EcsQuerySrc) & EcsQueryIsVar, ECS_INTERNAL_ERROR, NULL); ecs_var_t *var = &ctx->vars[op->src.var]; ecs_table_range_t *var_range = &var->range; var_range->offset = i; var_range->count = 1; var->entity = entities[i]; } flecs_query_set_trav_match(op, NULL, trav, second, ctx); return true; } static bool flecs_query_trav_unknown_src_reflexive( const ecs_query_op_t *op, const ecs_query_run_ctx_t *ctx, ecs_entity_t trav, ecs_entity_t second) { ecs_assert(flecs_query_ref_flags(op->flags, EcsQuerySrc) & EcsQueryIsVar, ECS_INTERNAL_ERROR, NULL); ecs_var_id_t src_var = op->src.var; flecs_query_var_set_entity(op, src_var, second, ctx); flecs_query_var_get_table(src_var, ctx); ecs_table_t *table = ctx->vars[src_var].range.table; if (table) { if (flecs_query_table_filter(table, op->other, (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled))) { return false; } } flecs_query_set_trav_match(op, NULL, trav, second, ctx); return true; } static bool flecs_query_trav_fixed_src_up_fixed_second( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { if (redo) { return false; /* If everything's fixed, can only have a single result */ } ecs_flags16_t f_1st = flecs_query_ref_flags(op->flags, EcsQueryFirst); ecs_flags16_t f_2nd = flecs_query_ref_flags(op->flags, EcsQuerySecond); ecs_flags16_t f_src = flecs_query_ref_flags(op->flags, EcsQuerySrc); ecs_entity_t trav = flecs_get_ref_entity(&op->first, f_1st, ctx); ecs_entity_t second = flecs_get_ref_entity(&op->second, f_2nd, ctx); ecs_table_range_t range = flecs_get_ref_range(&op->src, f_src, ctx); ecs_table_t *table = range.table; /* Check if table has transitive relationship by traversing upwards */ ecs_table_record_t *tr = NULL; ecs_search_relation(ctx->world, table, 0, ecs_pair(trav, second), trav, EcsSelf|EcsUp, NULL, NULL, &tr); if (!tr) { if (op->match_flags & EcsTermReflexive) { return flecs_query_trav_fixed_src_reflexive(op, ctx, &range, trav, second); } else { return false; } } flecs_query_set_trav_match(op, tr, trav, second, ctx); return true; } static bool flecs_query_trav_unknown_src_up_fixed_second( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { ecs_flags16_t f_1st = flecs_query_ref_flags(op->flags, EcsQueryFirst); ecs_flags16_t f_2nd = flecs_query_ref_flags(op->flags, EcsQuerySecond); ecs_entity_t trav = flecs_get_ref_entity(&op->first, f_1st, ctx); ecs_entity_t second = flecs_get_ref_entity(&op->second, f_2nd, ctx); ecs_query_trav_ctx_t *trav_ctx = flecs_op_ctx(ctx, trav); if (!redo) { ecs_record_t *r_second = flecs_entities_get(ctx->world, second); bool traversable = r_second && r_second->row & EcsEntityIsTraversable; bool reflexive = op->match_flags & EcsTermReflexive; if (!traversable && !reflexive) { trav_ctx->cache.id = 0; /* If there's no record for the entity, it can't have a subtree so * forward operation to a regular select. */ return flecs_query_select(op, redo, ctx); } /* Entity is traversable, which means it could have a subtree */ flecs_query_get_trav_down_cache(ctx, &trav_ctx->cache, trav, second); trav_ctx->index = 0; if (op->match_flags & EcsTermReflexive) { trav_ctx->index = -1; if(flecs_query_trav_unknown_src_reflexive( op, ctx, trav, second)) { /* It's possible that we couldn't return the entity required for * reflexive matching, like when it's a prefab or disabled. */ return true; } } } else { if (!trav_ctx->cache.id) { /* No traversal cache, which means this is a regular select */ return flecs_query_select(op, redo, ctx); } } if (trav_ctx->index == -1) { redo = false; /* First result after handling reflexive relationship */ trav_ctx->index = 0; } /* Forward to select */ int32_t count = ecs_vec_count(&trav_ctx->cache.entities); ecs_trav_elem_t *elems = ecs_vec_first(&trav_ctx->cache.entities); for (; trav_ctx->index < count; trav_ctx->index ++) { ecs_trav_elem_t *el = &elems[trav_ctx->index]; trav_ctx->and.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_table_entities(range->table)[trav_ctx->index]; flecs_query_set_trav_match(op, NULL, trav, entity, ctx); /* Hijack existing variable to return one result at a time */ if (src_is_var) { ecs_var_id_t src_var = op->src.var; ecs_table_t *table = vars[src_var].range.table; ecs_assert(!table || table == ecs_get_table(ctx->world, entity), ECS_INTERNAL_ERROR, NULL); (void)table; vars[src_var].entity = entity; vars[src_var].range = flecs_range_from_entity(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->tr, trav, el->entity, ctx); return true; } bool flecs_query_trav( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { uint64_t written = ctx->written[ctx->op_index]; 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 = ecs_table_entities(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 = ecs_table_entities(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.c * @brief Query engine implementation. */ /* Find tables with requested component that has traversable entities. */ static bool flecs_query_up_select_table( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx, ecs_query_up_select_trav_kind_t trav_kind, ecs_query_up_select_kind_t kind) { ecs_query_up_ctx_t *op_ctx = flecs_op_ctx(ctx, up); ecs_iter_t *it = ctx->it; bool self = trav_kind == FlecsQueryUpSelectSelfUp; ecs_table_range_t range; do { bool result; if (kind == FlecsQueryUpSelectId) { result = flecs_query_select_id(op, redo, ctx, 0); } else if (kind == FlecsQueryUpSelectDefault) { result = flecs_query_select_w_id(op, redo, ctx, op_ctx->with, 0); } else if (kind == FlecsQueryUpSelectUnion) { result = flecs_query_union_select(op, redo, ctx); } else { ecs_abort(ECS_INTERNAL_ERROR, NULL); } if (!result) { /* No remaining tables with component found. */ return false; } redo = true; range = flecs_query_get_range(op, &op->src, EcsQuerySrc, ctx); ecs_assert(range.table != NULL, ECS_INTERNAL_ERROR, NULL); /* Keep searching until we find a table that has the requested component, * with traversable entities */ } while (!self && range.table->_->traversable_count == 0); if (!range.count) { range.count = ecs_table_count(range.table); } 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]; return true; } /* Find next traversable entity in table. */ static ecs_trav_down_t* flecs_query_up_find_next_traversable( const ecs_query_op_t *op, const ecs_query_run_ctx_t *ctx, ecs_query_up_select_trav_kind_t trav_kind) { ecs_query_up_ctx_t *op_ctx = flecs_op_ctx(ctx, up); ecs_world_t *world = ctx->world; ecs_iter_t *it = ctx->it; const ecs_query_t *q = &ctx->query->pub; ecs_table_t *table = op_ctx->table; bool self = trav_kind == FlecsQueryUpSelectSelfUp; if (table->_->traversable_count == 0) { /* No traversable entities in table */ op_ctx->table = NULL; return NULL; } else { int32_t row; ecs_entity_t entity = 0; const ecs_entity_t *entities = ecs_table_entities(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) { /* Found traversable entity */ it->sources[op->field_index] = entity; break; } } if (row == op_ctx->end) { /* No traversable entities remaining in table */ op_ctx->table = NULL; return NULL; } op_ctx->row = row; /* Get down cache entry for traversable entity */ bool match_empty = (q->flags & EcsQueryMatchEmptyTables) != 0; 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; } return op_ctx->down; } /* Select all tables that can reach the target component through the traversal * relationship. */ bool flecs_query_up_select( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx, ecs_query_up_select_trav_kind_t trav_kind, ecs_query_up_select_kind_t kind) { ecs_query_up_ctx_t *op_ctx = flecs_op_ctx(ctx, up); ecs_iter_t *it = ctx->it; bool redo_select = redo; const ecs_query_t *q = &ctx->query->pub; bool self = trav_kind == FlecsQueryUpSelectSelfUp; op_ctx->trav = q->terms[op->term_index].trav; /* Reuse id record from previous iteration if possible*/ if (!op_ctx->idr_trav) { op_ctx->idr_trav = flecs_id_record_get(ctx->world, ecs_pair(op_ctx->trav, EcsWildcard)); } /* If id record is not found, or if it doesn't have any tables, revert to * iterating owned components (no traversal) */ if (!op_ctx->idr_trav || !flecs_table_cache_count(&op_ctx->idr_trav->cache)) { if (!self) { /* If operation does not match owned components, return false */ 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) { /* Get component id to match */ op_ctx->with = flecs_query_op_get_id(op, ctx); /* Get id record for component to match */ op_ctx->idr_with = flecs_id_record_get(ctx->world, op_ctx->with); if (!op_ctx->idr_with) { /* If id record does not exist, there can't be any results */ return false; } op_ctx->down = NULL; op_ctx->cache_elem = 0; } /* Get last used entry from down traversal cache. Cache entries in the down * traversal cache contain a list of tables that can reach the requested * component through the traversal relationship, for a traversable entity * which acts as the key for the cache. */ ecs_trav_down_t *down = op_ctx->down; next_down_entry: /* Get (next) entry in down traversal cache */ while (!down) { ecs_table_t *table = op_ctx->table; /* Get (next) table with traversable entities that have the * requested component. We'll traverse downwards from the * traversable entities in the table to find all entities that can * reach the component through the traversal relationship. */ if (!table) { /* Reset source, in case we have to return a component matched * by the entity in the found table. */ it->sources[op->field_index] = 0; if (!flecs_query_up_select_table( op, redo_select, ctx, trav_kind, kind)) { return false; } table = op_ctx->table; /* If 'self' is true, we're evaluating a term with self|up. This * means that before traversing downwards, we should also return * the current table as result. */ if (self) { if (!flecs_query_table_filter(table, op->other, (EcsTableNotQueryable|EcsTableIsPrefab|EcsTableIsDisabled))) { flecs_reset_source_set_flag(it, op->field_index); op_ctx->row --; return true; } } redo_select = true; } else { /* Evaluate next entity in table */ op_ctx->row ++; } /* Get down cache entry for next traversable entity in table */ down = flecs_query_up_find_next_traversable(op, ctx, trav_kind); if (!down) { goto next_down_entry; } } next_down_elem: /* Get next element (table) in cache entry */ if ((++ op_ctx->cache_elem) >= ecs_vec_count(&down->elems)) { /* No more elements in cache entry, find next.*/ down = NULL; goto next_down_entry; } ecs_trav_down_elem_t *elem = ecs_vec_get_t( &down->elems, ecs_trav_down_elem_t, 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))) { /* Go to next table if table contains prefabs, disabled entities or * entities that are not queryable. */ goto next_down_elem; } flecs_set_source_set_flag(it, op->field_index); return true; } /* Check if a table can reach the target component through the traversal * relationship. */ bool flecs_query_up_with( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx) { const ecs_query_t *q = &ctx->query->pub; ecs_query_up_ctx_t *op_ctx = flecs_op_ctx(ctx, up); ecs_iter_t *it = ctx->it; 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 there are no tables with traversable relationship, there are no * matches. */ 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 id record for component doesn't exist, there are no matches */ if (!op_ctx->idr_with) { return false; } /* Get the range (table) that is currently being evaluated. In most * cases the range will cover the entire table, but in some cases it * can only cover a subset of the entities in the table. */ ecs_table_range_t range = flecs_query_get_range( op, &op->src, EcsQuerySrc, ctx); if (!range.table) { return false; } /* Get entry from up traversal cache. The up traversal cache contains * the entity on which the component was found, with additional metadata * on where it is stored. */ 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) { /* Component is not reachable from table */ return false; } it->sources[op->field_index] = flecs_entities_get_alive( ctx->world, up->src); it->trs[op->field_index] = up->tr; 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 { /* The table either can or can't reach the component, nothing to do for * a second evaluation of this operation.*/ return false; } } /* Check if a table can reach the target component through the traversal * relationship, or if the table has the target component itself. */ bool flecs_query_self_up_with( const ecs_query_op_t *op, bool redo, const ecs_query_run_ctx_t *ctx, bool id_only) { if (!redo) { bool result; if (id_only) { /* Simple id, no wildcards */ 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) { /* Table has component, no need to traverse*/ 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) { /* Matching self, so set sources to 0 */ ecs_iter_t *it = ctx->it; it->sources[op->field_index] = 0; } return true; } /* Table doesn't have component, traverse relationship */ 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) { /* If matching components without traversing, make sure to still * match remaining components that match the id (wildcard). */ return flecs_query_with(op, redo, ctx); } } return false; } /** * @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_table_entities(table); if (it->entities) { it->entities += it->offset; } } else if (count == 1) { it->count = 1; it->entities = &ctx->vars[0].entity; } } ecs_query_op_ctx_t* flecs_op_ctx_( const ecs_query_run_ctx_t *ctx) { return &ctx->op_ctx[ctx->op_index]; } #define flecs_op_ctx(ctx, op_kind) (&flecs_op_ctx_(ctx)->is.op_kind) void flecs_reset_source_set_flag( ecs_iter_t *it, int32_t field_index) { ecs_assert(field_index != -1, ECS_INTERNAL_ERROR, NULL); ECS_TERMSET_CLEAR(it->up_fields, 1u << field_index); } void flecs_set_source_set_flag( ecs_iter_t *it, int32_t field_index) { ecs_assert(field_index != -1, ECS_INTERNAL_ERROR, NULL); ECS_TERMSET_SET(it->up_fields, 1u << field_index); } ecs_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; const ecs_entity_t *entities = ecs_table_entities(table); var->entity = entities[var->range.offset]; return var->entity; } void flecs_query_var_reset( ecs_var_id_t var_id, const ecs_query_run_ctx_t *ctx) { ctx->vars[var_id].entity = EcsWildcard; ctx->vars[var_id].range.table = NULL; } void flecs_query_var_set_range( const ecs_query_op_t *op, ecs_var_id_t var_id, ecs_table_t *table, int32_t offset, int32_t count, const ecs_query_run_ctx_t *ctx) { (void)op; ecs_assert(ctx->query_vars[var_id].kind == EcsVarTable, ECS_INTERNAL_ERROR, NULL); ecs_assert(flecs_query_is_written(var_id, op->written), ECS_INTERNAL_ERROR, NULL); ecs_var_t *var = &ctx->vars[var_id]; var->entity = 0; var->range = (ecs_table_range_t){ .table = table, .offset = offset, .count = count }; } void flecs_query_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 = ecs_table_entities(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_tr( ecs_iter_t *it, int32_t field_index, const ecs_table_record_t *tr) { ecs_assert(field_index >= 0, ECS_INTERNAL_ERROR, NULL); it->trs[field_index] = tr; } ecs_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); const ecs_table_record_t *tr = &table->_->records[column]; flecs_query_it_set_tr(it, field_index, tr); ecs_id_t matched = flecs_query_it_set_id(it, table, field_index, tr->index); flecs_query_set_vars(op, matched, ctx); } void flecs_query_set_trav_match( const ecs_query_op_t *op, const ecs_table_record_t *tr, ecs_entity_t trav, ecs_entity_t second, const ecs_query_run_ctx_t *ctx) { int32_t field_index = op->field_index; if (field_index == -1) { return; } ecs_iter_t *it = ctx->it; ecs_id_t matched = ecs_pair(trav, second); it->ids[op->field_index] = matched; flecs_query_it_set_tr(it, op->field_index, tr); flecs_query_set_vars(op, matched, ctx); } bool flecs_query_table_filter( ecs_table_t *table, ecs_query_lbl_t other, ecs_flags32_t filter_mask) { uint32_t filter = flecs_ito(uint32_t, other); return (table->flags & filter_mask & filter) != 0; } /** * @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); const ecs_entity_t *entities = ecs_table_entities(table); for (i = 0; i < count; i ++) { ecs_record_t *r = flecs_entities_get(world, entities[i]); if (r->row & EcsEntityIsTraversable) { flecs_query_build_down_cache( world, a, ctx, cache, trav, entities[i]); } } } } } static void flecs_query_build_up_cache( ecs_world_t *world, ecs_allocator_t *a, const ecs_query_run_ctx_t *ctx, ecs_trav_cache_t *cache, ecs_entity_t trav, ecs_table_t *table, const ecs_table_record_t *tr, int32_t root_column) { ecs_id_t *ids = table->type.array; int32_t i = tr->index, end = i + tr->count; bool is_root = root_column == -1; for (; i < end; i ++) { ecs_entity_t second = ecs_pair_second(world, ids[i]); if (is_root) { root_column = i; } ecs_trav_elem_t *el = ecs_vec_append_t(a, &cache->entities, ecs_trav_elem_t); el->entity = second; el->tr = &table->_->records[i]; el->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_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); const ecs_entity_t *entities = ecs_table_entities(table); int32_t i, count = ecs_table_count(table); for (i = 0; i < count; i ++) { ecs_entity_t 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, 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; } if (ecs_table_has_id(world, table, idr_with->id)) { /* Table owns component */ continue; } const ecs_entity_t *entities = ecs_table_entities(table); int32_t i, count = ecs_table_count(table); for (i = 0; i < count; i ++) { ecs_entity_t e = entities[i]; ecs_record_t *record = flecs_entities_get(world, e); if (!record) { continue; } uint32_t flags = ECS_RECORD_TO_ROW_FLAGS(record->row); if (flags & EcsEntityIsTraversable) { ecs_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, idr_trav, idr_with, self, empty); } flecs_trav_entity_down_isa(world, a, cache, dst, trav, e, 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_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); 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); /* Cover IsA -> trav paths. If a parent inherits a component, then children * of that parent should find the component through up traversal. */ if (idr_with->flags & EcsIdOnInstantiateInherit) { flecs_trav_entity_down_isa( world, a, cache, result, trav, e, idr_with, self, empty); } flecs_trav_entity_down( world, a, cache, result, trav, 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]; up->tr = tr; return tr->index; } return -1; } static int32_t flecs_trav_type_offset_search( ecs_trav_up_t *up, const ecs_table_t *table, int32_t offset, ecs_id_t with, ecs_type_t *type) { ecs_assert(offset > 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(with != 0, ECS_INVALID_PARAMETER, NULL); while (offset < type->count) { ecs_id_t type_id = type->array[offset ++]; if (ecs_id_match(type_id, with)) { up->id = type_id; up->tr = &table->_->records[offset - 1]; return offset - 1; } } return -1; } static ecs_trav_up_t* flecs_trav_table_up( const ecs_query_run_ctx_t *ctx, ecs_allocator_t *a, ecs_trav_up_cache_t *cache, const ecs_world_t *world, ecs_entity_t src, ecs_id_t with, ecs_id_t rel, ecs_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->tr) { up->src = up_parent->src; up->tr = up_parent->tr; up->id = up_parent->id; goto found; } r_column = flecs_trav_type_offset_search( &up_pair, table, r_column + 1, rel, &type); } if (!is_a && (idr_with->flags & EcsIdOnInstantiateInherit)) { 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->tr) { up->src = up_parent->src; up->tr = up_parent->tr; up->id = up_parent->id; goto found; } r_column = flecs_trav_type_offset_search( &up_pair, table, r_column + 1, rel, &type); } } } not_found: up->tr = NULL; found: 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 = 0; if (term_set) { for (; t < query->term_count; t ++) { if (term_set & (1llu << t)) { break; } } } ecs_assert(t != query->term_count, ECS_INTERNAL_ERROR, NULL); op_ctx->start_from = t; ecs_id_record_t *idr = flecs_id_record_get(ctx->world, query->ids[t]); 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 = t + 1; 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; } 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->trs[term->field_index] = tr_with; } if (t == term_count) { ctx->vars[0].range.table = table; ctx->vars[0].range.count = 0; ctx->vars[0].range.offset = 0; it->trs[op_ctx->start_from] = tr; break; } } while (true); return true; } bool flecs_query_is_trivial_search( const ecs_query_run_ctx_t *ctx, ecs_query_trivial_ctx_t *op_ctx, bool redo) { const ecs_query_impl_t *query = ctx->query; const ecs_query_t *q = &query->pub; const ecs_id_t *ids = q->ids; ecs_iter_t *it = ctx->it; int32_t t, term_count = query->pub.term_count; if (!flecs_query_trivial_search_init(ctx, op_ctx, q, redo, 0)) { return false; } next: { 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)) { goto next; } for (t = 1; t < term_count; t ++) { ecs_id_record_t *idr = flecs_id_record_get(ctx->world, ids[t]); if (!idr) { return false; } const ecs_table_record_t *tr_with = flecs_id_record_get_table( idr, table); if (!tr_with) { goto next; } it->trs[t] = tr_with; } it->table = table; it->count = ecs_table_count(table); it->entities = ecs_table_entities(table); it->trs[0] = tr; } 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->trs[term->field_index] = tr; } it->entities = ecs_table_entities(table); if (it->entities) { it->entities = &it->entities[it->offset]; } return true; } } /** * @file addons/script/expr_ast.c * @brief Script expression AST implementation. */ #ifdef FLECS_SCRIPT #define flecs_expr_ast_new(parser, T, kind)\ (T*)flecs_expr_ast_new_(parser, ECS_SIZEOF(T), kind) static void* flecs_expr_ast_new_( ecs_script_parser_t *parser, ecs_size_t size, ecs_expr_node_kind_t kind) { ecs_assert(parser->script != NULL, ECS_INTERNAL_ERROR, NULL); ecs_allocator_t *a = &parser->script->allocator; ecs_expr_node_t *result = flecs_calloc_w_dbg_info(a, size, "ecs_expr_node_t"); result->kind = kind; result->pos = parser->pos; return result; } ecs_expr_value_node_t* flecs_expr_value_from( ecs_script_t *script, ecs_expr_node_t *node, ecs_entity_t type) { ecs_expr_value_node_t *result = flecs_calloc_t( &((ecs_script_impl_t*)script)->allocator, ecs_expr_value_node_t); result->ptr = &result->storage.u64; result->node.kind = EcsExprValue; result->node.pos = node ? node->pos : NULL; result->node.type = type; result->node.type_info = ecs_get_type_info(script->world, type); return result; } ecs_expr_variable_t* flecs_expr_variable_from( ecs_script_t *script, ecs_expr_node_t *node, const char *name) { ecs_expr_variable_t *result = flecs_calloc_t( &((ecs_script_impl_t*)script)->allocator, ecs_expr_variable_t); result->name = name; result->sp = -1; result->node.kind = EcsExprVariable; result->node.pos = node ? node->pos : NULL; return result; } ecs_expr_value_node_t* flecs_expr_bool( ecs_script_parser_t *parser, bool value) { ecs_expr_value_node_t *result = flecs_expr_ast_new( parser, ecs_expr_value_node_t, EcsExprValue); result->storage.bool_ = value; result->ptr = &result->storage.bool_; result->node.type = ecs_id(ecs_bool_t); return result; } ecs_expr_value_node_t* flecs_expr_int( ecs_script_parser_t *parser, int64_t value) { ecs_expr_value_node_t *result = flecs_expr_ast_new( parser, ecs_expr_value_node_t, EcsExprValue); result->storage.i64 = value; result->ptr = &result->storage.i64; result->node.type = ecs_id(ecs_i64_t); return result; } ecs_expr_value_node_t* flecs_expr_uint( ecs_script_parser_t *parser, uint64_t value) { ecs_expr_value_node_t *result = flecs_expr_ast_new( parser, ecs_expr_value_node_t, EcsExprValue); result->storage.u64 = value; result->ptr = &result->storage.u64; result->node.type = ecs_id(ecs_i64_t); return result; } ecs_expr_value_node_t* flecs_expr_float( ecs_script_parser_t *parser, double value) { ecs_expr_value_node_t *result = flecs_expr_ast_new( parser, ecs_expr_value_node_t, EcsExprValue); result->storage.f64 = value; result->ptr = &result->storage.f64; result->node.type = ecs_id(ecs_f64_t); return result; } ecs_expr_value_node_t* flecs_expr_string( ecs_script_parser_t *parser, const char *value) { char *str = ECS_CONST_CAST(char*, value); ecs_expr_value_node_t *result = flecs_expr_ast_new( parser, ecs_expr_value_node_t, EcsExprValue); result->storage.string = str; result->ptr = &result->storage.string; result->node.type = ecs_id(ecs_string_t); if (!flecs_string_escape(str)) { return NULL; } return result; } ecs_expr_interpolated_string_t* flecs_expr_interpolated_string( ecs_script_parser_t *parser, const char *value) { ecs_expr_interpolated_string_t *result = flecs_expr_ast_new( parser, ecs_expr_interpolated_string_t, EcsExprInterpolatedString); result->value = ECS_CONST_CAST(char*, value); result->buffer = flecs_strdup(&parser->script->allocator, value); result->buffer_size = ecs_os_strlen(result->buffer) + 1; result->node.type = ecs_id(ecs_string_t); ecs_vec_init_t(&parser->script->allocator, &result->fragments, char*, 0); ecs_vec_init_t(&parser->script->allocator, &result->expressions, ecs_expr_node_t*, 0); return result; } ecs_expr_value_node_t* flecs_expr_entity( ecs_script_parser_t *parser, ecs_entity_t value) { ecs_expr_value_node_t *result = flecs_expr_ast_new( parser, ecs_expr_value_node_t, EcsExprValue); result->storage.entity = value; result->ptr = &result->storage.entity; result->node.type = ecs_id(ecs_entity_t); return result; } ecs_expr_initializer_t* flecs_expr_initializer( ecs_script_parser_t *parser) { ecs_expr_initializer_t *result = flecs_expr_ast_new( parser, ecs_expr_initializer_t, EcsExprInitializer); ecs_vec_init_t(&parser->script->allocator, &result->elements, ecs_expr_initializer_element_t, 0); return result; } ecs_expr_identifier_t* flecs_expr_identifier( ecs_script_parser_t *parser, const char *value) { ecs_expr_identifier_t *result = flecs_expr_ast_new( parser, ecs_expr_identifier_t, EcsExprIdentifier); result->value = value; return result; } ecs_expr_variable_t* flecs_expr_variable( ecs_script_parser_t *parser, const char *value) { ecs_expr_variable_t *result = flecs_expr_ast_new( parser, ecs_expr_variable_t, EcsExprVariable); result->name = value; result->sp = -1; return result; } ecs_expr_unary_t* flecs_expr_unary( ecs_script_parser_t *parser) { ecs_expr_unary_t *result = flecs_expr_ast_new( parser, ecs_expr_unary_t, EcsExprUnary); return result; } ecs_expr_binary_t* flecs_expr_binary( ecs_script_parser_t *parser) { ecs_expr_binary_t *result = flecs_expr_ast_new( parser, ecs_expr_binary_t, EcsExprBinary); return result; } ecs_expr_member_t* flecs_expr_member( ecs_script_parser_t *parser) { ecs_expr_member_t *result = flecs_expr_ast_new( parser, ecs_expr_member_t, EcsExprMember); return result; } ecs_expr_function_t* flecs_expr_function( ecs_script_parser_t *parser) { ecs_expr_function_t *result = flecs_expr_ast_new( parser, ecs_expr_function_t, EcsExprFunction); return result; } ecs_expr_element_t* flecs_expr_element( ecs_script_parser_t *parser) { ecs_expr_element_t *result = flecs_expr_ast_new( parser, ecs_expr_element_t, EcsExprElement); return result; } ecs_expr_match_t* flecs_expr_match( ecs_script_parser_t *parser) { ecs_expr_match_t *result = flecs_expr_ast_new( parser, ecs_expr_match_t, EcsExprMatch); return result; } static bool flecs_expr_explicit_cast_allowed( ecs_world_t *world, ecs_entity_t from, ecs_entity_t to) { if (from == to) { return true; } const EcsType *from_type = ecs_get(world, from, EcsType); const EcsType *to_type = ecs_get(world, to, EcsType); ecs_assert(from_type != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(to_type != NULL, ECS_INTERNAL_ERROR, NULL); /* Treat opaque types asthe types that they're pretending to be*/ if (from_type->kind == EcsOpaqueType) { const EcsOpaque *o = ecs_get(world, from, EcsOpaque); ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL); from_type = ecs_get(world, o->as_type, EcsType); ecs_assert(from_type != NULL, ECS_INTERNAL_ERROR, NULL); } if (to_type->kind == EcsOpaqueType) { const EcsOpaque *o = ecs_get(world, to, EcsOpaque); ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL); to_type = ecs_get(world, o->as_type, EcsType); ecs_assert(to_type != NULL, ECS_INTERNAL_ERROR, NULL); } if (from_type->kind != EcsPrimitiveType || to_type->kind != EcsPrimitiveType) { if (from_type->kind == EcsEnumType || from_type->kind == EcsBitmaskType) { if (flecs_expr_is_type_integer(to)) { /* Can cast enums/bitmasks to integers */ return true; } } if (flecs_expr_is_type_integer(from)) { if (to_type->kind == EcsEnumType || to_type->kind == EcsBitmaskType) { /* Can cast integers to enums/bitmasks */ return true; } } /* Cannot cast complex types that are not the same */ return false; } /* Anything can be casted to a number */ if (flecs_expr_is_type_number(to)) { return true; } /* Anything can be casted to a number */ if (to == ecs_id(ecs_string_t)) { return true; } return true; } ecs_expr_cast_t* flecs_expr_cast( ecs_script_t *script, ecs_expr_node_t *expr, ecs_entity_t type) { if (!flecs_expr_explicit_cast_allowed(script->world, expr->type, type)) { char *from = ecs_id_str(script->world, expr->type); char *to = ecs_id_str(script->world, type); flecs_expr_visit_error(script, expr, "invalid cast from %s to %s", from, to); ecs_os_free(from); ecs_os_free(to); return NULL; } ecs_allocator_t *a = &((ecs_script_impl_t*)script)->allocator; ecs_expr_cast_t *result = flecs_calloc_t(a, ecs_expr_cast_t); result->node.kind = EcsExprCast; if (flecs_expr_is_type_number(expr->type) && flecs_expr_is_type_number(type)) { result->node.kind = EcsExprCastNumber; } result->node.pos = expr->pos; result->node.type = type; result->node.type_info = ecs_get_type_info(script->world, type); ecs_assert(result->node.type_info != NULL, ECS_INTERNAL_ERROR, NULL); result->expr = expr; return result; } #endif /** * @file addons/script/expr/parser.c * @brief Script expression parser. */ #ifdef FLECS_SCRIPT /* From https://en.cppreference.com/w/c/language/operator_precedence */ static int flecs_expr_precedence[] = { [EcsTokParenOpen] = 1, [EcsTokMember] = 1, [EcsTokBracketOpen] = 1, [EcsTokNot] = 2, [EcsTokMul] = 3, [EcsTokDiv] = 3, [EcsTokMod] = 3, [EcsTokAdd] = 4, [EcsTokSub] = 4, [EcsTokShiftLeft] = 5, [EcsTokShiftRight] = 5, [EcsTokGt] = 6, [EcsTokGtEq] = 6, [EcsTokLt] = 6, [EcsTokLtEq] = 6, [EcsTokEq] = 7, [EcsTokNeq] = 7, [EcsTokBitwiseAnd] = 8, [EcsTokBitwiseOr] = 10, [EcsTokAnd] = 11, [EcsTokOr] = 12, }; static const char* flecs_script_parse_lhs( ecs_script_parser_t *parser, const char *pos, ecs_script_tokenizer_t *tokenizer, ecs_script_token_kind_t left_oper, ecs_expr_node_t **out); static void flecs_script_parser_expr_free( ecs_script_parser_t *parser, ecs_expr_node_t *node) { flecs_expr_visit_free(&parser->script->pub, node); } static bool flecs_has_precedence( ecs_script_token_kind_t first, ecs_script_token_kind_t second) { if (!flecs_expr_precedence[first]) { return false; } return flecs_expr_precedence[first] <= flecs_expr_precedence[second]; } static ecs_entity_t flecs_script_default_lookup( const ecs_world_t *world, const char *name, void *ctx) { (void)ctx; return ecs_lookup(world, name); } static const char* flecs_script_parse_match_elems( ecs_script_parser_t *parser, const char *pos, ecs_expr_match_t *node) { ecs_allocator_t *a = &parser->script->allocator; bool old_significant_newline = parser->significant_newline; parser->significant_newline = true; ecs_vec_init_t(NULL, &node->elements, ecs_expr_match_element_t, 0); do { ParserBegin; LookAhead( case '\n': { pos = lookahead; continue; } case '}': { /* Return last character of initializer */ pos = lookahead - 1; parser->significant_newline = old_significant_newline; EndOfRule; } ) ecs_expr_match_element_t *elem = ecs_vec_append_t( a, &node->elements, ecs_expr_match_element_t); ecs_os_zeromem(elem); pos = flecs_script_parse_expr(parser, pos, 0, &elem->compare); if (!pos) { goto error; } Parse_1(':', { pos = flecs_script_parse_expr(parser, pos, 0, &elem->expr); if (!pos) { goto error; } Parse( case ';': case '\n': { break; } ) break; }) } while (true); ParserEnd; } const char* flecs_script_parse_initializer( ecs_script_parser_t *parser, const char *pos, char until, ecs_expr_initializer_t **node_out) { bool first = true; ecs_expr_initializer_t *node = *node_out = flecs_expr_initializer(parser); ecs_allocator_t *a = &parser->script->allocator; do { ParserBegin; /* End of initializer */ LookAhead( case ')': case '}': { if ((char)lookahead_token.kind != until) { Error("expected '%c'", until); } if (first) { node->node.kind = EcsExprEmptyInitializer; } pos = lookahead - 1; EndOfRule; }) first = false; ecs_expr_initializer_element_t *elem = ecs_vec_append_t( a, &node->elements, ecs_expr_initializer_element_t); ecs_os_zeromem(elem); /* Parse member name */ { LookAhead_2(EcsTokIdentifier, ':', { elem->member = Token(0); LookAhead_Keep(); pos = lookahead; break; }) } { LookAhead_2(EcsTokIdentifier, EcsTokAddAssign, { elem->member = Token(0); elem->operator = EcsTokAddAssign; LookAhead_Keep(); pos = lookahead; break; }) } { LookAhead_2(EcsTokIdentifier, EcsTokMulAssign, { elem->member = Token(0); elem->operator = EcsTokMulAssign; LookAhead_Keep(); pos = lookahead; break; }) } pos = flecs_script_parse_expr(parser, pos, 0, &elem->value); if (!pos) { goto error; } { /* Parse next element or end of initializer*/ LookAhead( case ',': { pos = lookahead; break; } case ')': case '}': /* Return last character of initializer */ pos = lookahead - 1; case '\n': { if ((char)lookahead_token.kind != until) { Error("expected '%c'", until); } EndOfRule; } ) } } while (true); ParserEnd; } static const char* flecs_script_parse_collection_initializer( ecs_script_parser_t *parser, const char *pos, ecs_expr_initializer_t **node_out) { bool first = true; ecs_expr_initializer_t *node = *node_out = flecs_expr_initializer(parser); ecs_allocator_t *a = &parser->script->allocator; do { ParserBegin; /* End of initializer */ LookAhead_1(']', { if (first) { node->node.kind = EcsExprEmptyInitializer; } pos = lookahead - 1; EndOfRule; }) first = false; ecs_expr_initializer_element_t *elem = ecs_vec_append_t( a, &node->elements, ecs_expr_initializer_element_t); ecs_os_zeromem(elem); pos = flecs_script_parse_expr(parser, pos, 0, &elem->value); if (!pos) { goto error; } { /* Parse next element or end of initializer */ LookAhead( case ',': { pos = lookahead; break; } case ']': { EndOfRule; } ) } } while (true); ParserEnd; } static const char* flecs_script_parse_rhs( ecs_script_parser_t *parser, const char *pos, ecs_script_tokenizer_t *tokenizer, ecs_script_token_kind_t left_oper, ecs_expr_node_t **out) { const char *last_pos = pos; do { TokenFramePush(); last_pos = pos; LookAhead( case EcsTokNumber: if (pos[0] == '-') { lookahead = &pos[1]; lookahead_token.kind = EcsTokSub; } else { Error("unexpected number"); } case EcsTokAdd: case EcsTokSub: case EcsTokMul: case EcsTokDiv: case EcsTokMod: case EcsTokBitwiseOr: case EcsTokBitwiseAnd: case EcsTokEq: case EcsTokNeq: case EcsTokGt: case EcsTokGtEq: case EcsTokLt: case EcsTokLtEq: case EcsTokAnd: case EcsTokOr: case EcsTokShiftLeft: case EcsTokShiftRight: case EcsTokBracketOpen: case EcsTokMember: case EcsTokParenOpen: { ecs_script_token_kind_t oper = lookahead_token.kind; /* Only consume more tokens if operator has precedence */ if (flecs_has_precedence(left_oper, oper)) { break; } /* Consume lookahead token */ pos = lookahead; switch(oper) { case EcsTokBracketOpen: { ecs_expr_element_t *result = flecs_expr_element(parser); result->left = *out; *out = (ecs_expr_node_t*)result; pos = flecs_script_parse_lhs( parser, pos, tokenizer, 0, &result->index); if (!pos) { goto error; } Parse_1(']', { break; }); break; } case EcsTokMember: { Parse_1(EcsTokIdentifier, { ecs_expr_member_t *result = flecs_expr_member(parser); result->left = *out; result->member_name = Token(1); *out = (ecs_expr_node_t*)result; break; }); break; } case EcsTokParenOpen: { ecs_expr_function_t *result = flecs_expr_function(parser); result->left = *out; pos = flecs_script_parse_initializer( parser, pos, ')', &result->args); if (!pos) { goto error; } *out = (ecs_expr_node_t*)result; if (pos[0] != ')') { Error("expected end of argument list"); } pos ++; break; } default: { ecs_expr_binary_t *result = flecs_expr_binary(parser); result->left = *out; result->operator = oper; *out = (ecs_expr_node_t*)result; pos = flecs_script_parse_lhs(parser, pos, tokenizer, result->operator, &result->right); if (!pos) { goto error; } break; } }; /* Ensures lookahead tokens in token buffer don't get overwritten */ parser->token_keep = parser->token_cur; break; } ) TokenFramePop(); } while (pos != last_pos); return pos; error: return NULL; } static const char* flecs_script_parse_lhs( ecs_script_parser_t *parser, const char *pos, ecs_script_tokenizer_t *tokenizer, ecs_script_token_kind_t left_oper, ecs_expr_node_t **out) { TokenFramePush(); bool can_have_rhs = true; Parse( case EcsTokNumber: { const char *expr = Token(0); if (strchr(expr, '.') || strchr(expr, 'e')) { *out = (ecs_expr_node_t*)flecs_expr_float(parser, atof(expr)); } else if (expr[0] == '-') { char *end; *out = (ecs_expr_node_t*)flecs_expr_int(parser, strtoll(expr, &end, 10)); } else { char *end; *out = (ecs_expr_node_t*)flecs_expr_uint(parser, strtoull(expr, &end, 10)); } break; } case EcsTokString: { if (flecs_string_is_interpolated(Token(0))) { *out = (ecs_expr_node_t*)flecs_expr_interpolated_string( parser, Token(0)); } else { *out = (ecs_expr_node_t*)flecs_expr_string(parser, Token(0)); } break; } case EcsTokIdentifier: { const char *expr = Token(0); if (expr[0] == '$') { *out = (ecs_expr_node_t*)flecs_expr_variable(parser, &expr[1]); } else if (!ecs_os_strcmp(expr, "true")) { *out = (ecs_expr_node_t*)flecs_expr_bool(parser, true); } else if (!ecs_os_strcmp(expr, "false")) { *out = (ecs_expr_node_t*)flecs_expr_bool(parser, false); } else { char *last_elem = strrchr(expr, '.'); if (last_elem && last_elem[1] == '$') { /* Scoped global variable */ ecs_expr_variable_t *v = flecs_expr_variable(parser, expr); ecs_os_memmove(&last_elem[1], &last_elem[2], ecs_os_strlen(&last_elem[2]) + 1); v->node.kind = EcsExprGlobalVariable; *out = (ecs_expr_node_t*)v; } else { /* Entity identifier */ *out = (ecs_expr_node_t*)flecs_expr_identifier(parser, expr); } } break; } case EcsTokNot: { ecs_expr_unary_t *node = flecs_expr_unary(parser); pos = flecs_script_parse_expr(parser, pos, EcsTokNot, &node->expr); if (!pos) { flecs_script_parser_expr_free(parser, (ecs_expr_node_t*)node); goto error; } node->operator = EcsTokNot; *out = (ecs_expr_node_t*)node; break; } case EcsTokSub: { ecs_expr_binary_t *node = flecs_expr_binary(parser); /* Use EcsTokNot as it has the same precedence as a unary - */ pos = flecs_script_parse_expr(parser, pos, EcsTokNot, &node->right); if (!pos) { flecs_script_parser_expr_free(parser, (ecs_expr_node_t*)node); goto error; } node->left = (ecs_expr_node_t*)flecs_expr_int(parser, -1); node->operator = EcsTokMul; *out = (ecs_expr_node_t*)node; break; } case EcsTokKeywordMatch: { ecs_expr_match_t *node = flecs_expr_match(parser); pos = flecs_script_parse_expr(parser, pos, 0, &node->expr); if (!pos) { flecs_script_parser_expr_free(parser, (ecs_expr_node_t*)node); goto error; } Parse_1('{', { pos = flecs_script_parse_match_elems(parser, pos, node); if (!pos) { flecs_script_parser_expr_free( parser, (ecs_expr_node_t*)node); goto error; } Parse_1('}', { *out = (ecs_expr_node_t*)node; break; }) break; }) can_have_rhs = false; break; } case '(': { pos = flecs_script_parse_expr(parser, pos, 0, out); if (!pos) { goto error; } Parse_1(')', { break; }) break; } case '{': { ecs_expr_initializer_t *node = NULL; pos = flecs_script_parse_initializer(parser, pos, '}', &node); if (!pos) { flecs_script_parser_expr_free(parser, (ecs_expr_node_t*)node); goto error; } Parse_1('}', { break; }) can_have_rhs = false; *out = (ecs_expr_node_t*)node; break; } case '[': { ecs_expr_initializer_t *node = NULL; pos = flecs_script_parse_collection_initializer(parser, pos, &node); if (!pos) { flecs_script_parser_expr_free(parser, (ecs_expr_node_t*)node); goto error; } node->is_collection = true; Parse_1(']', { break; }) can_have_rhs = false; *out = (ecs_expr_node_t*)node; break; } ) TokenFramePop(); /* Return if this was end of expression, or if the parsed expression cannot * have a right hand side. */ if (!pos[0] || !can_have_rhs) { return pos; } /* Parse right-hand side of expression if there is one */ return flecs_script_parse_rhs(parser, pos, tokenizer, left_oper, out); error: return NULL; } const char* flecs_script_parse_expr( ecs_script_parser_t *parser, const char *pos, ecs_script_token_kind_t left_oper, ecs_expr_node_t **out) { ParserBegin; pos = flecs_script_parse_lhs(parser, pos, tokenizer, left_oper, out); EndOfRule; ParserEnd; } ecs_script_t* ecs_expr_parse( ecs_world_t *world, const char *expr, const ecs_expr_eval_desc_t *desc) { ecs_expr_eval_desc_t priv_desc = {0}; if (desc) { priv_desc = *desc; } if (!priv_desc.lookup_action) { priv_desc.lookup_action = flecs_script_default_lookup; } ecs_script_t *script = flecs_script_new(world); ecs_script_impl_t *impl = flecs_script_impl(script); ecs_script_parser_t parser = { .script = impl, .scope = impl->root, .significant_newline = false }; impl->token_buffer_size = ecs_os_strlen(expr) * 2 + 1; impl->token_buffer = flecs_alloc_w_dbg_info( &impl->allocator, impl->token_buffer_size, "token buffer"); parser.token_cur = impl->token_buffer; const char *ptr = flecs_script_parse_expr(&parser, expr, 0, &impl->expr); if (!ptr) { goto error; } impl->next_token = ptr; impl->token_remaining = parser.token_cur; if (flecs_expr_visit_type(script, impl->expr, &priv_desc)) { goto error; } //printf("%s\n", ecs_script_ast_to_str(script, true)); if (!desc || !desc->disable_folding) { if (flecs_expr_visit_fold(script, &impl->expr, &priv_desc)) { goto error; } } //printf("%s\n", ecs_script_ast_to_str(script, true)); return script; error: ecs_script_free(script); return NULL; } int ecs_expr_eval( const ecs_script_t *script, ecs_value_t *value, const ecs_expr_eval_desc_t *desc) { ecs_assert(script != NULL, ECS_INTERNAL_ERROR, NULL); ecs_script_impl_t *impl = flecs_script_impl( /* Safe, won't be writing to script */ ECS_CONST_CAST(ecs_script_t*, script)); ecs_assert(impl->expr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_expr_eval_desc_t priv_desc = {0}; if (desc) { priv_desc = *desc; } if (!priv_desc.lookup_action) { priv_desc.lookup_action = flecs_script_default_lookup; } if (flecs_expr_visit_eval(script, impl->expr, &priv_desc, value)) { goto error; } return 0; error: return -1; } FLECS_API const char* ecs_expr_run( ecs_world_t *world, const char *expr, ecs_value_t *value, const ecs_expr_eval_desc_t *desc) { ecs_expr_eval_desc_t priv_desc = {0}; if (desc) { priv_desc = *desc; } if (!priv_desc.type) { priv_desc.type = value->type; } else if (desc && (value->type != desc->type)) { ecs_throw(ECS_INVALID_PARAMETER, "type of value parameter does not match desc->type"); } ecs_script_t *s = ecs_expr_parse(world, expr, &priv_desc); if (!s) { goto error; } if (ecs_expr_eval(s, value, &priv_desc)) { ecs_script_free(s); goto error; } const char *result = flecs_script_impl(s)->next_token; ecs_script_free(s); return result; error: return NULL; } FLECS_API char* ecs_script_string_interpolate( ecs_world_t *world, const char *str, const ecs_script_vars_t *vars) { if (!flecs_string_is_interpolated(str)) { char *result = ecs_os_strdup(str); if (!flecs_string_escape(result)) { ecs_os_free(result); return NULL; } return result; } char *expr = flecs_asprintf("\"%s\"", str); ecs_expr_eval_desc_t desc = { .vars = vars }; char *r = NULL; if (!ecs_expr_run(world, expr, &ecs_value_ptr(ecs_string_t, &r), &desc)) { ecs_os_free(expr); return NULL; } ecs_os_free(expr); return r; } #endif /** * @file addons/script/expr/stack.c * @brief Script expression stack implementation. */ #ifdef FLECS_SCRIPT static void flecs_expr_value_alloc( ecs_expr_stack_t *stack, ecs_expr_value_t *v, const ecs_type_info_t *ti) { ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(v->type_info == NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); v->type_info = ti; v->value.type = ti->component; v->value.ptr = flecs_stack_alloc(&stack->stack, ti->size, ti->alignment); if (ti->hooks.ctor) { ti->hooks.ctor(v->value.ptr, 1, ti); } } static void flecs_expr_value_free( ecs_expr_value_t *v) { const ecs_type_info_t *ti = v->type_info; v->type_info = NULL; if (!v->owned) { return; /* Runtime doesn't own value, don't destruct */ } if (ti && ti->hooks.dtor) { ecs_assert(v->value.ptr != NULL, ECS_INTERNAL_ERROR, NULL); ti->hooks.dtor(v->value.ptr, 1, ti); flecs_stack_free(v->value.ptr, ti->size); } v->value.ptr = NULL; } void flecs_expr_stack_init( ecs_expr_stack_t *stack) { ecs_os_zeromem(stack); flecs_stack_init(&stack->stack); } void flecs_expr_stack_fini( ecs_expr_stack_t *stack) { ecs_assert(stack->frame == 0, ECS_INTERNAL_ERROR, NULL); flecs_stack_fini(&stack->stack); } ecs_expr_value_t* flecs_expr_stack_result( ecs_expr_stack_t *stack, ecs_expr_node_t *node) { ecs_assert(node != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(node->type_info != NULL, ECS_INTERNAL_ERROR, NULL); return flecs_expr_stack_alloc(stack, node->type_info); } ecs_expr_value_t* flecs_expr_stack_alloc( ecs_expr_stack_t *stack, const ecs_type_info_t *ti) { ecs_assert(stack->frame > 0, ECS_INTERNAL_ERROR, NULL); int32_t sp = stack->frames[stack->frame - 1].sp ++; ecs_assert(sp < FLECS_EXPR_STACK_MAX, ECS_OUT_OF_RANGE, "expression nesting is too deep"); ecs_expr_value_t *v = &stack->values[sp]; if (ti) { flecs_expr_value_alloc(stack, v, ti); } return v; } void flecs_expr_stack_push( ecs_expr_stack_t *stack) { int32_t frame = stack->frame ++; ecs_assert(frame < FLECS_EXPR_STACK_MAX, ECS_OUT_OF_RANGE, "expression nesting is too deep"); stack->frames[frame].cur = flecs_stack_get_cursor(&stack->stack); if (frame) { stack->frames[frame].sp = stack->frames[frame - 1].sp; } else { stack->frames[frame].sp = 0; } } void flecs_expr_stack_pop( ecs_expr_stack_t *stack) { int32_t frame = -- stack->frame; ecs_assert(frame >= 0, ECS_INTERNAL_ERROR, NULL); int32_t sp, start = 0, end = stack->frames[frame].sp; if (frame) { start = stack->frames[frame - 1].sp; } for (sp = end - 1; sp >= start; sp --) { flecs_expr_value_free(&stack->values[sp]); } flecs_stack_restore_cursor(&stack->stack, stack->frames[frame].cur); } #endif /** * @file addons/script/expr/parser.c * brief Scriptexpoutsion parser. */ #ifdef FLECS_SCRIPT int flecs_value_copy_to( ecs_world_t *world, ecs_value_t *dst, const ecs_expr_value_t *src) { ecs_assert(dst->type != 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(src->value.type != 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(src->value.ptr != 0, ECS_INTERNAL_ERROR, NULL); if (src->value.type == dst->type) { ecs_assert(src->type_info != NULL, ECS_INTERNAL_ERROR, NULL); ecs_value_copy_w_type_info( world, src->type_info, dst->ptr, src->value.ptr); } else { /* Cast value to desired output type */ ecs_meta_cursor_t cur = ecs_meta_cursor(world, dst->type, dst->ptr); if (ecs_meta_set_value(&cur, &src->value)) { goto error; } } return 0; error: return -1; } int flecs_value_move_to( ecs_world_t *world, ecs_value_t *dst, ecs_value_t *src) { ecs_assert(dst->type != 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(src->type != 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(src->ptr != 0, ECS_INTERNAL_ERROR, NULL); if (src->type == dst->type) { ecs_value_move(world, src->type, dst->ptr, src->ptr); } else { ecs_value_t tmp; tmp.type = src->type; tmp.ptr = ecs_value_new(world, src->type); ecs_value_move(world, src->type, tmp.ptr, src->ptr); /* Cast value to desired output type */ ecs_meta_cursor_t cur = ecs_meta_cursor(world, dst->type, dst->ptr); if (ecs_meta_set_value(&cur, &tmp)) { goto error; } ecs_value_free(world, src->type, tmp.ptr); } return 0; error: return -1; } int flecs_value_unary( const ecs_script_t *script, const ecs_value_t *expr, ecs_value_t *out, ecs_script_token_kind_t operator) { (void)script; switch(operator) { case EcsTokNot: ecs_assert(expr->type == ecs_id(ecs_bool_t), ECS_INTERNAL_ERROR, NULL); ecs_assert(out->type == ecs_id(ecs_bool_t), ECS_INTERNAL_ERROR, NULL); *(bool*)out->ptr = !*(bool*)expr->ptr; break; case EcsTokEnd: case EcsTokUnknown: case EcsTokScopeOpen: case EcsTokScopeClose: case EcsTokParenOpen: case EcsTokParenClose: case EcsTokBracketOpen: case EcsTokBracketClose: case EcsTokMember: case EcsTokComma: case EcsTokSemiColon: case EcsTokColon: case EcsTokAssign: case EcsTokAdd: case EcsTokSub: case EcsTokMul: case EcsTokDiv: case EcsTokMod: case EcsTokBitwiseOr: case EcsTokBitwiseAnd: case EcsTokOptional: case EcsTokAnnotation: case EcsTokNewline: case EcsTokEq: case EcsTokNeq: case EcsTokGt: case EcsTokGtEq: case EcsTokLt: case EcsTokLtEq: case EcsTokAnd: case EcsTokOr: case EcsTokMatch: case EcsTokRange: case EcsTokShiftLeft: case EcsTokShiftRight: case EcsTokAddAssign: case EcsTokMulAssign: case EcsTokIdentifier: case EcsTokString: case EcsTokNumber: case EcsTokKeywordModule: case EcsTokKeywordUsing: case EcsTokKeywordWith: case EcsTokKeywordIf: case EcsTokKeywordElse: case EcsTokKeywordFor: case EcsTokKeywordIn: case EcsTokKeywordMatch: case EcsTokKeywordTemplate: case EcsTokKeywordProp: case EcsTokKeywordConst: default: ecs_abort(ECS_INTERNAL_ERROR, "invalid operator for binary expression"); } return 0; } #define ECS_VALUE_GET(value, T) (*(T*)(value)->ptr) #define ECS_BOP(left, right, result, op, R, T)\ ECS_VALUE_GET(result, R) = (R)(ECS_VALUE_GET(left, T) op ECS_VALUE_GET(right, T)) #define ECS_BOP_COND(left, right, result, op, R, T)\ ECS_VALUE_GET(result, ecs_bool_t) = ECS_VALUE_GET(left, T) op ECS_VALUE_GET(right, T) #define ECS_BOP_ASSIGN(left, right, result, op, R, T)\ ECS_VALUE_GET(result, R) op (R)(ECS_VALUE_GET(right, T)) /* Unsigned operations */ #define ECS_BINARY_UINT_OPS(left, right, result, op, OP)\ if ((right)->type == ecs_id(ecs_u64_t)) { \ OP(left, right, result, op, ecs_u64_t, ecs_u64_t);\ } else if ((right)->type == ecs_id(ecs_u32_t)) { \ OP(left, right, result, op, ecs_u32_t, ecs_u32_t);\ } else if ((right)->type == ecs_id(ecs_u16_t)) { \ OP(left, right, result, op, ecs_u16_t, ecs_u16_t);\ } else if ((right)->type == ecs_id(ecs_u8_t)) { \ OP(left, right, result, op, ecs_u8_t, ecs_u8_t);\ } /* Unsigned + signed operations */ #define ECS_BINARY_INT_OPS(left, right, result, op, OP)\ ECS_BINARY_UINT_OPS(left, right, result, op, OP)\ else if ((right)->type == ecs_id(ecs_i64_t)) { \ OP(left, right, result, op, ecs_i64_t, ecs_i64_t);\ } else if ((right)->type == ecs_id(ecs_i32_t)) { \ OP(left, right, result, op, ecs_i32_t, ecs_i32_t);\ } else if ((right)->type == ecs_id(ecs_i16_t)) { \ OP(left, right, result, op, ecs_i16_t, ecs_i16_t);\ } else if ((right)->type == ecs_id(ecs_i8_t)) { \ OP(left, right, result, op, ecs_i8_t, ecs_i8_t);\ } /* Unsigned + signed + floating point operations */ #define ECS_BINARY_NUMBER_OPS(left, right, result, op, OP)\ ECS_BINARY_INT_OPS(left, right, result, op, OP)\ else if ((right)->type == ecs_id(ecs_f64_t)) { \ OP(left, right, result, op, ecs_f64_t, ecs_f64_t);\ } else if ((right)->type == ecs_id(ecs_f32_t)) { \ OP(left, right, result, op, ecs_f32_t, ecs_f32_t);\ } /* Combinations + error checking */ #define ECS_BINARY_INT_OP(left, right, result, op)\ ECS_BINARY_INT_OPS(left, right, result, op, ECS_BOP) else {\ ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ } #define ECS_BINARY_UINT_OP(left, right, result, op)\ ECS_BINARY_UINT_OPS(left, right, result, op, ECS_BOP) else {\ ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ } #define ECS_BINARY_OP(left, right, result, op)\ ECS_BINARY_NUMBER_OPS(left, right, result, op, ECS_BOP) else {\ ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ } #define ECS_BINARY_COND_EQ_OP(left, right, result, op)\ ECS_BINARY_INT_OPS(left, right, result, op, ECS_BOP_COND)\ else if ((right)->type == ecs_id(ecs_char_t)) { \ ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_char_t);\ } else if ((right)->type == ecs_id(ecs_u8_t)) { \ ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_u8_t);\ } else if ((right)->type == ecs_id(ecs_bool_t)) { \ ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_bool_t);\ } else if ((right)->type == ecs_id(ecs_entity_t)) { \ ECS_BOP_COND(left, right, result, op, ecs_entity_t, ecs_entity_t);\ } else if ((right)->type == ecs_id(ecs_string_t)) { \ char *lstr = *(char**)(left)->ptr;\ char *rstr = *(char**)(right)->ptr;\ if (lstr && rstr) {\ *(bool*)(result)->ptr = ecs_os_strcmp(lstr, rstr) op 0;\ } else {\ *(bool*)(result)->ptr = lstr == rstr;\ }\ } else {\ ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ } #define ECS_BINARY_COND_OP(left, right, result, op)\ ECS_BINARY_NUMBER_OPS(left, right, result, op, ECS_BOP_COND)\ else if ((right)->type == ecs_id(ecs_char_t)) { \ ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_char_t);\ } else if ((right)->type == ecs_id(ecs_u8_t)) { \ ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_u8_t);\ } else if ((right)->type == ecs_id(ecs_bool_t)) { \ ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_bool_t);\ } else {\ ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ } #define ECS_BINARY_ASSIGN_OP(left, right, result, op)\ ECS_BINARY_NUMBER_OPS(left, right, result, op, ECS_BOP_ASSIGN)\ #define ECS_BINARY_BOOL_OP(left, right, result, op)\ if ((right)->type == ecs_id(ecs_bool_t)) { \ ECS_BOP_COND(left, right, result, op, ecs_bool_t, ecs_bool_t);\ } else {\ ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ } int flecs_value_binary( const ecs_script_t *script, const ecs_value_t *left, const ecs_value_t *right, ecs_value_t *out, ecs_script_token_kind_t operator) { (void)script; if (operator == EcsTokDiv || operator == EcsTokMod) { if (flecs_value_is_0(right)) { ecs_err("%s: division by zero", script->name ? script->name : "anonymous script"); return -1; } } switch(operator) { case EcsTokAdd: ECS_BINARY_OP(left, right, out, +); break; case EcsTokSub: ECS_BINARY_OP(left, right, out, -); break; case EcsTokMul: ECS_BINARY_OP(left, right, out, *); break; case EcsTokDiv: ECS_BINARY_OP(left, right, out, /); break; case EcsTokMod: ECS_BINARY_INT_OP(left, right, out, %); break; case EcsTokEq: ECS_BINARY_COND_EQ_OP(left, right, out, ==); break; case EcsTokNeq: ECS_BINARY_COND_EQ_OP(left, right, out, !=); break; case EcsTokGt: ECS_BINARY_COND_OP(left, right, out, >); break; case EcsTokGtEq: ECS_BINARY_COND_OP(left, right, out, >=); break; case EcsTokLt: ECS_BINARY_COND_OP(left, right, out, <); break; case EcsTokLtEq: ECS_BINARY_COND_OP(left, right, out, <=); break; case EcsTokAnd: ECS_BINARY_BOOL_OP(left, right, out, &&); break; case EcsTokOr: ECS_BINARY_BOOL_OP(left, right, out, ||); break; case EcsTokBitwiseAnd: ECS_BINARY_INT_OP(left, right, out, &); break; case EcsTokBitwiseOr: ECS_BINARY_INT_OP(left, right, out, |); break; case EcsTokShiftLeft: ECS_BINARY_INT_OP(left, right, out, <<); break; case EcsTokShiftRight: ECS_BINARY_INT_OP(left, right, out, >>); break; case EcsTokAddAssign: ECS_BINARY_ASSIGN_OP(left, right, out, +=); break; case EcsTokMulAssign: ECS_BINARY_ASSIGN_OP(left, right, out, *=); break; case EcsTokEnd: case EcsTokUnknown: case EcsTokScopeOpen: case EcsTokScopeClose: case EcsTokParenOpen: case EcsTokParenClose: case EcsTokBracketOpen: case EcsTokBracketClose: case EcsTokMember: case EcsTokComma: case EcsTokSemiColon: case EcsTokColon: case EcsTokAssign: case EcsTokNot: case EcsTokOptional: case EcsTokAnnotation: case EcsTokNewline: case EcsTokMatch: case EcsTokRange: case EcsTokIdentifier: case EcsTokString: case EcsTokNumber: case EcsTokKeywordModule: case EcsTokKeywordUsing: case EcsTokKeywordWith: case EcsTokKeywordIf: case EcsTokKeywordElse: case EcsTokKeywordFor: case EcsTokKeywordIn: case EcsTokKeywordTemplate: case EcsTokKeywordMatch: case EcsTokKeywordProp: case EcsTokKeywordConst: default: ecs_abort(ECS_INTERNAL_ERROR, "invalid operator for binary expression"); } return 0; } bool flecs_string_is_interpolated( const char *value) { const char *ptr = value; for (ptr = strchr(ptr, '$'); ptr; ptr = strchr(ptr + 1, '$')) { if (ptr != value) { if (ptr[-1] == '\\') { continue; /* Escaped */ } } if (isspace(ptr[1]) || !ptr[1]) { continue; /* $ by itself */ } return true; } ptr = value; for (ptr = strchr(ptr, '{'); ptr; ptr = strchr(ptr + 1, '{')) { if (ptr != value) { if (ptr[-1] == '\\') { continue; /* Escaped */ } } return true; } return false; } char* flecs_string_escape( char *str) { const char *ptr; char *out = str, ch; for (ptr = str; ptr[0]; ) { if (ptr[0] == '\\') { if (ptr[1] == '{') { /* Escape string interpolation delimiter */ ch = '{'; ptr += 2; } else if (ptr[1] == '$') { /* Escape string interpolation var */ ch = '$'; ptr += 2; } else { ptr = flecs_chrparse(ptr, &ch); if (!ptr) { ecs_err("invalid escape sequence in string '%s'", str); return NULL; } } } else { ch = ptr[0]; ptr ++; } out[0] = ch; out ++; } out[0] = '\0'; return out + 1; } bool flecs_value_is_0( const ecs_value_t *value) { ecs_entity_t type = value->type; void *ptr = value->ptr; if (type == ecs_id(ecs_i8_t)) { return *(ecs_i8_t*)ptr == 0; } else if (type == ecs_id(ecs_i16_t)) { return *(ecs_i16_t*)ptr == 0; } else if (type == ecs_id(ecs_i32_t)) { return *(ecs_i32_t*)ptr == 0; } else if (type == ecs_id(ecs_i64_t)) { return *(ecs_i64_t*)ptr == 0; } else if (type == ecs_id(ecs_iptr_t)) { return *(ecs_iptr_t*)ptr == 0; } else if (type == ecs_id(ecs_u8_t)) { return *(ecs_u8_t*)ptr == 0; } else if (type == ecs_id(ecs_u16_t)) { return *(ecs_u16_t*)ptr == 0; } else if (type == ecs_id(ecs_u32_t)) { return *(ecs_u32_t*)ptr == 0; } else if (type == ecs_id(ecs_u64_t)) { return *(ecs_u64_t*)ptr == 0; } else if (type == ecs_id(ecs_uptr_t)) { return *(ecs_uptr_t*)ptr == 0; } else if (type == ecs_id(ecs_f32_t)) { return ECS_EQZERO(*(ecs_f32_t*)ptr); } else if (type == ecs_id(ecs_f64_t)) { return ECS_EQZERO(*(ecs_f64_t*)ptr); } else { return true; } } #endif /** * @file addons/script/expr_ast.c * @brief Script expression AST implementation. */ #ifdef FLECS_SCRIPT typedef struct ecs_script_eval_ctx_t { const ecs_script_t *script; ecs_world_t *world; const ecs_expr_eval_desc_t *desc; ecs_expr_stack_t *stack; } ecs_script_eval_ctx_t; static int flecs_expr_visit_eval_priv( ecs_script_eval_ctx_t *ctx, ecs_expr_node_t *node, ecs_expr_value_t *out); static int flecs_expr_value_visit_eval( ecs_script_eval_ctx_t *ctx, ecs_expr_value_node_t *node, ecs_expr_value_t *out) { (void)ctx; out->value.type = node->node.type; out->value.ptr = node->ptr; out->owned = false; return 0; } static int flecs_expr_interpolated_string_visit_eval( ecs_script_eval_ctx_t *ctx, ecs_expr_interpolated_string_t *node, ecs_expr_value_t *out) { ecs_assert(node->node.type == ecs_id(ecs_string_t), ECS_INTERNAL_ERROR, NULL); ecs_strbuf_t buf = ECS_STRBUF_INIT; flecs_expr_stack_push(ctx->stack); int32_t i, e = 0, count = ecs_vec_count(&node->fragments); char **fragments = ecs_vec_first(&node->fragments); for (i = 0; i < count; i ++) { char *fragment = fragments[i]; if (fragment) { ecs_strbuf_appendstr(&buf, fragment); } else { ecs_expr_node_t *expr = ecs_vec_get_t( &node->expressions, ecs_expr_node_t*, e ++)[0]; ecs_expr_value_t *val = flecs_expr_stack_result(ctx->stack, (ecs_expr_node_t*)node); val->owned = true; if (flecs_expr_visit_eval_priv(ctx, expr, val)) { goto error; } ecs_assert(val->value.type == ecs_id(ecs_string_t), ECS_INTERNAL_ERROR, NULL); ecs_strbuf_appendstr(&buf, *(char**)val->value.ptr); } } *(char**)out->value.ptr = ecs_strbuf_get(&buf); out->owned = true; flecs_expr_stack_pop(ctx->stack); return 0; error: flecs_expr_stack_pop(ctx->stack); return -1; } static int flecs_expr_initializer_eval( ecs_script_eval_ctx_t *ctx, ecs_expr_initializer_t *node, void *value); static int flecs_expr_initializer_eval_static( ecs_script_eval_ctx_t *ctx, ecs_expr_initializer_t *node, void *value) { flecs_expr_stack_push(ctx->stack); ecs_expr_initializer_element_t *elems = ecs_vec_first(&node->elements); int32_t i, count = ecs_vec_count(&node->elements); for (i = 0; i < count; i ++) { ecs_expr_initializer_element_t *elem = &elems[i]; if (elem->value->kind == EcsExprInitializer) { if (flecs_expr_initializer_eval(ctx, (ecs_expr_initializer_t*)elem->value, value)) { goto error; } continue; } ecs_expr_value_t *expr = flecs_expr_stack_result(ctx->stack, elem->value); if (flecs_expr_visit_eval_priv(ctx, elem->value, expr)) { goto error; } /* Type is guaranteed to be correct, since type visitor will insert * a cast to the type of the initializer element. */ ecs_entity_t type = elem->value->type; if (!elem->operator) { if (expr->owned) { if (ecs_value_move(ctx->world, type, ECS_OFFSET(value, elem->offset), expr->value.ptr)) { goto error; } } else { if (ecs_value_copy(ctx->world, type, ECS_OFFSET(value, elem->offset), expr->value.ptr)) { goto error; } } } else { ecs_value_t dst = { .type = type, .ptr = ECS_OFFSET(value, elem->offset) }; if (flecs_value_binary( ctx->script, NULL, &expr->value, &dst, elem->operator)) { goto error; } } } flecs_expr_stack_pop(ctx->stack); return 0; error: flecs_expr_stack_pop(ctx->stack); return -1; } static int flecs_expr_initializer_eval_dynamic( ecs_script_eval_ctx_t *ctx, ecs_expr_initializer_t *node, void *value) { flecs_expr_stack_push(ctx->stack); ecs_meta_cursor_t cur = ecs_meta_cursor( ctx->world, node->node.type, value); if (ecs_meta_push(&cur)) { goto error; } int32_t i, count = ecs_vec_count(&node->elements); ecs_expr_initializer_element_t *elems = ecs_vec_first(&node->elements); for (i = 0; i < count; i ++) { ecs_expr_initializer_element_t *elem = &elems[i]; if (i) { ecs_meta_next(&cur); } if (elem->value->kind == EcsExprInitializer) { if (flecs_expr_initializer_eval(ctx, (ecs_expr_initializer_t*)elem->value, value)) { goto error; } continue; } ecs_expr_value_t *expr = flecs_expr_stack_result(ctx->stack, elem->value); if (flecs_expr_visit_eval_priv(ctx, elem->value, expr)) { goto error; } if (elem->member) { ecs_meta_member(&cur, elem->member); } ecs_value_t v_elem_value = { .ptr = expr->value.ptr, .type = expr->value.type }; if (ecs_meta_set_value(&cur, &v_elem_value)) { goto error; } } if (ecs_meta_pop(&cur)) { goto error; } flecs_expr_stack_pop(ctx->stack); return 0; error: flecs_expr_stack_pop(ctx->stack); return -1; } static int flecs_expr_initializer_eval( ecs_script_eval_ctx_t *ctx, ecs_expr_initializer_t *node, void *value) { if (node->is_dynamic) { return flecs_expr_initializer_eval_dynamic(ctx, node, value); } else { return flecs_expr_initializer_eval_static(ctx, node, value); } } static int flecs_expr_initializer_visit_eval( ecs_script_eval_ctx_t *ctx, ecs_expr_initializer_t *node, ecs_expr_value_t *out) { out->owned = true; return flecs_expr_initializer_eval(ctx, node, out->value.ptr); } static int flecs_expr_unary_visit_eval( ecs_script_eval_ctx_t *ctx, ecs_expr_unary_t *node, ecs_expr_value_t *out) { flecs_expr_stack_push(ctx->stack); ecs_expr_value_t *expr = flecs_expr_stack_result(ctx->stack, node->expr); if (flecs_expr_visit_eval_priv(ctx, node->expr, expr)) { goto error; } if (flecs_value_unary( ctx->script, &expr->value, &out->value, node->operator)) { goto error; } flecs_expr_stack_pop(ctx->stack); return 0; error: flecs_expr_stack_pop(ctx->stack); return -1; } static int flecs_expr_binary_visit_eval( ecs_script_eval_ctx_t *ctx, ecs_expr_binary_t *node, ecs_expr_value_t *out) { flecs_expr_stack_push(ctx->stack); /* Evaluate left & right expressions */ ecs_expr_value_t *left = flecs_expr_stack_result(ctx->stack, node->left); if (flecs_expr_visit_eval_priv(ctx, node->left, left)) { goto error; } ecs_expr_value_t *right = flecs_expr_stack_result(ctx->stack, node->right); if (flecs_expr_visit_eval_priv(ctx, node->right, right)) { goto error; } if (flecs_value_binary( ctx->script, &left->value, &right->value, &out->value, node->operator)) { goto error; } flecs_expr_stack_pop(ctx->stack); return 0; error: flecs_expr_stack_pop(ctx->stack); return -1; } static int flecs_expr_identifier_visit_eval( ecs_script_eval_ctx_t *ctx, ecs_expr_identifier_t *node, ecs_expr_value_t *out) { if (node->expr) { return flecs_expr_visit_eval_priv(ctx, node->expr, out); } else { ecs_assert(ctx->desc != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(ctx->desc->lookup_action != NULL, ECS_INTERNAL_ERROR, NULL); ecs_entity_t e = ctx->desc->lookup_action( ctx->world, node->value, ctx->desc->lookup_ctx); if (!e) { flecs_expr_visit_error(ctx->script, node, "unresolved identifier '%s'", node->value); goto error; } ecs_assert(out->value.type == ecs_id(ecs_entity_t), ECS_INTERNAL_ERROR, NULL); *(ecs_entity_t*)out->value.ptr = e; } return 0; error: return -1; } static int flecs_expr_variable_visit_eval( ecs_script_eval_ctx_t *ctx, ecs_expr_variable_t *node, ecs_expr_value_t *out) { ecs_assert(ctx->desc != NULL, ECS_INVALID_OPERATION, "variables available at parse time are not provided"); ecs_assert(ctx->desc->vars != NULL, ECS_INVALID_OPERATION, "variables available at parse time are not provided"); const ecs_script_var_t *var = flecs_script_find_var( ctx->desc->vars, node->name, ctx->desc->disable_dynamic_variable_binding ? &node->sp : NULL); if (!var) { flecs_expr_visit_error(ctx->script, node, "unresolved variable '%s'", node->name); goto error; } /* Should've been populated by type visitor */ ecs_assert(var != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(var->value.type == node->node.type, ECS_INTERNAL_ERROR, NULL); out->value = var->value; out->owned = false; return 0; error: return -1; } static int flecs_expr_global_variable_visit_eval( ecs_script_eval_ctx_t *ctx, ecs_expr_variable_t *node, ecs_expr_value_t *out) { (void)ctx; ecs_assert(ctx->desc != NULL, ECS_INVALID_OPERATION, "variables available at parse time are not provided"); ecs_assert(node->global_value.type == node->node.type, ECS_INTERNAL_ERROR, NULL); out->value = node->global_value; out->owned = false; return 0; } static int flecs_expr_cast_visit_eval( ecs_script_eval_ctx_t *ctx, ecs_expr_cast_t *node, ecs_expr_value_t *out) { flecs_expr_stack_push(ctx->stack); /* Evaluate expression to cast */ ecs_expr_value_t *expr = flecs_expr_stack_result(ctx->stack, node->expr); if (flecs_expr_visit_eval_priv(ctx, node->expr, expr)) { goto error; } /* Copy expression result to storage of casted-to type */ if (flecs_value_copy_to(ctx->world, &out->value, expr)) { flecs_expr_visit_error(ctx->script, node, "failed to cast value"); goto error; } flecs_expr_stack_pop(ctx->stack); return 0; error: flecs_expr_stack_pop(ctx->stack); return -1; } static bool flecs_expr_get_signed( const ecs_value_t *value, int64_t *out) { ecs_entity_t type = value->type; void *ptr = value->ptr; if (type == ecs_id(ecs_i8_t)) { *out = *(int8_t*)ptr; return true; } else if (type == ecs_id(ecs_i16_t)) { *out = *(int16_t*)ptr; return true; } else if (type == ecs_id(ecs_i32_t)) { *out = *(int32_t*)ptr; return true; } else if (type == ecs_id(ecs_i64_t)) { *out = *(int64_t*)ptr; return true; } return false; } static bool flecs_expr_get_unsigned( const ecs_value_t *value, uint64_t *out) { ecs_entity_t type = value->type; void *ptr = value->ptr; if (type == ecs_id(ecs_u8_t)) { *out = *(uint8_t*)ptr; return true; } else if (type == ecs_id(ecs_u16_t)) { *out = *(uint16_t*)ptr; return true; } else if (type == ecs_id(ecs_u32_t)) { *out = *(uint32_t*)ptr; return true; } else if (type == ecs_id(ecs_u64_t)) { *out = *(uint64_t*)ptr; return true; } return false; } static bool flecs_expr_get_float( const ecs_value_t *value, double *out) { ecs_entity_t type = value->type; void *ptr = value->ptr; if (type == ecs_id(ecs_f32_t)) { *out = (double)*(float*)ptr; return true; } else if (type == ecs_id(ecs_f64_t)) { *out = *(double*)ptr; return true; } return false; } #define FLECS_EXPR_NUMBER_CAST\ if (type == ecs_id(ecs_i8_t)) *(ecs_i8_t*)ptr = (ecs_i8_t)value;\ else if (type == ecs_id(ecs_i16_t)) *(ecs_i16_t*)ptr = (ecs_i16_t)value;\ else if (type == ecs_id(ecs_i32_t)) *(ecs_i32_t*)ptr = (ecs_i32_t)value;\ else if (type == ecs_id(ecs_i64_t)) *(ecs_i64_t*)ptr = (ecs_i64_t)value;\ else if (type == ecs_id(ecs_iptr_t)) *(ecs_iptr_t*)ptr = (ecs_iptr_t)value;\ else if (type == ecs_id(ecs_u8_t)) *(ecs_u8_t*)ptr = (ecs_u8_t)value;\ else if (type == ecs_id(ecs_u16_t)) *(ecs_u16_t*)ptr = (ecs_u16_t)value;\ else if (type == ecs_id(ecs_u32_t)) *(ecs_u32_t*)ptr = (ecs_u32_t)value;\ else if (type == ecs_id(ecs_u64_t)) *(ecs_u64_t*)ptr = (ecs_u64_t)value;\ else if (type == ecs_id(ecs_uptr_t)) *(ecs_uptr_t*)ptr = (ecs_uptr_t)value;\ else if (type == ecs_id(ecs_f32_t)) *(ecs_f32_t*)ptr = (ecs_f32_t)value;\ else if (type == ecs_id(ecs_f64_t)) *(ecs_f64_t*)ptr = (ecs_f64_t)value;\ static void flecs_expr_set_signed( const ecs_value_t *out, int64_t value) { ecs_entity_t type = out->type; void *ptr = out->ptr; FLECS_EXPR_NUMBER_CAST } static void flecs_expr_set_unsigned( const ecs_value_t *out, uint64_t value) { ecs_entity_t type = out->type; void *ptr = out->ptr; FLECS_EXPR_NUMBER_CAST } static void flecs_expr_set_float( const ecs_value_t *out, double value) { ecs_entity_t type = out->type; void *ptr = out->ptr; FLECS_EXPR_NUMBER_CAST } static int flecs_expr_cast_number_visit_eval( ecs_script_eval_ctx_t *ctx, ecs_expr_cast_t *node, ecs_expr_value_t *out) { flecs_expr_stack_push(ctx->stack); /* Evaluate expression to cast */ ecs_expr_value_t *expr = flecs_expr_stack_result(ctx->stack, node->expr); if (flecs_expr_visit_eval_priv(ctx, node->expr, expr)) { goto error; } int64_t signed_; uint64_t unsigned_; double float_; if (flecs_expr_get_signed(&expr->value, &signed_)) { flecs_expr_set_signed(&out->value, signed_); } else if (flecs_expr_get_unsigned(&expr->value, &unsigned_)) { flecs_expr_set_unsigned(&out->value, unsigned_); } else if (flecs_expr_get_float(&expr->value, &float_)) { flecs_expr_set_float(&out->value, float_); } flecs_expr_stack_pop(ctx->stack); return 0; error: flecs_expr_stack_pop(ctx->stack); return -1; } static int flecs_expr_function_args_visit_eval( ecs_script_eval_ctx_t *ctx, ecs_expr_initializer_t *node, ecs_value_t *args) { ecs_expr_initializer_element_t *elems = ecs_vec_first(&node->elements); int32_t i, count = ecs_vec_count(&node->elements); for (i = 0; i < count; i ++) { ecs_expr_initializer_element_t *elem = &elems[i]; ecs_expr_value_t *expr = flecs_expr_stack_result( ctx->stack, elem->value); if (flecs_expr_visit_eval_priv(ctx, elem->value, expr)) { goto error; } args[i] = expr->value; } return 0; error: return -1; } static int flecs_expr_function_visit_eval( ecs_script_eval_ctx_t *ctx, ecs_expr_function_t *node, ecs_expr_value_t *out) { flecs_expr_stack_push(ctx->stack); ecs_function_ctx_t call_ctx = { .world = ctx->world, .function = node->calldata.function, .ctx = node->calldata.ctx }; ecs_assert(out->value.ptr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_value_t *argv = NULL; int32_t argc = ecs_vec_count(&node->args->elements); if (argc) { argv = ecs_os_alloca_n(ecs_value_t, argc); if (flecs_expr_function_args_visit_eval(ctx, node->args, argv)) { goto error; } } node->calldata.callback(&call_ctx, argc, argv, &out->value); out->owned = true; flecs_expr_stack_pop(ctx->stack); return 0; error: flecs_expr_stack_pop(ctx->stack); return -1; } static int flecs_expr_method_visit_eval( ecs_script_eval_ctx_t *ctx, ecs_expr_function_t *node, ecs_expr_value_t *out) { flecs_expr_stack_push(ctx->stack); if (node->left) { ecs_expr_value_t *expr = flecs_expr_stack_result(ctx->stack, node->left); if (flecs_expr_visit_eval_priv(ctx, node->left, expr)) { goto error; } ecs_function_ctx_t call_ctx = { .world = ctx->world, .function = node->calldata.function, .ctx = node->calldata.ctx }; ecs_assert(expr->value.ptr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(out->value.ptr != NULL, ECS_INTERNAL_ERROR, NULL); int32_t argc = ecs_vec_count(&node->args->elements); ecs_value_t *argv = ecs_os_alloca_n(ecs_value_t, argc + 1); argv[0] = expr->value; if (argc) { if (flecs_expr_function_args_visit_eval( ctx, node->args, &argv[1])) { goto error; } } node->calldata.callback(&call_ctx, argc, argv, &out->value); out->owned = true; } flecs_expr_stack_pop(ctx->stack); return 0; error: flecs_expr_stack_pop(ctx->stack); return -1; } static int flecs_expr_member_visit_eval( ecs_script_eval_ctx_t *ctx, ecs_expr_member_t *node, ecs_expr_value_t *out) { flecs_expr_stack_push(ctx->stack); ecs_expr_value_t *expr = flecs_expr_stack_result(ctx->stack, node->left); if (flecs_expr_visit_eval_priv(ctx, node->left, expr)) { goto error; } out->value.ptr = ECS_OFFSET(expr->value.ptr, node->offset); out->value.type = node->node.type; out->owned = false; flecs_expr_stack_pop(ctx->stack); return 0; error: flecs_expr_stack_pop(ctx->stack); return -1; } static int flecs_expr_element_visit_eval( ecs_script_eval_ctx_t *ctx, ecs_expr_element_t *node, ecs_expr_value_t *out) { ecs_expr_value_t *expr = flecs_expr_stack_result(ctx->stack, node->left); if (flecs_expr_visit_eval_priv(ctx, node->left, expr)) { goto error; } ecs_expr_value_t *index = flecs_expr_stack_result(ctx->stack, node->index); if (flecs_expr_visit_eval_priv(ctx, node->index, index)) { goto error; } int64_t index_value = *(int64_t*)index->value.ptr; out->value.ptr = ECS_OFFSET(expr->value.ptr, node->elem_size * index_value); out->value.type = node->node.type; out->owned = false; return 0; error: return -1; } static int flecs_expr_match_visit_eval( ecs_script_eval_ctx_t *ctx, ecs_expr_match_t *node, ecs_expr_value_t *out) { flecs_expr_stack_push(ctx->stack); ecs_expr_value_t *expr = flecs_expr_stack_result(ctx->stack, node->expr); if (flecs_expr_visit_eval_priv(ctx, node->expr, expr)) { goto error; } int32_t i, count = ecs_vec_count(&node->elements); ecs_expr_match_element_t *elems = ecs_vec_first(&node->elements); for (i = 0; i < count; i ++) { ecs_expr_match_element_t *elem = &elems[i]; flecs_expr_stack_push(ctx->stack); ecs_expr_value_t *compare = flecs_expr_stack_result( ctx->stack, node->expr); if (flecs_expr_visit_eval_priv(ctx, elem->compare, compare)) { goto error; } bool value = false; ecs_value_t result = { .type = ecs_id(ecs_bool_t), .ptr = &value }; if (flecs_value_binary( ctx->script, &expr->value, &compare->value, &result, EcsTokEq)) { goto error; } flecs_expr_stack_pop(ctx->stack); if (value) { if (flecs_expr_visit_eval_priv(ctx, elem->expr, out)) { goto error; } break; } } if (i == count) { if (node->any.expr) { if (flecs_expr_visit_eval_priv(ctx, node->any.expr, out)) { goto error; } } else { char *str = ecs_ptr_to_str( ctx->world, expr->value.type, expr->value.ptr); flecs_expr_visit_error(ctx->script, node, "match value '%s' not handled by case", str); ecs_os_free(str); goto error; } } flecs_expr_stack_pop(ctx->stack); return 0; error: flecs_expr_stack_pop(ctx->stack); return -1; } static int flecs_expr_component_visit_eval( ecs_script_eval_ctx_t *ctx, ecs_expr_element_t *node, ecs_expr_value_t *out) { ecs_expr_value_t *left = flecs_expr_stack_result(ctx->stack, node->left); if (flecs_expr_visit_eval_priv(ctx, node->left, left)) { goto error; } /* Left side of expression must be of entity type */ ecs_assert(left->value.type == ecs_id(ecs_entity_t), ECS_INTERNAL_ERROR, NULL); /* Component must be resolvable at parse time */ ecs_expr_node_t *index = node->index; if (index->kind == EcsExprIdentifier) { index = ((ecs_expr_identifier_t*)index)->expr; } ecs_assert(index->kind == EcsExprValue, ECS_INTERNAL_ERROR, NULL); ecs_entity_t entity = *(ecs_entity_t*)left->value.ptr; ecs_entity_t component = ((ecs_expr_value_node_t*)index)->storage.entity; ecs_assert(out->value.type == node->node.type, ECS_INTERNAL_ERROR, NULL); out->value.ptr = ECS_CONST_CAST(void*, ecs_get_id(ctx->world, entity, component)); out->owned = false; if (!out->value.ptr) { char *estr = ecs_get_path(ctx->world, entity); char *cstr = ecs_get_path(ctx->world, component); flecs_expr_visit_error(ctx->script, node, "entity '%s' does not have component '%s'", estr, cstr); ecs_os_free(estr); ecs_os_free(cstr); goto error; } return 0; error: return -1; } static int flecs_expr_visit_eval_priv( ecs_script_eval_ctx_t *ctx, ecs_expr_node_t *node, ecs_expr_value_t *out) { ecs_assert(node != NULL, ECS_INVALID_PARAMETER, NULL); switch(node->kind) { case EcsExprValue: if (flecs_expr_value_visit_eval( ctx, (ecs_expr_value_node_t*)node, out)) { goto error; } break; case EcsExprInterpolatedString: if (flecs_expr_interpolated_string_visit_eval( ctx, (ecs_expr_interpolated_string_t*)node, out)) { goto error; } break; case EcsExprEmptyInitializer: break; case EcsExprInitializer: if (flecs_expr_initializer_visit_eval( ctx, (ecs_expr_initializer_t*)node, out)) { goto error; } break; case EcsExprUnary: if (flecs_expr_unary_visit_eval( ctx, (ecs_expr_unary_t*)node, out)) { goto error; } break; case EcsExprBinary: if (flecs_expr_binary_visit_eval( ctx, (ecs_expr_binary_t*)node, out)) { goto error; } break; case EcsExprIdentifier: if (flecs_expr_identifier_visit_eval( ctx, (ecs_expr_identifier_t*)node, out)) { goto error; } break; case EcsExprVariable: if (flecs_expr_variable_visit_eval( ctx, (ecs_expr_variable_t*)node, out)) { goto error; } break; case EcsExprGlobalVariable: if (flecs_expr_global_variable_visit_eval( ctx, (ecs_expr_variable_t*)node, out)) { goto error; } break; case EcsExprFunction: if (flecs_expr_function_visit_eval( ctx, (ecs_expr_function_t*)node, out)) { goto error; } break; case EcsExprMethod: if (flecs_expr_method_visit_eval( ctx, (ecs_expr_function_t*)node, out)) { goto error; } break; case EcsExprMember: if (flecs_expr_member_visit_eval( ctx, (ecs_expr_member_t*)node, out)) { goto error; } break; case EcsExprElement: if (flecs_expr_element_visit_eval( ctx, (ecs_expr_element_t*)node, out)) { goto error; } break; case EcsExprMatch: if (flecs_expr_match_visit_eval( ctx, (ecs_expr_match_t*)node, out)) { goto error; } break; case EcsExprComponent: if (flecs_expr_component_visit_eval( ctx, (ecs_expr_element_t*)node, out)) { goto error; } break; case EcsExprCast: if (flecs_expr_cast_visit_eval( ctx, (ecs_expr_cast_t*)node, out)) { goto error; } break; case EcsExprCastNumber: if (flecs_expr_cast_number_visit_eval( ctx, (ecs_expr_cast_t*)node, out)) { goto error; } break; } return 0; error: return -1; } int flecs_expr_visit_eval( const ecs_script_t *script, ecs_expr_node_t *node, const ecs_expr_eval_desc_t *desc, ecs_value_t *out) { ecs_expr_stack_t *stack = NULL, stack_local; if (desc && desc->runtime) { stack = &desc->runtime->expr_stack; ecs_assert(stack->frame == 0, ECS_INTERNAL_ERROR, NULL); } if (!stack) { stack = &stack_local; flecs_expr_stack_init(stack); } flecs_expr_stack_push(stack); ecs_expr_value_t val_tmp; ecs_expr_value_t *val; if (out->type && (out->type == node->type) && out->ptr) { val_tmp = (ecs_expr_value_t){ .value = *out, .owned = false, .type_info = ecs_get_type_info(script->world, out->type) }; val = &val_tmp; } else { val = flecs_expr_stack_result(stack, node); } ecs_script_eval_ctx_t ctx = { .script = script, .world = script->world, .stack = stack, .desc = desc }; if (flecs_expr_visit_eval_priv(&ctx, node, val)) { goto error; } if (desc && !out->type) { out->type = desc->type; } if (!out->type) { out->type = node->type; } if (out->type && !out->ptr) { out->ptr = ecs_value_new(ctx.world, out->type); } if (val != &val_tmp || out->ptr != val->value.ptr) { if (val->owned) { /* Values owned by the runtime can be moved to output */ if (flecs_value_move_to(ctx.world, out, &val->value)) { flecs_expr_visit_error(script, node, "failed to write to output"); goto error; } } else { /* Values not owned by runtime should be copied */ if (flecs_value_copy_to(ctx.world, out, val)) { flecs_expr_visit_error(script, node, "failed to write to output"); goto error; } } } flecs_expr_stack_pop(stack); if (stack == &stack_local) { flecs_expr_stack_fini(stack); } return 0; error: flecs_expr_stack_pop(stack); if (stack == &stack_local) { flecs_expr_stack_fini(stack); } return -1; } #endif /** * @file addons/script/expr_fold.c * @brief Script expression constant folding. */ #ifdef FLECS_SCRIPT static void flecs_visit_fold_replace( ecs_script_t *script, ecs_expr_node_t **node_ptr, ecs_expr_node_t *with) { ecs_assert(*node_ptr != with, ECS_INTERNAL_ERROR, NULL); flecs_expr_visit_free(script, *node_ptr); *node_ptr = with; } static int flecs_expr_unary_visit_fold( ecs_script_t *script, ecs_expr_node_t **node_ptr, const ecs_expr_eval_desc_t *desc) { ecs_expr_unary_t *node = (ecs_expr_unary_t*)*node_ptr; if (node->operator != EcsTokNot) { flecs_expr_visit_error(script, node, "operator invalid for unary expression"); goto error; } if (flecs_expr_visit_fold(script, &node->expr, desc)) { goto error; } if (node->expr->kind != EcsExprValue) { /* Only folding literals */ return 0; } if (node->expr->type != ecs_id(ecs_bool_t)) { char *type_str = ecs_get_path(script->world, node->node.type); flecs_expr_visit_error(script, node, "! operator cannot be applied to value of type '%s' (must be bool)"); ecs_os_free(type_str); goto error; } ecs_expr_value_node_t *result = flecs_expr_value_from( script, (ecs_expr_node_t*)node, ecs_id(ecs_bool_t)); result->ptr = &result->storage.bool_; ecs_value_t dst = { .ptr = result->ptr, .type = ecs_id(ecs_bool_t) }; ecs_value_t src = { .ptr = ((ecs_expr_value_node_t*)node->expr)->ptr, .type = ecs_id(ecs_bool_t) }; if (flecs_value_unary(script, &src, &dst, node->operator)) { goto error; } flecs_visit_fold_replace(script, node_ptr, (ecs_expr_node_t*)result); return 0; error: return -1; } static int flecs_expr_binary_visit_fold( ecs_script_t *script, ecs_expr_node_t **node_ptr, const ecs_expr_eval_desc_t *desc) { ecs_expr_binary_t *node = (ecs_expr_binary_t*)*node_ptr; if (flecs_expr_visit_fold(script, &node->left, desc)) { goto error; } if (flecs_expr_visit_fold(script, &node->right, desc)) { goto error; } if (node->left->kind != EcsExprValue || node->right->kind != EcsExprValue) { /* Only folding literals */ return 0; } ecs_expr_value_node_t *left = (ecs_expr_value_node_t*)node->left; ecs_expr_value_node_t *right = (ecs_expr_value_node_t*)node->right; ecs_value_t lop = { .type = left->node.type, .ptr = left->ptr }; ecs_value_t rop = { .type = right->node.type, .ptr = right->ptr }; /* flecs_value_binary will detect division by 0, but we have more * information about where it happens here. */ if (node->operator == EcsTokDiv || node->operator == EcsTokMod) { if (flecs_value_is_0(&rop)) { flecs_expr_visit_error(script, node, "invalid division by zero"); goto error; } } ecs_expr_value_node_t *result = flecs_expr_value_from( script, (ecs_expr_node_t*)node, node->node.type); ecs_value_t res = { .type = result->node.type, .ptr = result->ptr }; if (flecs_value_binary(script, &lop, &rop, &res, node->operator)) { goto error; } flecs_visit_fold_replace(script, node_ptr, (ecs_expr_node_t*)result); return 0; error: return -1; } static int flecs_expr_cast_visit_fold( ecs_script_t *script, ecs_expr_node_t **node_ptr, const ecs_expr_eval_desc_t *desc) { ecs_expr_cast_t *node = (ecs_expr_cast_t*)*node_ptr; if (flecs_expr_visit_fold(script, &node->expr, desc)) { goto error; } if (node->expr->kind != EcsExprValue) { /* Only folding literals for now */ return 0; } ecs_expr_value_node_t *expr = (ecs_expr_value_node_t*)node->expr; ecs_entity_t dst_type = node->node.type; ecs_entity_t src_type = expr->node.type; if (dst_type == src_type) { /* No cast necessary if types are equal */ return 0; } void *dst_ptr = ecs_value_new(script->world, dst_type); ecs_meta_cursor_t cur = ecs_meta_cursor(script->world, dst_type, dst_ptr); ecs_value_t value = { .type = src_type, .ptr = expr->ptr }; if (ecs_meta_set_value(&cur, &value)) { flecs_expr_visit_error(script, node, "failed to assign value"); ecs_value_free(script->world, dst_type, dst_ptr); goto error; } if (expr->ptr != &expr->storage) { ecs_value_free(script->world, expr->node.type, expr->ptr); } expr->node.type = dst_type; expr->ptr = dst_ptr; node->expr = NULL; /* Prevent cleanup */ flecs_visit_fold_replace(script, node_ptr, (ecs_expr_node_t*)expr); return 0; error: return -1; } static int flecs_expr_interpolated_string_visit_fold( ecs_script_t *script, ecs_expr_node_t **node_ptr, const ecs_expr_eval_desc_t *desc) { ecs_expr_interpolated_string_t *node = (ecs_expr_interpolated_string_t*)*node_ptr; bool can_fold = true; int32_t i, e = 0, count = ecs_vec_count(&node->fragments); char **fragments = ecs_vec_first(&node->fragments); for (i = 0; i < count; i ++) { char *fragment = fragments[i]; if (!fragment) { ecs_expr_node_t **expr_ptr = ecs_vec_get_t( &node->expressions, ecs_expr_node_t*, e ++); if (flecs_expr_visit_fold(script, expr_ptr, desc)) { goto error; } if (expr_ptr[0]->kind != EcsExprValue) { can_fold = false; } } } if (can_fold) { ecs_strbuf_t buf = ECS_STRBUF_INIT; e = 0; for (i = 0; i < count; i ++) { char *fragment = fragments[i]; if (fragment) { ecs_strbuf_appendstr(&buf, fragment); } else { ecs_expr_node_t *expr = ecs_vec_get_t( &node->expressions, ecs_expr_node_t*, e ++)[0]; ecs_assert(expr->kind == EcsExprValue, ECS_INTERNAL_ERROR, NULL); ecs_assert(expr->type == ecs_id(ecs_string_t), ECS_INTERNAL_ERROR, NULL); ecs_expr_value_node_t *val = (ecs_expr_value_node_t*)expr; ecs_strbuf_appendstr(&buf, *(char**)val->ptr); } } char **value = ecs_value_new(script->world, ecs_id(ecs_string_t)); *value = ecs_strbuf_get(&buf); ecs_expr_value_node_t *result = flecs_expr_value_from( script, (ecs_expr_node_t*)node, ecs_id(ecs_string_t)); result->ptr = value; flecs_visit_fold_replace(script, node_ptr, (ecs_expr_node_t*)result); } return 0; error: return -1; } static int flecs_expr_initializer_pre_fold( ecs_script_t *script, ecs_expr_initializer_t *node, const ecs_expr_eval_desc_t *desc, bool *can_fold) { ecs_expr_initializer_element_t *elems = ecs_vec_first(&node->elements); int32_t i, count = ecs_vec_count(&node->elements); for (i = 0; i < count; i ++) { ecs_expr_initializer_element_t *elem = &elems[i]; /* If this is a nested initializer, don't fold it but instead fold its * values. Because nested initializers are flattened, this ensures that * we'll be using the correct member offsets later. */ if (elem->value->kind == EcsExprInitializer) { if (flecs_expr_initializer_pre_fold( script, (ecs_expr_initializer_t*)elem->value, desc, can_fold)) { goto error; } continue; } if (flecs_expr_visit_fold(script, &elem->value, desc)) { goto error; } if (elem->value->kind != EcsExprValue) { *can_fold = false; } if (elem->operator) { *can_fold = false; } } if (node->is_dynamic) { *can_fold = false; return 0; } return 0; error: return -1; } static int flecs_expr_initializer_post_fold( ecs_script_t *script, ecs_expr_initializer_t *node, void *value) { ecs_expr_initializer_element_t *elems = ecs_vec_first(&node->elements); int32_t i, count = ecs_vec_count(&node->elements); for (i = 0; i < count; i ++) { ecs_expr_initializer_element_t *elem = &elems[i]; if (elem->value->kind == EcsExprInitializer) { if (flecs_expr_initializer_post_fold( script, (ecs_expr_initializer_t*)elem->value, value)) { goto error; } continue; } ecs_expr_value_node_t *elem_value = (ecs_expr_value_node_t*)elem->value; /* Type is guaranteed to be correct, since type visitor will insert * a cast to the type of the initializer element. */ ecs_entity_t type = elem_value->node.type; if (ecs_value_copy(script->world, type, ECS_OFFSET(value, elem->offset), elem_value->ptr)) { goto error; } } return 0; error: return -1; } static int flecs_expr_initializer_visit_fold( ecs_script_t *script, ecs_expr_node_t **node_ptr, const ecs_expr_eval_desc_t *desc) { bool can_fold = true; ecs_expr_initializer_t *node = (ecs_expr_initializer_t*)*node_ptr; if (flecs_expr_initializer_pre_fold(script, node, desc, &can_fold)) { goto error; } /* If all elements of initializer fold to literals, initializer itself can * be folded into a literal. */ if (can_fold) { void *value = ecs_value_new(script->world, node->node.type); if (flecs_expr_initializer_post_fold(script, node, value)) { goto error; } ecs_expr_value_node_t *result = flecs_expr_value_from( script, (ecs_expr_node_t*)node, node->node.type); result->ptr = value; flecs_visit_fold_replace(script, node_ptr, (ecs_expr_node_t*)result); } return 0; error: return -1; } static int flecs_expr_identifier_visit_fold( ecs_script_t *script, ecs_expr_node_t **node_ptr, const ecs_expr_eval_desc_t *desc) { (void)desc; ecs_expr_identifier_t *node = (ecs_expr_identifier_t*)*node_ptr; ecs_expr_node_t *expr = node->expr; if (expr) { node->expr = NULL; flecs_visit_fold_replace(script, node_ptr, expr); } return 0; } static int flecs_expr_variable_visit_fold( ecs_script_t *script, ecs_expr_node_t **node_ptr, const ecs_expr_eval_desc_t *desc) { (void)desc; ecs_expr_variable_t *node = (ecs_expr_variable_t*)*node_ptr; ecs_script_var_t *var = flecs_script_find_var( desc->vars, node->name, &node->sp); /* Should've been caught by type visitor */ ecs_assert(var != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(var->value.type == node->node.type, ECS_INTERNAL_ERROR, NULL); ecs_entity_t type = node->node.type; if (var->is_const) { ecs_expr_value_node_t *result = flecs_expr_value_from( script, (ecs_expr_node_t*)node, type); void *value = ecs_value_new(script->world, type); ecs_value_copy(script->world, type, value, var->value.ptr); result->ptr = value; flecs_visit_fold_replace(script, node_ptr, (ecs_expr_node_t*)result); } return 0; } static int flecs_expr_global_variable_visit_fold( ecs_script_t *script, ecs_expr_node_t **node_ptr, const ecs_expr_eval_desc_t *desc) { (void)desc; ecs_expr_variable_t *node = (ecs_expr_variable_t*)*node_ptr; ecs_entity_t type = node->node.type; /* Global const variables are always const, so we can always fold */ ecs_expr_value_node_t *result = flecs_expr_value_from( script, (ecs_expr_node_t*)node, type); void *value = ecs_value_new(script->world, type); ecs_value_copy(script->world, type, value, node->global_value.ptr); result->ptr = value; flecs_visit_fold_replace(script, node_ptr, (ecs_expr_node_t*)result); return 0; } static int flecs_expr_arguments_visit_fold( ecs_script_t *script, ecs_expr_initializer_t *node, const ecs_expr_eval_desc_t *desc) { ecs_expr_initializer_element_t *elems = ecs_vec_first(&node->elements); int32_t i, count = ecs_vec_count(&node->elements); for (i = 0; i < count; i ++) { ecs_expr_initializer_element_t *elem = &elems[i]; if (flecs_expr_visit_fold(script, &elem->value, desc)) { goto error; } } return 0; error: return -1; } static int flecs_expr_function_visit_fold( ecs_script_t *script, ecs_expr_node_t **node_ptr, const ecs_expr_eval_desc_t *desc) { ecs_expr_function_t *node = (ecs_expr_function_t*)*node_ptr; if (flecs_expr_visit_fold(script, &node->left, desc)) { goto error; } if (node->args) { if (flecs_expr_arguments_visit_fold(script, node->args, desc)) { goto error; } } return 0; error: return -1; } static int flecs_expr_member_visit_fold( ecs_script_t *script, ecs_expr_node_t **node_ptr, const ecs_expr_eval_desc_t *desc) { ecs_expr_member_t *node = (ecs_expr_member_t*)*node_ptr; if (flecs_expr_visit_fold(script, &node->left, desc)) { goto error; } return 0; error: return -1; } static int flecs_expr_element_visit_fold( ecs_script_t *script, ecs_expr_node_t **node_ptr, const ecs_expr_eval_desc_t *desc) { ecs_expr_element_t *node = (ecs_expr_element_t*)*node_ptr; if (flecs_expr_visit_fold(script, &node->left, desc)) { goto error; } if (flecs_expr_visit_fold(script, &node->index, desc)) { goto error; } return 0; error: return -1; } static int flecs_expr_match_visit_fold( ecs_script_t *script, ecs_expr_node_t **node_ptr, const ecs_expr_eval_desc_t *desc) { ecs_expr_match_t *node = (ecs_expr_match_t*)*node_ptr; if (flecs_expr_visit_fold(script, &node->expr, desc)) { goto error; } int32_t i, count = ecs_vec_count(&node->elements); ecs_expr_match_element_t *elems = ecs_vec_first(&node->elements); for (i = 0; i < count; i ++) { ecs_expr_match_element_t *elem = &elems[i]; if (flecs_expr_visit_fold(script, &elem->compare, desc)) { goto error; } if (flecs_expr_visit_fold(script, &elem->expr, desc)) { goto error; } } return 0; error: return -1; } int flecs_expr_visit_fold( ecs_script_t *script, ecs_expr_node_t **node_ptr, const ecs_expr_eval_desc_t *desc) { ecs_assert(node_ptr != NULL, ECS_INVALID_PARAMETER, NULL); ecs_expr_node_t *node = *node_ptr; switch(node->kind) { case EcsExprValue: break; case EcsExprInterpolatedString: if (flecs_expr_interpolated_string_visit_fold(script, node_ptr, desc)) { goto error; } break; case EcsExprInitializer: case EcsExprEmptyInitializer: if (flecs_expr_initializer_visit_fold(script, node_ptr, desc)) { goto error; } break; case EcsExprUnary: if (flecs_expr_unary_visit_fold(script, node_ptr, desc)) { goto error; } break; case EcsExprBinary: if (flecs_expr_binary_visit_fold(script, node_ptr, desc)) { goto error; } break; case EcsExprIdentifier: if (flecs_expr_identifier_visit_fold(script, node_ptr, desc)) { goto error; } break; case EcsExprVariable: if (flecs_expr_variable_visit_fold(script, node_ptr, desc)) { goto error; } break; case EcsExprGlobalVariable: if (flecs_expr_global_variable_visit_fold(script, node_ptr, desc)) { goto error; } break; case EcsExprFunction: case EcsExprMethod: if (flecs_expr_function_visit_fold(script, node_ptr, desc)) { goto error; } break; case EcsExprMember: if (flecs_expr_member_visit_fold(script, node_ptr, desc)) { goto error; } break; case EcsExprElement: case EcsExprComponent: if (flecs_expr_element_visit_fold(script, node_ptr, desc)) { goto error; } break; case EcsExprMatch: if (flecs_expr_match_visit_fold(script, node_ptr, desc)) { goto error; } break; case EcsExprCast: case EcsExprCastNumber: if (flecs_expr_cast_visit_fold(script, node_ptr, desc)) { goto error; } break; } return 0; error: return -1; } #endif /** * @file addons/script/expr/visit_free.c * @brief Visitor to free expression AST. */ #ifdef FLECS_SCRIPT static void flecs_expr_value_visit_free( ecs_script_t *script, ecs_expr_value_node_t *node) { if (node->ptr != &node->storage) { ecs_value_free(script->world, node->node.type, node->ptr); } } static void flecs_expr_interpolated_string_visit_free( ecs_script_t *script, ecs_expr_interpolated_string_t *node) { int32_t i, count = ecs_vec_count(&node->expressions); ecs_expr_node_t **expressions = ecs_vec_first(&node->expressions); for (i = 0; i < count; i ++) { flecs_expr_visit_free(script, expressions[i]); } ecs_vec_fini_t(&flecs_script_impl(script)->allocator, &node->fragments, char*); ecs_vec_fini_t(&flecs_script_impl(script)->allocator, &node->expressions, ecs_expr_node_t*); flecs_free_n(&flecs_script_impl(script)->allocator, char, node->buffer_size, node->buffer); } static void flecs_expr_initializer_visit_free( ecs_script_t *script, ecs_expr_initializer_t *node) { ecs_expr_initializer_element_t *elems = ecs_vec_first(&node->elements); int32_t i, count = ecs_vec_count(&node->elements); for (i = 0; i < count; i ++) { ecs_expr_initializer_element_t *elem = &elems[i]; flecs_expr_visit_free(script, elem->value); } ecs_allocator_t *a = &flecs_script_impl(script)->allocator; ecs_vec_fini_t(a, &node->elements, ecs_expr_initializer_element_t); } static void flecs_expr_unary_visit_free( ecs_script_t *script, ecs_expr_unary_t *node) { flecs_expr_visit_free(script, node->expr); } static void flecs_expr_binary_visit_free( ecs_script_t *script, ecs_expr_binary_t *node) { flecs_expr_visit_free(script, node->left); flecs_expr_visit_free(script, node->right); } static void flecs_expr_identifier_visit_free( ecs_script_t *script, ecs_expr_identifier_t *node) { flecs_expr_visit_free(script, node->expr); } static void flecs_expr_function_visit_free( ecs_script_t *script, ecs_expr_function_t *node) { flecs_expr_visit_free(script, node->left); flecs_expr_visit_free(script, (ecs_expr_node_t*)node->args); } static void flecs_expr_member_visit_free( ecs_script_t *script, ecs_expr_member_t *node) { flecs_expr_visit_free(script, node->left); } static void flecs_expr_element_visit_free( ecs_script_t *script, ecs_expr_element_t *node) { flecs_expr_visit_free(script, node->left); flecs_expr_visit_free(script, node->index); } static void flecs_expr_match_visit_free( ecs_script_t *script, ecs_expr_match_t *node) { flecs_expr_visit_free(script, node->expr); int32_t i, count = ecs_vec_count(&node->elements); ecs_expr_match_element_t *elems = ecs_vec_first(&node->elements); for (i = 0; i < count; i ++) { ecs_expr_match_element_t *elem = &elems[i]; flecs_expr_visit_free(script, elem->compare); flecs_expr_visit_free(script, elem->expr); } if (node->any.compare) { flecs_expr_visit_free(script, node->any.compare); } if (node->any.expr) { flecs_expr_visit_free(script, node->any.expr); } ecs_vec_fini_t(&flecs_script_impl(script)->allocator, &node->elements, ecs_expr_match_element_t); } static void flecs_expr_cast_visit_free( ecs_script_t *script, ecs_expr_cast_t *node) { flecs_expr_visit_free(script, node->expr); } void flecs_expr_visit_free( ecs_script_t *script, ecs_expr_node_t *node) { if (!node) { return; } ecs_allocator_t *a = &flecs_script_impl(script)->allocator; switch(node->kind) { case EcsExprValue: flecs_expr_value_visit_free( script, (ecs_expr_value_node_t*)node); flecs_free_t(a, ecs_expr_value_node_t, node); break; case EcsExprInterpolatedString: flecs_expr_interpolated_string_visit_free( script, (ecs_expr_interpolated_string_t*)node); flecs_free_t(a, ecs_expr_interpolated_string_t, node); break; case EcsExprInitializer: case EcsExprEmptyInitializer: flecs_expr_initializer_visit_free( script, (ecs_expr_initializer_t*)node); flecs_free_t(a, ecs_expr_initializer_t, node); break; case EcsExprUnary: flecs_expr_unary_visit_free( script, (ecs_expr_unary_t*)node); flecs_free_t(a, ecs_expr_unary_t, node); break; case EcsExprBinary: flecs_expr_binary_visit_free( script, (ecs_expr_binary_t*)node); flecs_free_t(a, ecs_expr_binary_t, node); break; case EcsExprIdentifier: flecs_expr_identifier_visit_free( script, (ecs_expr_identifier_t*)node); flecs_free_t(a, ecs_expr_identifier_t, node); break; case EcsExprVariable: case EcsExprGlobalVariable: flecs_free_t(a, ecs_expr_variable_t, node); break; case EcsExprFunction: case EcsExprMethod: flecs_expr_function_visit_free( script, (ecs_expr_function_t*)node); flecs_free_t(a, ecs_expr_function_t, node); break; case EcsExprMember: flecs_expr_member_visit_free( script, (ecs_expr_member_t*)node); flecs_free_t(a, ecs_expr_member_t, node); break; case EcsExprElement: case EcsExprComponent: flecs_expr_element_visit_free( script, (ecs_expr_element_t*)node); flecs_free_t(a, ecs_expr_element_t, node); break; case EcsExprMatch: flecs_expr_match_visit_free( script, (ecs_expr_match_t*)node); flecs_free_t(a, ecs_expr_match_t, node); break; case EcsExprCast: case EcsExprCastNumber: flecs_expr_cast_visit_free( script, (ecs_expr_cast_t*)node); flecs_free_t(a, ecs_expr_cast_t, node); break; } } #endif /** * @file addons/script/expr_to_str.c * @brief Script expression AST to string visitor. */ #ifdef FLECS_SCRIPT typedef struct ecs_expr_str_visitor_t { const ecs_world_t *world; ecs_strbuf_t *buf; int32_t depth; bool newline; bool colors; } ecs_expr_str_visitor_t; static int flecs_expr_node_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_node_t *node); static void flecs_expr_color_to_str( ecs_expr_str_visitor_t *v, const char *color) { if (v->colors) ecs_strbuf_appendstr(v->buf, color); } static int flecs_expr_value_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_value_node_t *node) { flecs_expr_color_to_str(v, ECS_YELLOW); int ret = ecs_ptr_to_str_buf( v->world, node->node.type, node->ptr, v->buf); flecs_expr_color_to_str(v, ECS_NORMAL); return ret; } static int flecs_expr_interpolated_string_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_interpolated_string_t *node) { int32_t i, e = 0, count = ecs_vec_count(&node->fragments); char **fragments = ecs_vec_first(&node->fragments); ecs_expr_node_t **expressions = ecs_vec_first(&node->expressions); ecs_strbuf_appendlit(v->buf, "interpolated("); for (i = 0; i < count; i ++) { char *fragment = fragments[i]; if (i) { ecs_strbuf_appendlit(v->buf, ", "); } if (fragment) { flecs_expr_color_to_str(v, ECS_YELLOW); ecs_strbuf_appendlit(v->buf, "\""); ecs_strbuf_appendstr(v->buf, fragment); ecs_strbuf_appendlit(v->buf, "\""); flecs_expr_color_to_str(v, ECS_NORMAL); } else { ecs_expr_node_t *expr = expressions[e ++]; if (flecs_expr_node_to_str(v, expr)) { return -1; } } } ecs_strbuf_appendlit(v->buf, ")"); return 0; } static int flecs_expr_unary_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_unary_t *node) { ecs_strbuf_appendstr(v->buf, flecs_script_token_str(node->operator)); if (flecs_expr_node_to_str(v, node->expr)) { goto error; } return 0; error: return -1; } static int flecs_expr_initializer_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_initializer_t *node) { ecs_strbuf_appendlit(v->buf, "{"); ecs_expr_initializer_element_t *elems = ecs_vec_first(&node->elements); int32_t i, count = ecs_vec_count(&node->elements); for (i = 0; i < count; i ++) { if (i) { ecs_strbuf_appendstr(v->buf, ", "); } ecs_expr_initializer_element_t *elem = &elems[i]; if (elem->member) { ecs_strbuf_appendstr(v->buf, elem->member); ecs_strbuf_appendlit(v->buf, ":"); } if (flecs_expr_node_to_str(v, elem->value)) { goto error; } } ecs_strbuf_appendlit(v->buf, "}"); return 0; error: return -1; } static int flecs_expr_binary_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_binary_t *node) { ecs_strbuf_appendlit(v->buf, "("); if (flecs_expr_node_to_str(v, node->left)) { goto error; } ecs_strbuf_appendlit(v->buf, " "); ecs_strbuf_appendstr(v->buf, flecs_script_token_str(node->operator)); ecs_strbuf_appendlit(v->buf, " "); if (flecs_expr_node_to_str(v, node->right)) { goto error; } ecs_strbuf_appendlit(v->buf, ")"); return 0; error: return -1; } static int flecs_expr_identifier_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_identifier_t *node) { ecs_strbuf_appendlit(v->buf, "@"); ecs_strbuf_appendstr(v->buf, node->value); return 0; } static int flecs_expr_variable_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_variable_t *node) { flecs_expr_color_to_str(v, ECS_GREEN); ecs_strbuf_appendlit(v->buf, "$"); ecs_strbuf_appendstr(v->buf, node->name); flecs_expr_color_to_str(v, ECS_NORMAL); return 0; } static int flecs_expr_member_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_member_t *node) { if (flecs_expr_node_to_str(v, node->left)) { return -1; } ecs_strbuf_appendlit(v->buf, "."); ecs_strbuf_appendstr(v->buf, node->member_name); return 0; } static int flecs_expr_function_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_function_t *node) { if (node->left) { if (flecs_expr_node_to_str(v, node->left)) { return -1; } ecs_strbuf_appendlit(v->buf, "."); } ecs_strbuf_append(v->buf, "%s(", node->function_name); if (node->args) { if (flecs_expr_node_to_str(v, (ecs_expr_node_t*)node->args)) { return -1; } } ecs_strbuf_append(v->buf, ")"); return 0; } static int flecs_expr_element_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_element_t *node) { if (flecs_expr_node_to_str(v, node->left)) { return -1; } ecs_strbuf_appendlit(v->buf, "["); if (flecs_expr_node_to_str(v, node->index)) { return -1; } ecs_strbuf_appendlit(v->buf, "]"); return 0; } static int flecs_expr_match_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_match_t *node) { if (node->node.type) { flecs_expr_color_to_str(v, ECS_BLUE); const char *name = ecs_get_name(v->world, node->node.type); if (name) { ecs_strbuf_appendstr(v->buf, name); } else { char *path = ecs_get_path(v->world, node->node.type); ecs_strbuf_appendstr(v->buf, path); ecs_os_free(path); } flecs_expr_color_to_str(v, ECS_NORMAL); ecs_strbuf_appendlit(v->buf, "("); } flecs_expr_color_to_str(v, ECS_BLUE); ecs_strbuf_appendlit(v->buf, "match "); flecs_expr_color_to_str(v, ECS_GREEN); if (flecs_expr_node_to_str(v, node->expr)) { return -1; } ecs_strbuf_appendlit(v->buf, " {\n"); int32_t i, count = ecs_vec_count(&node->elements); ecs_expr_match_element_t *elems = ecs_vec_first(&node->elements); for (i = 0; i < count; i ++) { ecs_strbuf_appendlit(v->buf, " "); ecs_expr_match_element_t *elem = &elems[i]; if (flecs_expr_node_to_str(v, elem->compare)) { return -1; } ecs_strbuf_appendlit(v->buf, ": "); if (flecs_expr_node_to_str(v, elem->expr)) { return -1; } ecs_strbuf_appendlit(v->buf, "\n"); } ecs_strbuf_appendlit(v->buf, "}"); if (node->node.type) { ecs_strbuf_appendlit(v->buf, ")"); } ecs_strbuf_appendlit(v->buf, "\n"); return 0; } static int flecs_expr_cast_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_cast_t *node) { ecs_entity_t type = node->node.type; flecs_expr_color_to_str(v, ECS_BLUE); const char *name = ecs_get_name(v->world, type); if (name) { ecs_strbuf_appendstr(v->buf, name); } else { char *path = ecs_get_path(v->world, type); ecs_strbuf_appendstr(v->buf, path); ecs_os_free(path); } flecs_expr_color_to_str(v, ECS_NORMAL); ecs_strbuf_appendlit(v->buf, "("); if (flecs_expr_node_to_str(v, node->expr)) { return -1; } ecs_strbuf_append(v->buf, ")"); return 0; } static int flecs_expr_node_to_str( ecs_expr_str_visitor_t *v, const ecs_expr_node_t *node) { ecs_assert(node != NULL, ECS_INVALID_PARAMETER, NULL); switch(node->kind) { case EcsExprValue: if (flecs_expr_value_to_str(v, (const ecs_expr_value_node_t*)node)) { goto error; } break; case EcsExprInterpolatedString: if (flecs_expr_interpolated_string_to_str(v, (const ecs_expr_interpolated_string_t*)node)) { goto error; } break; case EcsExprInitializer: case EcsExprEmptyInitializer: if (flecs_expr_initializer_to_str(v, (const ecs_expr_initializer_t*)node)) { goto error; } break; case EcsExprUnary: if (flecs_expr_unary_to_str(v, (const ecs_expr_unary_t*)node)) { goto error; } break; case EcsExprBinary: if (flecs_expr_binary_to_str(v, (const ecs_expr_binary_t*)node)) { goto error; } break; case EcsExprIdentifier: if (flecs_expr_identifier_to_str(v, (const ecs_expr_identifier_t*)node)) { goto error; } break; case EcsExprVariable: case EcsExprGlobalVariable: if (flecs_expr_variable_to_str(v, (const ecs_expr_variable_t*)node)) { goto error; } break; case EcsExprFunction: case EcsExprMethod: if (flecs_expr_function_to_str(v, (const ecs_expr_function_t*)node)) { goto error; } break; case EcsExprMember: if (flecs_expr_member_to_str(v, (const ecs_expr_member_t*)node)) { goto error; } break; case EcsExprElement: case EcsExprComponent: if (flecs_expr_element_to_str(v, (const ecs_expr_element_t*)node)) { goto error; } break; case EcsExprMatch: if (flecs_expr_match_to_str(v, (const ecs_expr_match_t*)node)) { goto error; } break; case EcsExprCast: case EcsExprCastNumber: if (flecs_expr_cast_to_str(v, (const ecs_expr_cast_t*)node)) { goto error; } break; default: ecs_abort(ECS_INTERNAL_ERROR, "invalid node kind"); } return 0; error: return -1; } void flecs_expr_to_str_buf( const ecs_world_t *world, const ecs_expr_node_t *expr, ecs_strbuf_t *buf, bool colors) { ecs_expr_str_visitor_t v = { .world = world, .buf = buf, .colors = colors }; if (flecs_expr_node_to_str(&v, expr)) { ecs_strbuf_reset(buf); } } #endif /** * @file addons/script/expr_ast.c * @brief Script expression AST implementation. */ #ifdef FLECS_SCRIPT static int flecs_expr_visit_type_priv( ecs_script_t *script, ecs_expr_node_t *node, ecs_meta_cursor_t *cur, const ecs_expr_eval_desc_t *desc); bool flecs_expr_is_type_integer( ecs_entity_t type) { if (type == ecs_id(ecs_u8_t)) return true; else if (type == ecs_id(ecs_u16_t)) return true; else if (type == ecs_id(ecs_u32_t)) return true; else if (type == ecs_id(ecs_u64_t)) return true; else if (type == ecs_id(ecs_uptr_t)) return true; else if (type == ecs_id(ecs_i8_t)) return true; else if (type == ecs_id(ecs_i16_t)) return true; else if (type == ecs_id(ecs_i32_t)) return true; else if (type == ecs_id(ecs_i64_t)) return true; else if (type == ecs_id(ecs_iptr_t)) return true; else return false; } bool flecs_expr_is_type_number( ecs_entity_t type) { if (flecs_expr_is_type_integer(type)) return true; else if (type == ecs_id(ecs_f32_t)) return true; else if (type == ecs_id(ecs_f64_t)) return true; else return false; } /* Returns how expressive a type is. This is used to determine whether an * implicit cast is allowed, where only casts from less to more expressive types * are valid. */ static int32_t flecs_expr_expressiveness_score( ecs_entity_t type) { if (type == ecs_id(ecs_bool_t)) return 1; else if (type == ecs_id(ecs_char_t)) return 2; else if (type == ecs_id(ecs_u8_t)) return 2; else if (type == ecs_id(ecs_u16_t)) return 3; else if (type == ecs_id(ecs_u32_t)) return 4; else if (type == ecs_id(ecs_uptr_t)) return 5; else if (type == ecs_id(ecs_u64_t)) return 6; else if (type == ecs_id(ecs_i8_t)) return 7; else if (type == ecs_id(ecs_i16_t)) return 8; else if (type == ecs_id(ecs_i32_t)) return 9; else if (type == ecs_id(ecs_iptr_t)) return 10; else if (type == ecs_id(ecs_i64_t)) return 11; else if (type == ecs_id(ecs_f32_t)) return 12; else if (type == ecs_id(ecs_f64_t)) return 13; else if (type == ecs_id(ecs_string_t)) return -1; else if (type == ecs_id(ecs_entity_t)) return -1; else return false; } /* Returns a score based on the storage size of a type. This is used in * combination with expressiveness to determine whether a type can be implicitly * casted. An implicit cast is only valid if the destination type is both more * expressive and has a larger storage size. */ static ecs_size_t flecs_expr_storage_score( ecs_entity_t type) { if (type == ecs_id(ecs_bool_t)) return 1; else if (type == ecs_id(ecs_char_t)) return 1; /* Unsigned integers have a larger storage size than signed integers, since * the unsigned range of a signed integer is smaller. */ else if (type == ecs_id(ecs_u8_t)) return 2; else if (type == ecs_id(ecs_u16_t)) return 3; else if (type == ecs_id(ecs_u32_t)) return 4; else if (type == ecs_id(ecs_uptr_t)) return 6; else if (type == ecs_id(ecs_u64_t)) return 7; else if (type == ecs_id(ecs_i8_t)) return 1; else if (type == ecs_id(ecs_i16_t)) return 2; else if (type == ecs_id(ecs_i32_t)) return 3; else if (type == ecs_id(ecs_iptr_t)) return 5; else if (type == ecs_id(ecs_i64_t)) return 6; /* Floating points have a smaller storage score, since the largest integer * that can be represented exactly is lower than the actual storage size. */ else if (type == ecs_id(ecs_f32_t)) return 3; else if (type == ecs_id(ecs_f64_t)) return 4; else if (type == ecs_id(ecs_string_t)) return -1; else if (type == ecs_id(ecs_entity_t)) return -1; else return false; } /* This function returns true if an type can be casted without changing the * precision of the value. It is used to determine a type for operands in a * binary expression in case they are of different types. */ static bool flecs_expr_implicit_cast_allowed( ecs_entity_t from, ecs_entity_t to) { int32_t from_e = flecs_expr_expressiveness_score(from); int32_t to_e = flecs_expr_expressiveness_score(to); if (from_e == -1 || to_e == -1) { return false; } if (to_e >= from_e) { return flecs_expr_storage_score(to) >= flecs_expr_storage_score(from); } return false; } static ecs_entity_t flecs_expr_cast_to_lvalue( ecs_entity_t lvalue, ecs_entity_t operand) { if (flecs_expr_implicit_cast_allowed(operand, lvalue)) { return lvalue; } return operand; } static ecs_entity_t flecs_expr_narrow_type( ecs_entity_t lvalue, ecs_expr_node_t *node) { ecs_entity_t type = node->type; if (node->kind != EcsExprValue) { return type; } if (!flecs_expr_is_type_number(type)) { return type; } void *ptr = ((ecs_expr_value_node_t*)node)->ptr; uint64_t uval; if (type == ecs_id(ecs_u8_t)) { uval = *(ecs_u8_t*)ptr; } else if (type == ecs_id(ecs_u16_t)) { uval = *(ecs_u16_t*)ptr; } else if (type == ecs_id(ecs_u32_t)) { uval = *(ecs_u32_t*)ptr; } else if (type == ecs_id(ecs_u64_t)) { uval = *(ecs_u32_t*)ptr; } else { int64_t ival; if (type == ecs_id(ecs_i8_t)) { ival = *(ecs_i8_t*)ptr; } else if (type == ecs_id(ecs_i16_t)) { ival = *(ecs_i16_t*)ptr; } else if (type == ecs_id(ecs_i32_t)) { ival = *(ecs_i32_t*)ptr; } else if (type == ecs_id(ecs_i64_t)) { ival = *(ecs_i64_t*)ptr; } else { /* If the lvalue type is a floating point type we can narrow the * literal to that since we'll lose double precision anyway. */ if (lvalue == ecs_id(ecs_f32_t)) { return ecs_id(ecs_f32_t); } return type; } if (ival <= INT8_MAX && ival >= INT8_MIN) { return ecs_id(ecs_i8_t); } else if (ival <= INT16_MAX && ival >= INT16_MIN) { return ecs_id(ecs_i16_t); } else if (ival <= INT32_MAX && ival >= INT32_MIN) { return ecs_id(ecs_i32_t); } else { return ecs_id(ecs_i64_t); } } if (uval <= UINT8_MAX) { return ecs_id(ecs_u8_t); } else if (uval <= UINT16_MAX) { return ecs_id(ecs_u16_t); } else if (uval <= UINT32_MAX) { return ecs_id(ecs_u32_t); } else { return ecs_id(ecs_u64_t); } } static bool flecs_expr_oper_valid_for_type( ecs_world_t *world, ecs_entity_t type, ecs_script_token_kind_t op) { switch(op) { case EcsTokAdd: case EcsTokSub: case EcsTokMul: case EcsTokDiv: case EcsTokMod: case EcsTokAddAssign: case EcsTokMulAssign: return flecs_expr_is_type_number(type); case EcsTokBitwiseAnd: case EcsTokBitwiseOr: if (ecs_get(world, type, EcsBitmask) != NULL) { return true; } /* fall through */ case EcsTokShiftLeft: case EcsTokShiftRight: return flecs_expr_is_type_integer(type); case EcsTokEq: case EcsTokNeq: case EcsTokAnd: case EcsTokOr: case EcsTokGt: case EcsTokGtEq: case EcsTokLt: case EcsTokLtEq: return flecs_expr_is_type_number(type) || (type == ecs_id(ecs_bool_t)) || (type == ecs_id(ecs_char_t)) || (type == ecs_id(ecs_entity_t)); case EcsTokUnknown: case EcsTokScopeOpen: case EcsTokScopeClose: case EcsTokParenOpen: case EcsTokParenClose: case EcsTokBracketOpen: case EcsTokBracketClose: case EcsTokMember: case EcsTokComma: case EcsTokSemiColon: case EcsTokColon: case EcsTokAssign: case EcsTokNot: case EcsTokOptional: case EcsTokAnnotation: case EcsTokNewline: case EcsTokMatch: case EcsTokRange: case EcsTokIdentifier: case EcsTokString: case EcsTokNumber: case EcsTokKeywordModule: case EcsTokKeywordUsing: case EcsTokKeywordWith: case EcsTokKeywordIf: case EcsTokKeywordElse: case EcsTokKeywordFor: case EcsTokKeywordIn: case EcsTokKeywordMatch: case EcsTokKeywordTemplate: case EcsTokKeywordProp: case EcsTokKeywordConst: case EcsTokEnd: default: ecs_abort(ECS_INTERNAL_ERROR, NULL); } } static int flecs_expr_type_for_operator( ecs_script_t *script, ecs_expr_node_t *node, /* Only used for error reporting */ ecs_entity_t node_type, ecs_expr_node_t *left, ecs_expr_node_t *right, ecs_script_token_kind_t operator, ecs_entity_t *operand_type, ecs_entity_t *result_type) { ecs_world_t *world = script->world; if (operator == EcsTokDiv || operator == EcsTokMod) { if (right->kind == EcsExprValue) { ecs_expr_value_node_t *val = (ecs_expr_value_node_t*)right; ecs_value_t v = { .type = val->node.type, .ptr = val->ptr }; if (flecs_value_is_0(&v)) { flecs_expr_visit_error(script, node, "invalid division by zero"); return -1; } } } switch(operator) { case EcsTokDiv: /* Result type of a division is always a float */ if (left->type != ecs_id(ecs_f32_t) && left->type != ecs_id(ecs_f64_t)){ *operand_type = ecs_id(ecs_f64_t); *result_type = ecs_id(ecs_f64_t); } else { *operand_type = left->type; *result_type = left->type; } return 0; case EcsTokMod: /* Mod only accepts integer operands, and results in an integer. We * could disallow doing mod on floating point types, but in practice * that would likely just result in code having to do a manual * conversion to an integer. */ *operand_type = ecs_id(ecs_i64_t); *result_type = ecs_id(ecs_i64_t); return 0; case EcsTokAnd: case EcsTokOr: /* Result type of a condition operator is always a bool */ *operand_type = ecs_id(ecs_bool_t); *result_type = ecs_id(ecs_bool_t); return 0; case EcsTokEq: case EcsTokNeq: case EcsTokGt: case EcsTokGtEq: case EcsTokLt: case EcsTokLtEq: /* Result type of equality operator is always bool, but operand types * should not be casted to bool */ *result_type = ecs_id(ecs_bool_t); break; case EcsTokShiftLeft: case EcsTokShiftRight: case EcsTokBitwiseAnd: case EcsTokBitwiseOr: case EcsTokAdd: case EcsTokSub: case EcsTokMul: break; case EcsTokAddAssign: case EcsTokMulAssign: case EcsTokUnknown: case EcsTokScopeOpen: case EcsTokScopeClose: case EcsTokParenOpen: case EcsTokParenClose: case EcsTokBracketOpen: case EcsTokBracketClose: case EcsTokMember: case EcsTokComma: case EcsTokSemiColon: case EcsTokColon: case EcsTokAssign: case EcsTokNot: case EcsTokOptional: case EcsTokAnnotation: case EcsTokNewline: case EcsTokMatch: case EcsTokRange: case EcsTokIdentifier: case EcsTokString: case EcsTokNumber: case EcsTokKeywordModule: case EcsTokKeywordUsing: case EcsTokKeywordWith: case EcsTokKeywordIf: case EcsTokKeywordElse: case EcsTokKeywordFor: case EcsTokKeywordIn: case EcsTokKeywordMatch: case EcsTokKeywordTemplate: case EcsTokKeywordProp: case EcsTokKeywordConst: case EcsTokEnd: default: ecs_throw(ECS_INTERNAL_ERROR, "invalid operator"); } /* If one of the types is an entity or id, the other one should be also */ if (left->type == ecs_id(ecs_entity_t) || right->type == ecs_id(ecs_entity_t)) { *operand_type = ecs_id(ecs_entity_t); goto done; } const EcsPrimitive *ltype_ptr = ecs_get(world, left->type, EcsPrimitive); const EcsPrimitive *rtype_ptr = ecs_get(world, right->type, EcsPrimitive); if (!ltype_ptr || !rtype_ptr) { /* Only primitives, bitmask constants and enums are allowed */ if (left->type == right->type) { if (ecs_get(world, left->type, EcsBitmask) != NULL) { *operand_type = left->type; goto done; } } { const EcsEnum *ptr = ecs_get(script->world, left->type, EcsEnum); if (ptr) { *operand_type = ptr->underlying_type; goto done; } } { const EcsEnum *ptr = ecs_get(script->world, right->type, EcsEnum); if (ptr) { *operand_type = ptr->underlying_type; goto done; } } char *lname = ecs_get_path(world, left->type); char *rname = ecs_get_path(world, right->type); flecs_expr_visit_error(script, node, "invalid non-primitive type in expression (%s, %s)", lname, rname); ecs_os_free(lname); ecs_os_free(rname); goto error; } /* If left and right type are the same, do nothing */ if (left->type == right->type) { *operand_type = left->type; goto done; } /* If types are not the same, find the smallest type for literals that can * represent the value without losing precision. */ ecs_entity_t ltype = flecs_expr_narrow_type(node_type, left); ecs_entity_t rtype = flecs_expr_narrow_type(node_type, right); /* If types are not the same, try to implicitly cast to expression type */ ltype = flecs_expr_cast_to_lvalue(node_type, ltype); rtype = flecs_expr_cast_to_lvalue(node_type, rtype); if (ltype == rtype) { *operand_type = ltype; goto done; } if (operator == EcsTokEq || operator == EcsTokNeq) { /* If this is an equality comparison and one of the operands is a bool, * cast the other operand to a bool as well. This ensures that * expressions such as true == 2 evaluate to true. */ if (ltype == ecs_id(ecs_bool_t) || rtype == ecs_id(ecs_bool_t)) { *operand_type = ecs_id(ecs_bool_t); goto done; } /* Equality comparisons between floating point types are invalid */ if (ltype == ecs_id(ecs_f32_t) || ltype == ecs_id(ecs_f64_t)) { flecs_expr_visit_error(script, node, "floating point value is invalid in equality comparison"); goto error; } if (rtype == ecs_id(ecs_f32_t) || rtype == ecs_id(ecs_f64_t)) { flecs_expr_visit_error(script, node, "floating point value is invalid in equality comparison"); goto error; } } /* If after the implicit cast types are not the same, try to implicitly cast * to the most expressive type. */ if (flecs_expr_expressiveness_score(ltype) >= flecs_expr_expressiveness_score(rtype)) { if (flecs_expr_implicit_cast_allowed(rtype, ltype)) { *operand_type = ltype; goto done; } } else { if (flecs_expr_implicit_cast_allowed(ltype, rtype)) { *operand_type = rtype; goto done; } } /* If we get here one or both operands cannot be coerced to the same type * while guaranteeing no loss of precision. Pick the type that's least * likely to cause trouble. */ if (flecs_expr_is_type_number(ltype) && flecs_expr_is_type_number(rtype)) { /* If one of the types is a floating point, use f64 */ if (ltype == ecs_id(ecs_f32_t) || ltype == ecs_id(ecs_f64_t) || rtype == ecs_id(ecs_f32_t) || rtype == ecs_id(ecs_f64_t)) { *operand_type = ecs_id(ecs_f64_t); goto done; } /* If one of the types is an integer, use i64 */ if (ltype == ecs_id(ecs_i8_t) || ltype == ecs_id(ecs_i16_t) || ltype == ecs_id(ecs_i32_t) || ltype == ecs_id(ecs_i64_t)) { *operand_type = ecs_id(ecs_i64_t); goto done; } if (rtype == ecs_id(ecs_i8_t) || rtype == ecs_id(ecs_i16_t) || rtype == ecs_id(ecs_i32_t) || rtype == ecs_id(ecs_i64_t)) { *operand_type = ecs_id(ecs_i64_t); goto done; } } /* If all of that didn't work, give up */ char *ltype_str = ecs_id_str(world, ltype); char *rtype_str = ecs_id_str(world, rtype); flecs_expr_visit_error(script, node, "incompatible types in expression (%s vs %s)", ltype_str, rtype_str); ecs_os_free(ltype_str); ecs_os_free(rtype_str); error: return -1; done: if (operator == EcsTokSub && *operand_type == ecs_id(ecs_u64_t)) { /* Result of subtracting two unsigned ints can be negative */ *operand_type = ecs_id(ecs_i64_t); } if (!*result_type) { *result_type = *operand_type; } if (ecs_get(script->world, *result_type, EcsBitmask) != NULL) { *operand_type = ecs_id(ecs_u64_t); } return 0; } static int flecs_expr_type_for_binary_expr( ecs_script_t *script, ecs_expr_binary_t *node, ecs_entity_t *operand_type, ecs_entity_t *result_type) { return flecs_expr_type_for_operator(script, (ecs_expr_node_t*)node, node->node.type, node->left, node->right, node->operator, operand_type, result_type); } static int flecs_expr_interpolated_string_visit_type( ecs_script_t *script, ecs_expr_interpolated_string_t *node, ecs_meta_cursor_t *cur, const ecs_expr_eval_desc_t *desc) { char *ptr, *frag = NULL; char ch; for (ptr = node->value; (ch = ptr[0]); ptr ++) { if (ch == '\\') { ptr ++; /* Next character is escaped, ignore */ continue; } if ((ch == '$') && (isspace(ptr[1]) || !ptr[1])) { /* $ by itself */ continue; } if (ch == '$' || ch == '{') { if (!frag) { frag = node->value; } char *frag_end = ptr; ecs_expr_node_t *result = NULL; if (ch == '$') { char *var_name = ++ ptr; ptr = ECS_CONST_CAST(char*, flecs_script_identifier( NULL, ptr, NULL)); if (!ptr) { goto error; } /* Fiddly, but reduces need for allocations */ ecs_size_t var_name_pos = flecs_ito(int32_t, var_name - node->value); var_name = &node->buffer[var_name_pos]; ecs_size_t var_name_end = flecs_ito(int32_t, ptr - node->value); node->buffer[var_name_end] = '\0'; ecs_expr_variable_t *var = flecs_expr_variable_from( script, (ecs_expr_node_t*)node, var_name); if (!var) { goto error; } result = (ecs_expr_node_t*)var; } else { ecs_script_impl_t *impl = flecs_script_impl(script); ecs_script_parser_t parser = { .script = impl, .scope = impl->root, .significant_newline = false, .token_cur = impl->token_remaining }; ptr = ECS_CONST_CAST(char*, flecs_script_parse_expr( &parser, ptr + 1, 0, &result)); if (!ptr) { goto error; } if (ptr[0] != '}') { flecs_expr_visit_error(script, node, "expected '}' at end of interpolated expression"); goto error; } ptr ++; } ecs_assert(result != NULL, ECS_INTERNAL_ERROR, NULL); ecs_expr_eval_desc_t priv_desc = *desc; priv_desc.type = ecs_id(ecs_string_t); /* String output */ if (flecs_expr_visit_type_priv(script, result, cur, &priv_desc)) { flecs_expr_visit_free(script, result); goto error; } if (result->type != ecs_id(ecs_string_t)) { result = (ecs_expr_node_t*)flecs_expr_cast(script, (ecs_expr_node_t*)result, ecs_id(ecs_string_t)); if (!result) { /* Cast failed */ goto error; } } ecs_vec_append_t(&((ecs_script_impl_t*)script)->allocator, &node->expressions, ecs_expr_node_t*)[0] = result; frag_end[0] = '\0'; if (frag != frag_end) { ecs_vec_append_t(&((ecs_script_impl_t*)script)->allocator, &node->fragments, char*)[0] = frag; } ecs_vec_append_t(&((ecs_script_impl_t*)script)->allocator, &node->fragments, char*)[0] = NULL; frag = ptr; /* Point to next fragment */ if (!ptr[0]) { break; /* We already parsed the end of the string */ } } } /* This would mean it's not an interpolated string, which means the parser * messed up when creating the node. */ ecs_assert(frag != NULL, ECS_INTERNAL_ERROR, NULL); /* Add remaining fragment */ if (frag != ptr) { ecs_vec_append_t(&((ecs_script_impl_t*)script)->allocator, &node->fragments, char*)[0] = frag; } return 0; error: return -1; } static int flecs_expr_initializer_collection_check( ecs_script_t *script, ecs_expr_initializer_t *node, ecs_meta_cursor_t *cur) { if (cur) { if (ecs_meta_is_collection(cur) != node->is_collection) { char *type_str = ecs_get_path(script->world, node->node.type); if (node->is_collection) { flecs_expr_visit_error(script, node, "invalid collection literal for non-collection type '%s'", type_str); } else { flecs_expr_visit_error(script, node, "invalid object literal for collection type '%s'", type_str); } ecs_os_free(type_str); goto error; } } ecs_entity_t type = node->node.type; if (type) { const EcsOpaque *op = ecs_get(script->world, type, EcsOpaque); if (op) { type = op->as_type; } const EcsType *ptr = ecs_get(script->world, type, EcsType); if (ptr) { ecs_type_kind_t kind = ptr->kind; if (node->is_collection) { /* Only do this check if no cursor is provided. Cursors also * handle inline arrays. */ if (!cur) { if (kind != EcsArrayType && kind != EcsVectorType) { char *type_str = ecs_get_path( script->world, node->node.type); flecs_expr_visit_error(script, node, "invalid collection literal for type '%s'", type_str); ecs_os_free(type_str); goto error; } } } else { if (kind != EcsStructType) { char *type_str = ecs_get_path( script->world, node->node.type); flecs_expr_visit_error(script, node, "invalid object literal for type '%s'", type_str); ecs_os_free(type_str); goto error; } } } } return 0; error: return -1; } static int flecs_expr_empty_initializer_visit_type( ecs_script_t *script, ecs_expr_initializer_t *node, ecs_meta_cursor_t *cur, const ecs_expr_eval_desc_t *desc) { (void)desc; node->node.type = ecs_meta_get_type(cur); if (!node->node.type) { flecs_expr_visit_error(script, node, "unknown type for initializer"); goto error; } if (ecs_meta_push(cur)) { goto error; } if (flecs_expr_initializer_collection_check(script, node, cur)) { goto error; } if (ecs_meta_pop(cur)) { goto error; } return 0; error: return -1; } static int flecs_expr_initializer_visit_type( ecs_script_t *script, ecs_expr_initializer_t *node, ecs_meta_cursor_t *cur, const ecs_expr_eval_desc_t *desc) { if (!cur || !cur->valid) { flecs_expr_visit_error(script, node, "missing type for initializer"); goto error; } ecs_entity_t type = ecs_meta_get_type(cur); ecs_assert(type != 0, ECS_INTERNAL_ERROR, NULL); /* Opaque types do not have deterministic offsets */ bool is_opaque = ecs_get(script->world, type, EcsOpaque) != NULL; node->is_dynamic = is_opaque; if (ecs_meta_push(cur)) { goto error; } if (flecs_expr_initializer_collection_check(script, node, cur)) { goto error; } ecs_expr_initializer_element_t *elems = ecs_vec_first(&node->elements); int32_t i, count = ecs_vec_count(&node->elements); for (i = 0; i < count; i ++) { if (i) { if (ecs_meta_next(cur)) { /* , */ goto error; } } ecs_expr_initializer_element_t *elem = &elems[i]; if (elem->member) { if (ecs_meta_dotmember(cur, elem->member)) { /* x: */ flecs_expr_visit_error(script, node, "cannot resolve member"); goto error; } } /* Check for "member: $" syntax */ if (elem->value->kind == EcsExprVariable) { ecs_expr_variable_t *var = (ecs_expr_variable_t*)elem->value; if (var->name && !var->name[0]) { var->name = ecs_meta_get_member(cur); if (!var->name) { flecs_expr_visit_error(script, node, "cannot deduce variable name: not a member"); goto error; } } } ecs_entity_t elem_type = ecs_meta_get_type(cur); ecs_meta_cursor_t elem_cur = *cur; if (flecs_expr_visit_type_priv( script, elem->value, &elem_cur, desc)) { goto error; } if (elem->value->type != elem_type) { ecs_expr_node_t *cast = (ecs_expr_node_t*)flecs_expr_cast( script, elem->value, elem_type); if (!cast) { goto error; } elem->value = cast; } if (elem->operator) { if (!flecs_expr_oper_valid_for_type( script->world, elem_type, elem->operator)) { char *type_str = ecs_get_path(script->world, elem_type); flecs_expr_visit_error(script, node, "invalid operator for type '%s'", type_str); ecs_os_free(type_str); goto error; } } if (!is_opaque) { elem->offset = (uintptr_t)ecs_meta_get_ptr(cur); } } node->node.type = type; if (ecs_meta_pop(cur)) { goto error; } return 0; error: return -1; } static int flecs_expr_unary_visit_type( ecs_script_t *script, ecs_expr_unary_t *node, ecs_meta_cursor_t *cur, const ecs_expr_eval_desc_t *desc) { if (flecs_expr_visit_type_priv(script, node->expr, cur, desc)) { goto error; } /* The only supported unary expression is not (!) which returns a bool */ node->node.type = ecs_id(ecs_bool_t); if (node->expr->type != ecs_id(ecs_bool_t)) { node->expr = (ecs_expr_node_t*)flecs_expr_cast( script, node->expr, ecs_id(ecs_bool_t)); if (!node->expr) { goto error; } } return 0; error: return -1; } static int flecs_expr_binary_visit_type( ecs_script_t *script, ecs_expr_binary_t *node, ecs_meta_cursor_t *cur, const ecs_expr_eval_desc_t *desc) { /* Operands must be of this type or casted to it */ ecs_entity_t operand_type = 0; /* Resulting type of binary expression */ ecs_entity_t result_type = 0; if (cur->valid) { /* Provides a hint to the type visitor. The lvalue type will be used to * reduce the number of casts where possible. */ node->node.type = ecs_meta_get_type(cur); /* If the result of the binary expression is a boolean it's likely a * conditional expression. We don't want to hint that the operands * of conditional expressions should be casted to booleans. */ if (node->node.type == ecs_id(ecs_bool_t)) { ecs_os_zeromem(cur); } } if (flecs_expr_visit_type_priv(script, node->left, cur, desc)) { goto error; } if (flecs_expr_visit_type_priv(script, node->right, cur, desc)) { goto error; } if (flecs_expr_type_for_binary_expr( script, node, &operand_type, &result_type)) { goto error; } if (!flecs_expr_oper_valid_for_type( script->world, result_type, node->operator)) { char *type_str = ecs_get_path(script->world, result_type); flecs_expr_visit_error(script, node, "invalid operator %s for type '%s'", flecs_script_token_str(node->operator), type_str); ecs_os_free(type_str); goto error; } if (operand_type != node->left->type) { node->left = (ecs_expr_node_t*)flecs_expr_cast( script, node->left, operand_type); if (!node->left) { goto error; } } if (operand_type != node->right->type) { node->right = (ecs_expr_node_t*)flecs_expr_cast( script, node->right, operand_type); if (!node->right) { goto error; } } node->node.type = result_type; return 0; error: return -1; } static int flecs_expr_constant_identifier_visit_type( ecs_script_t *script, ecs_expr_identifier_t *node) { ecs_expr_value_node_t *result = flecs_expr_value_from( script, (ecs_expr_node_t*)node, node->node.type); ecs_meta_cursor_t expr_cur = ecs_meta_cursor( script->world, node->node.type, &result->storage.u64); if (ecs_meta_set_string(&expr_cur, node->value)) { flecs_expr_visit_free(script, (ecs_expr_node_t*)result); goto error; } result->ptr = &result->storage.u64; node->expr = (ecs_expr_node_t*)result; return 0; error: return -1; } static int flecs_expr_identifier_visit_type( ecs_script_t *script, ecs_expr_identifier_t *node, ecs_meta_cursor_t *cur, const ecs_expr_eval_desc_t *desc) { (void)desc; ecs_entity_t type = node->node.type; if (cur->valid) { type = ecs_meta_get_type(cur); } const EcsType *type_ptr = NULL; if (type) { type_ptr = ecs_get(script->world, type, EcsType); ecs_assert(type_ptr != NULL, ECS_INTERNAL_ERROR, NULL); } if (type_ptr && (type_ptr->kind == EcsEnumType || type_ptr->kind == EcsBitmaskType)) { /* If the requested type is an enum or bitmask, use cursor to resolve * identifier to correct type constant. This lets us type 'Red' in places * where we expect a value of type Color, instead of Color.Red. */ node->node.type = type; if (flecs_expr_constant_identifier_visit_type(script, node)) { goto error; } return 0; } else { /* If not, try to resolve the identifier as entity */ ecs_entity_t e = desc->lookup_action( script->world, node->value, desc->lookup_ctx); if (e) { const EcsScriptConstVar *global = ecs_get( script->world, e, EcsScriptConstVar); if (!global) { if (!type) { type = ecs_id(ecs_entity_t); } ecs_expr_value_node_t *result = flecs_expr_value_from( script, (ecs_expr_node_t*)node, type); result->storage.entity = e; result->ptr = &result->storage.entity; node->expr = (ecs_expr_node_t*)result; node->node.type = type; } else { ecs_expr_variable_t *var_node = flecs_expr_variable_from( script, (ecs_expr_node_t*)node, node->value); node->expr = (ecs_expr_node_t*)var_node; node->node.type = global->value.type; ecs_meta_cursor_t tmp_cur; ecs_os_zeromem(&tmp_cur); if (flecs_expr_visit_type_priv( script, (ecs_expr_node_t*)var_node, &tmp_cur, desc)) { goto error; } } return 0; } /* If identifier could not be resolved as entity, try as variable */ int32_t var_sp = -1; ecs_script_var_t *var = flecs_script_find_var( desc->vars, node->value, &var_sp); if (var) { ecs_expr_variable_t *var_node = flecs_expr_variable_from( script, (ecs_expr_node_t*)node, node->value); node->expr = (ecs_expr_node_t*)var_node; node->node.type = var->value.type; ecs_meta_cursor_t tmp_cur; ecs_os_zeromem(&tmp_cur); if (flecs_expr_visit_type_priv( script, (ecs_expr_node_t*)var_node, &tmp_cur, desc)) { goto error; } return 0; } /* If unresolved identifiers aren't allowed here, throw error */ if (!desc->allow_unresolved_identifiers) { flecs_expr_visit_error(script, node, "unresolved identifier '%s'", node->value); goto error; } /* Identifier will be resolved at eval time, default to entity */ node->node.type = ecs_id(ecs_entity_t); } return 0; error: return -1; } static int flecs_expr_global_variable_resolve( ecs_script_t *script, ecs_expr_variable_t *node, const ecs_expr_eval_desc_t *desc) { ecs_world_t *world = script->world; ecs_entity_t global = desc->lookup_action( world, node->name, desc->lookup_ctx); if (!global) { flecs_expr_visit_error(script, node, "unresolved variable '%s'", node->name); goto error; } const EcsScriptConstVar *v = ecs_get(world, global, EcsScriptConstVar); if (!v) { char *str = ecs_get_path(world, global); flecs_expr_visit_error(script, node, "entity '%s' is not a variable", node->name); ecs_os_free(str); goto error; } node->node.kind = EcsExprGlobalVariable; node->node.type = v->value.type; node->global_value = v->value; return 0; error: return -1; } static int flecs_expr_variable_visit_type( ecs_script_t *script, ecs_expr_variable_t *node, ecs_meta_cursor_t *cur, const ecs_expr_eval_desc_t *desc) { ecs_script_var_t *var = flecs_script_find_var( desc->vars, node->name, &node->sp); if (var) { node->node.type = var->value.type; if (!node->node.type) { flecs_expr_visit_error(script, node, "variable '%s' is not initialized", node->name); goto error; } } else { if (flecs_expr_global_variable_resolve(script, node, desc)) { goto error; } } *cur = ecs_meta_cursor(script->world, node->node.type, NULL); return 0; error: return -1; } static int flecs_expr_global_variable_visit_type( ecs_script_t *script, ecs_expr_variable_t *node, ecs_meta_cursor_t *cur, const ecs_expr_eval_desc_t *desc) { (void)cur; if (flecs_expr_global_variable_resolve(script, node, desc)) { goto error; } return 0; error: return -1; } static int flecs_expr_arguments_visit_type( ecs_script_t *script, ecs_expr_initializer_t *node, const ecs_expr_eval_desc_t *desc, const ecs_vec_t *param_vec) { ecs_expr_initializer_element_t *elems = ecs_vec_first(&node->elements); int32_t i, count = ecs_vec_count(&node->elements); if (count != ecs_vec_count(param_vec)) { flecs_expr_visit_error(script, node, "expected %d arguments, got %d", ecs_vec_count(param_vec), count); goto error; } ecs_script_parameter_t *params = ecs_vec_first(param_vec); for (i = 0; i < count; i ++) { ecs_expr_initializer_element_t *elem = &elems[i]; ecs_meta_cursor_t cur = ecs_meta_cursor( script->world, params[i].type, NULL); if (flecs_expr_visit_type_priv(script, elem->value, &cur, desc)){ goto error; } if (elem->value->type != params[i].type) { elem->value = (ecs_expr_node_t*)flecs_expr_cast( script, elem->value, params[i].type); if (!elem->value) { goto error; } } } return 0; error: return -1; } static int flecs_expr_function_visit_type( ecs_script_t *script, ecs_expr_function_t *node, ecs_meta_cursor_t *cur, const ecs_expr_eval_desc_t *desc) { bool is_method = false; char *last_elem = NULL; const char *func_identifier = NULL; if (node->left->kind == EcsExprIdentifier) { /* If identifier contains '.' separator(s), this is a method call, * otherwise it's a regular function. */ ecs_expr_identifier_t *ident = (ecs_expr_identifier_t*)node->left; func_identifier = ident->value; last_elem = strrchr(func_identifier, '.'); if (last_elem && last_elem != ident->value && last_elem[-1] != '\\') { node->function_name = last_elem + 1; last_elem[0] = '\0'; is_method = true; } else { node->function_name = ident->value; } } else if (node->left->kind == EcsExprMember) { /* This is a method. Just like identifiers, method strings can contain * separators. Split off last separator to get the method. */ ecs_expr_member_t *member = (ecs_expr_member_t*)node->left; last_elem = strrchr(member->member_name, '.'); if (!last_elem) { node->left = member->left; node->function_name = member->member_name; member->left = NULL; /* Prevent cleanup */ flecs_expr_visit_free(script, (ecs_expr_node_t*)member); } else { node->function_name = last_elem + 1; last_elem[0] = '\0'; } is_method = true; } /* Left of function expression should not inherit lvalue type, since the * function return type is what's going to be assigned. */ ecs_os_zeromem(cur); if (flecs_expr_visit_type_priv(script, node->left, cur, desc)) { goto error; } ecs_world_t *world = script->world; const ecs_vec_t *params = NULL; /* If this is a method, lookup function entity in scope of type */ if (is_method) { ecs_entity_t func = ecs_lookup_from( world, node->left->type, node->function_name); if (!func) { /* If identifier could be a function (not a method) try that */ if (func_identifier) { is_method = false; last_elem[0] = '.'; node->function_name = func_identifier; goto try_function; } char *type_str = ecs_get_path(world, node->left->type); flecs_expr_visit_error(script, node, "unresolved method identifier '%s' for type '%s'", node->function_name, type_str); ecs_os_free(type_str); goto error; } const EcsScriptMethod *func_data = ecs_get( world, func, EcsScriptMethod); if (!func_data) { char *path = ecs_get_path(world, func); flecs_expr_visit_error(script, node, "entity '%s' is not a valid method", path); ecs_os_free(path); goto error; } node->node.kind = EcsExprMethod; node->node.type = func_data->return_type; node->calldata.function = func; node->calldata.callback = func_data->callback; node->calldata.ctx = func_data->ctx; params = &func_data->params; } try_function: if (!is_method) { ecs_entity_t func = desc->lookup_action( world, node->function_name, desc->lookup_ctx); if (!func) { flecs_expr_visit_error(script, node, "unresolved function identifier '%s'", node->function_name); goto error; } const EcsScriptFunction *func_data = ecs_get( world, func, EcsScriptFunction); if (!func_data) { char *path = ecs_get_path(world, func); flecs_expr_visit_error(script, node, "entity '%s' is not a valid method", path); ecs_os_free(path); goto error; } node->node.type = func_data->return_type; node->calldata.function = func; node->calldata.callback = func_data->callback; node->calldata.ctx = func_data->ctx; params = &func_data->params; } if (flecs_expr_arguments_visit_type(script, node->args, desc, params)) { goto error; } return 0; error: return -1; } static int flecs_expr_member_visit_type( ecs_script_t *script, ecs_expr_member_t *node, ecs_meta_cursor_t *cur, const ecs_expr_eval_desc_t *desc) { if (flecs_expr_visit_type_priv(script, node->left, cur, desc)) { goto error; } ecs_world_t *world = script->world; ecs_entity_t left_type = node->left->type; const EcsType *type = ecs_get(world, left_type, EcsType); if (!type) { char *type_str = ecs_get_path(world, left_type); flecs_expr_visit_error(script, node, "cannot resolve member on value of type '%s' " "(missing reflection data)", type_str); ecs_os_free(type_str); goto error; } if (type->kind != EcsStructType) { char *type_str = ecs_get_path(world, left_type); flecs_expr_visit_error(script, node, "cannot resolve member on non-struct type '%s'", type_str); ecs_os_free(type_str); goto error; } if (ecs_meta_push(cur)) { goto error; } int prev_log = ecs_log_set_level(-4); if (ecs_meta_dotmember(cur, node->member_name)) { ecs_log_set_level(prev_log); char *type_str = ecs_get_path(world, left_type); flecs_expr_visit_error(script, node, "unresolved member '%s' for type '%s'", node->member_name, type_str); ecs_os_free(type_str); goto error; } ecs_log_set_level(prev_log); node->node.type = ecs_meta_get_type(cur); #ifdef FLECS_DEBUG const EcsMember *m = ecs_get(world, ecs_meta_get_member_id(cur), EcsMember); ecs_assert(m != NULL, ECS_INTERNAL_ERROR, NULL); #endif node->offset = (uintptr_t)ecs_meta_get_ptr(cur); return 0; error: return -1; } static int flecs_expr_element_visit_type( ecs_script_t *script, ecs_expr_element_t *node, ecs_meta_cursor_t *cur, const ecs_expr_eval_desc_t *desc) { if (flecs_expr_visit_type_priv(script, node->left, cur, desc)) { goto error; } ecs_meta_cursor_t index_cur = {0}; if (flecs_expr_visit_type_priv( script, node->index, &index_cur, desc)) { goto error; } ecs_world_t *world = script->world; ecs_entity_t left_type = node->left->type; const EcsType *type = ecs_get(world, left_type, EcsType); if (!type) { char *type_str = ecs_get_path(world, left_type); flecs_expr_visit_error(script, node, "cannot use [] on value of type '%s' (missing reflection data)", type_str); ecs_os_free(type_str); goto error; } bool is_entity_type = false; if (type->kind == EcsPrimitiveType) { const EcsPrimitive *ptype = ecs_get(world, left_type, EcsPrimitive); if (ptype->kind == EcsEntity) { is_entity_type = true; } } if (is_entity_type) { if (node->index->kind == EcsExprIdentifier) { ecs_expr_identifier_t *ident = (ecs_expr_identifier_t*)node->index; node->node.type = desc->lookup_action( script->world, ident->value, desc->lookup_ctx); if (!node->node.type) { flecs_expr_visit_error(script, node, "unresolved component identifier '%s'", ident->value); goto error; } node->node.kind = EcsExprComponent; *cur = ecs_meta_cursor(script->world, node->node.type, NULL); } else { flecs_expr_visit_error(script, node, "invalid component expression"); goto error; } } else { if (ecs_meta_push(cur)) { goto not_a_collection; } if (!ecs_meta_is_collection(cur)) { goto not_a_collection; } node->node.type = ecs_meta_get_type(cur); const ecs_type_info_t *elem_ti = ecs_get_type_info( script->world, node->node.type); node->elem_size = elem_ti->size; } return 0; not_a_collection: { char *type_str = ecs_get_path(script->world, node->left->type); flecs_expr_visit_error(script, node, "invalid usage of [] on non collection/entity type '%s'", type_str); ecs_os_free(type_str); } error: return -1; } static bool flecs_expr_identifier_is_any( ecs_expr_node_t *node) { if (node->kind == EcsExprIdentifier) { ecs_expr_identifier_t *id = (ecs_expr_identifier_t*)node; if (id->value && !ecs_os_strcmp(id->value, "_")) { return true; } } return false; } static int flecs_expr_match_visit_type( ecs_script_t *script, ecs_expr_match_t *node, ecs_meta_cursor_t *cur, const ecs_expr_eval_desc_t *desc) { ecs_assert(node != NULL, ECS_INVALID_PARAMETER, NULL); ecs_meta_cursor_t expr_cur; ecs_os_zeromem(&expr_cur); if (flecs_expr_visit_type_priv(script, node->expr, &expr_cur, desc)) { goto error; } int32_t i, count = ecs_vec_count(&node->elements); ecs_expr_match_element_t *elems = ecs_vec_first(&node->elements); if (!count) { flecs_expr_visit_error(script, node, "match statement must have at least one case"); goto error; } /* Determine most expressive type of all elements */ node->node.type = ecs_meta_get_type(cur); for (i = 0; i < count; i ++) { ecs_expr_match_element_t *elem = &elems[i]; if (node->node.type) { expr_cur = ecs_meta_cursor(script->world, node->node.type, NULL); } else { ecs_os_zeromem(&expr_cur); } if (flecs_expr_visit_type_priv(script, elem->expr, &expr_cur, desc)) { goto error; } if (!node->node.type) { node->node.type = elem->expr->type; continue; } if (flecs_expr_is_type_number(node->node.type)) { ecs_entity_t result_type = 0, operand_type = 0; if (flecs_expr_type_for_operator(script, (ecs_expr_node_t*)node, 0, (ecs_expr_node_t*)node, elem->expr, EcsTokAdd, /* Use operator that doesn't change types */ &operand_type, &result_type)) { goto error; } /* "Accumulate" most expressive type in result node */ node->node.type = result_type; } else { /* If type is not a number it must match exactly */ if (elem->expr->type != node->node.type) { char *got = ecs_get_path(script->world, elem->expr->type); char *expect = ecs_get_path(script->world, node->node.type); flecs_expr_visit_error(script, node, "invalid type for case %d in match (got %s, expected %s)", i + 1, got, expect); ecs_os_free(got); ecs_os_free(expect); goto error; } } } /* Loop over elements again, cast values to result type */ for (i = 0; i < count; i ++) { ecs_expr_match_element_t *elem = &elems[i]; if (elem->expr->type != node->node.type) { elem->expr = (ecs_expr_node_t*) flecs_expr_cast(script, elem->expr, node->node.type); if (!elem->expr) { goto error; } } } /* If this is an enum type, cast to the underlying type. This is necessary * because the compare operation executed by the match evaluation code isn't * implemented for enums. */ ecs_entity_t expr_type = node->expr->type; const EcsEnum *ptr = ecs_get(script->world, expr_type, EcsEnum); if (ptr) { node->expr = (ecs_expr_node_t*) flecs_expr_cast(script, node->expr, ptr->underlying_type); } /* Make sure that case values match the input type */ for (i = 0; i < count; i ++) { ecs_expr_match_element_t *elem = &elems[i]; if (flecs_expr_identifier_is_any(elem->compare)) { if (i != count - 1) { flecs_expr_visit_error(script, node, "any (_) must be the last case in match"); goto error; } node->any.compare = elem->compare; node->any.expr = elem->expr; ecs_vec_remove_last(&node->elements); } else { expr_cur = ecs_meta_cursor(script->world, expr_type, NULL); if (flecs_expr_visit_type_priv( script, elem->compare, &expr_cur, desc)) { goto error; } ecs_expr_node_t *compare = elem->compare; if (compare->type != node->expr->type) { elem->compare = (ecs_expr_node_t*) flecs_expr_cast(script, compare, node->expr->type); if (!elem->compare) { goto error; } } } } return 0; error: return -1; } static int flecs_expr_visit_type_priv( ecs_script_t *script, ecs_expr_node_t *node, ecs_meta_cursor_t *cur, const ecs_expr_eval_desc_t *desc) { ecs_assert(node != NULL, ECS_INVALID_PARAMETER, NULL); switch(node->kind) { case EcsExprValue: break; case EcsExprInterpolatedString: if (flecs_expr_interpolated_string_visit_type( script, (ecs_expr_interpolated_string_t*)node, cur, desc)) { goto error; } break; case EcsExprEmptyInitializer: if (flecs_expr_empty_initializer_visit_type( script, (ecs_expr_initializer_t*)node, cur, desc)) { goto error; } break; case EcsExprInitializer: if (flecs_expr_initializer_visit_type( script, (ecs_expr_initializer_t*)node, cur, desc)) { goto error; } break; case EcsExprUnary: if (flecs_expr_unary_visit_type( script, (ecs_expr_unary_t*)node, cur, desc)) { goto error; } break; case EcsExprBinary: if (flecs_expr_binary_visit_type( script, (ecs_expr_binary_t*)node, cur, desc)) { goto error; } break; case EcsExprIdentifier: if (flecs_expr_identifier_visit_type( script, (ecs_expr_identifier_t*)node, cur, desc)) { goto error; } break; case EcsExprVariable: if (flecs_expr_variable_visit_type( script, (ecs_expr_variable_t*)node, cur, desc)) { goto error; } break; case EcsExprGlobalVariable: if (flecs_expr_global_variable_visit_type( script, (ecs_expr_variable_t*)node, cur, desc)) { goto error; } break; case EcsExprFunction: if (flecs_expr_function_visit_type( script, (ecs_expr_function_t*)node, cur, desc)) { goto error; } break; case EcsExprMember: if (flecs_expr_member_visit_type( script, (ecs_expr_member_t*)node, cur, desc)) { goto error; } break; case EcsExprElement: if (flecs_expr_element_visit_type( script, (ecs_expr_element_t*)node, cur, desc)) { goto error; } break; case EcsExprMatch: if (flecs_expr_match_visit_type( script, (ecs_expr_match_t*)node, cur, desc)) { goto error; } break; case EcsExprCast: case EcsExprCastNumber: break; case EcsExprMethod: case EcsExprComponent: /* Expressions are derived by type visitor */ ecs_abort(ECS_INTERNAL_ERROR, NULL); } ecs_assert(node->type != 0, ECS_INTERNAL_ERROR, NULL); node->type_info = ecs_get_type_info(script->world, node->type); ecs_assert(node->type_info != NULL, ECS_INTERNAL_ERROR, NULL); return 0; error: return -1; } int flecs_expr_visit_type( ecs_script_t *script, ecs_expr_node_t *node, const ecs_expr_eval_desc_t *desc) { if (node->kind == EcsExprEmptyInitializer) { node->type = desc->type; if (node->type) { if (flecs_expr_initializer_collection_check( script, (ecs_expr_initializer_t*)node, NULL)) { return -1; } node->type_info = ecs_get_type_info(script->world, node->type); return 0; } } if (desc->type) { ecs_meta_cursor_t cur = ecs_meta_cursor( script->world, desc->type, NULL); return flecs_expr_visit_type_priv(script, node, &cur, desc); } else { ecs_meta_cursor_t cur; ecs_os_zeromem(&cur); return flecs_expr_visit_type_priv(script, node, &cur, desc); } } #endif