/** * @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); /* Make sure table records are in correct table cache list */ bool flecs_table_records_update_empty( ecs_table_t *table); /* Move a row from one table to another */ void flecs_table_move( ecs_world_t *world, ecs_entity_t dst_entity, ecs_entity_t src_entity, ecs_table_t *new_table, int32_t new_index, ecs_table_t *old_table, int32_t old_index, bool construct); /* Grow table with specified number of records. Populate table with entities, * starting from specified entity id. */ int32_t flecs_table_appendn( ecs_world_t *world, ecs_table_t *table, 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_list_t empty_tables; } ecs_table_cache_t; /* World level allocators are for operations that are not multithreaded */ typedef struct ecs_world_allocators_t { ecs_map_params_t ptr; ecs_map_params_t query_table_list; ecs_block_allocator_t query_table; ecs_block_allocator_t query_table_match; ecs_block_allocator_t graph_edge_lo; ecs_block_allocator_t graph_edge; ecs_block_allocator_t id_record; ecs_block_allocator_t id_record_chunk; ecs_block_allocator_t table_diff; ecs_block_allocator_t sparse_chunk; ecs_block_allocator_t hashmap; /* Temporary vectors used for creating table diff id sequences */ ecs_table_diff_builder_t diff_builder; } ecs_world_allocators_t; /* Stage level allocators are for operations that can be multithreaded */ typedef struct ecs_stage_allocators_t { ecs_stack_t iter_stack; ecs_stack_t deser_stack; ecs_block_allocator_t cmd_entry_chunk; ecs_block_allocator_t query_impl; ecs_block_allocator_t query_cache; } ecs_stage_allocators_t; /** Types for deferred operations */ typedef enum ecs_cmd_kind_t { EcsCmdClone, EcsCmdBulkNew, EcsCmdAdd, EcsCmdRemove, EcsCmdSet, EcsCmdEmplace, EcsCmdEnsure, EcsCmdModified, EcsCmdModifiedNoHook, EcsCmdAddModified, EcsCmdPath, EcsCmdDelete, EcsCmdClear, EcsCmdOnDeleteAction, EcsCmdEnable, EcsCmdDisable, EcsCmdEvent, EcsCmdSkip } ecs_cmd_kind_t; /* Entity specific metadata for command in queue */ typedef struct ecs_cmd_entry_t { int32_t first; int32_t last; /* If -1, a delete command was inserted */ } ecs_cmd_entry_t; typedef struct ecs_cmd_1_t { void *value; /* Component value (used by set / ensure) */ ecs_size_t size; /* Size of value */ bool clone_value; /* Clone entity with value (used for clone) */ } ecs_cmd_1_t; typedef struct ecs_cmd_n_t { ecs_entity_t *entities; int32_t count; } ecs_cmd_n_t; typedef struct ecs_cmd_t { ecs_cmd_kind_t kind; /* Command kind */ int32_t next_for_entity; /* Next operation for entity */ ecs_id_t id; /* (Component) id */ ecs_id_record_t *idr; /* Id record (only for set/mut/emplace) */ ecs_cmd_entry_t *entry; ecs_entity_t entity; /* Entity id */ union { ecs_cmd_1_t _1; /* Data for single entity operation */ ecs_cmd_n_t _n; /* Data for multi entity operation */ } is; ecs_entity_t system; /* System that enqueued the command */ } ecs_cmd_t; /* Data structures that store the command queue */ typedef struct ecs_commands_t { ecs_vec_t queue; ecs_stack_t stack; /* Temp memory used by deferred commands */ ecs_sparse_t entries; /* - command batching */ } ecs_commands_t; /** Callback used to capture commands of a frame */ typedef void (*ecs_on_commands_action_t)( const ecs_stage_t *stage, const ecs_vec_t *commands, void *ctx); /** A stage is a context that allows for safely using the API from multiple * threads. Stage pointers can be passed to the world argument of API * operations, which causes the operation to be ran on the stage instead of the * world. The features provided by a stage are: * * - A command queue for deferred ECS operations and events * - Thread specific allocators * - Thread specific world state (like current scope, with, current system) * - Thread specific buffers for preventing allocations */ struct ecs_stage_t { ecs_header_t hdr; /* Unique id that identifies the stage */ int32_t id; /* Zero if not deferred, positive if deferred, negative if suspended */ int32_t defer; /* Command queue stack, for nested execution */ ecs_commands_t *cmd; ecs_commands_t cmd_stack[ECS_MAX_DEFER_STACK]; int32_t cmd_sp; /* Thread context */ ecs_world_t *thread_ctx; /* Points to stage when a thread stage */ ecs_world_t *world; /* Reference to world */ ecs_os_thread_t thread; /* Thread handle (0 if no threading is used) */ /* One-shot actions to be executed after the merge */ ecs_vec_t post_frame_actions; /* Namespacing */ ecs_entity_t scope; /* Entity of current scope */ ecs_entity_t with; /* Id to add by default to new entities */ ecs_entity_t base; /* Currently instantiated top-level base */ const ecs_entity_t *lookup_path; /* Search path used by lookup operations */ /* Running system */ ecs_entity_t system; /* Thread specific allocators */ ecs_stage_allocators_t allocators; ecs_allocator_t allocator; /* Caches for query creation */ ecs_vec_t variables; ecs_vec_t operations; /* Temporary token storage for DSL parser. This allows for parsing and * interpreting a term without having to do allocations. */ char parser_tokens[1024]; char *parser_token; /* Pointer to next token */ }; /* Component monitor */ typedef struct ecs_monitor_t { ecs_vec_t queries; /* vector */ bool is_dirty; /* Should queries be rematched? */ } ecs_monitor_t; /* Component monitors */ typedef struct ecs_monitor_set_t { ecs_map_t monitors; /* map */ bool is_dirty; /* Should monitors be evaluated? */ } ecs_monitor_set_t; /* Data stored for id marked for deletion */ typedef struct ecs_marked_id_t { ecs_id_record_t *idr; ecs_id_t id; ecs_entity_t action; /* Set explicitly for delete_with, remove_all */ bool delete_id; } ecs_marked_id_t; typedef struct ecs_store_t { /* Entity lookup */ ecs_entity_index_t entity_index; /* Tables */ ecs_sparse_t tables; /* sparse */ /* Table lookup by hash */ ecs_hashmap_t table_map; /* hashmap */ /* Root table */ ecs_table_t root; /* 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; /* -- Pending table event buffers -- */ ecs_sparse_t *pending_buffer; /* sparse */ ecs_sparse_t *pending_tables; /* sparse */ /* Used to track when cache needs to be updated */ ecs_monitor_set_t monitors; /* map */ /* -- Systems -- */ ecs_entity_t pipeline; /* Current pipeline */ /* -- Identifiers -- */ ecs_hashmap_t aliases; ecs_hashmap_t symbols; /* -- Staging -- */ ecs_stage_t **stages; /* Stages */ int32_t stage_count; /* Number of stages */ /* Internal callback for command inspection. Only one callback can be set at * a time. After assignment the action will become active at the start of * the next frame, set by ecs_frame_begin, and will be reset by * ecs_frame_end. */ ecs_on_commands_action_t on_commands; ecs_on_commands_action_t on_commands_active; void *on_commands_ctx; void *on_commands_ctx_active; /* -- Multithreading -- */ ecs_os_cond_t worker_cond; /* Signal that worker threads can start */ ecs_os_cond_t sync_cond; /* Signal that worker thread job is done */ ecs_os_mutex_t sync_mutex; /* Mutex for job_cond */ int32_t workers_running; /* Number of threads running */ int32_t workers_waiting; /* Number of workers waiting on sync */ ecs_pipeline_state_t* pq; /* Pointer to the pipeline for the workers to execute */ bool workers_use_task_api; /* Workers are short-lived tasks, not long-running threads */ /* -- Time management -- */ ecs_time_t world_start_time; /* Timestamp of simulation start */ ecs_time_t frame_start_time; /* Timestamp of frame start */ ecs_ftime_t fps_sleep; /* Sleep time to prevent fps overshoot */ /* -- Metrics -- */ ecs_world_info_t info; /* -- World flags -- */ ecs_flags32_t flags; /* -- Default query flags -- */ ecs_flags32_t default_query_flags; /* Count that increases when component monitors change */ int32_t monitor_generation; /* -- Allocators -- */ ecs_world_allocators_t allocators; /* Static allocation sizes */ ecs_allocator_t allocator; /* Dynamic allocation sizes */ void *ctx; /* Application context */ void *binding_ctx; /* Binding-specific context */ ecs_ctx_free_t ctx_free; /**< Callback to free ctx */ ecs_ctx_free_t binding_ctx_free; /**< Callback to free binding_ctx */ ecs_vec_t fini_actions; /* Callbacks to execute when world exits */ }; #endif /** * @file storage/table_cache.h * @brief Data structure for fast table iteration/lookups. */ #ifndef FLECS_TABLE_CACHE_H_ #define FLECS_TABLE_CACHE_H_ void ecs_table_cache_init( ecs_world_t *world, ecs_table_cache_t *cache); void ecs_table_cache_fini( ecs_table_cache_t *cache); void ecs_table_cache_insert( ecs_table_cache_t *cache, const ecs_table_t *table, ecs_table_cache_hdr_t *result); void ecs_table_cache_insert_w_empty( ecs_table_cache_t *cache, const ecs_table_t *table, ecs_table_cache_hdr_t *result, bool is_empty); void ecs_table_cache_replace( ecs_table_cache_t *cache, const ecs_table_t *table, ecs_table_cache_hdr_t *elem); void* ecs_table_cache_remove( ecs_table_cache_t *cache, uint64_t table_id, ecs_table_cache_hdr_t *elem); void* ecs_table_cache_get( const ecs_table_cache_t *cache, const ecs_table_t *table); bool ecs_table_cache_set_empty( ecs_table_cache_t *cache, const ecs_table_t *table, bool empty); bool ecs_table_cache_is_empty( const ecs_table_cache_t *cache); #define flecs_table_cache_count(cache) (cache)->tables.count #define flecs_table_cache_empty_count(cache) (cache)->empty_tables.count #define flecs_table_cache_all_count(cache) ((cache)->tables.count + (cache)->empty_tables.count) bool flecs_table_cache_iter( ecs_table_cache_t *cache, ecs_table_cache_iter_t *out); bool flecs_table_cache_empty_iter( ecs_table_cache_t *cache, ecs_table_cache_iter_t *out); bool flecs_table_cache_all_iter( ecs_table_cache_t *cache, ecs_table_cache_iter_t *out); ecs_table_cache_hdr_t* flecs_table_cache_next_( ecs_table_cache_iter_t *it); #define flecs_table_cache_next(it, T)\ (ECS_CAST(T*, flecs_table_cache_next_(it))) #endif /** * @file storage/id_index.h * @brief Index for looking up tables by (component) id. */ #ifndef FLECS_ID_INDEX_H #define FLECS_ID_INDEX_H /* 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_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 empty tables in cache */ int32_t flecs_query_cache_empty_table_count( ecs_query_cache_t *cache); /* Return number of entities in cache (requires iterating tables) */ int32_t flecs_query_cache_entity_count( const ecs_query_cache_t *cache); /** * @file query/engine/cache_iter.h * @brief Cache iterator functions. */ /* Cache search */ 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); void flecs_commands_push( ecs_stage_t *stage); void flecs_commands_pop( ecs_stage_t *stage); ecs_entity_t flecs_stage_set_system( ecs_stage_t *stage, ecs_entity_t system); ecs_allocator_t* flecs_stage_get_allocator( ecs_world_t *world); ecs_stack_t* flecs_stage_get_stack_allocator( ecs_world_t *world); 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_table_set_empty( ecs_world_t *world, ecs_table_t *table); void flecs_delete_table( ecs_world_t *world, ecs_table_t *table); void flecs_process_pending_tables( const ecs_world_t *world); /* Suspend/resume readonly state. To fully support implicit registration of * components, it should be possible to register components while the world is * in readonly mode. It is not uncommon that a component is used first from * within a system, which are often ran while in readonly mode. * * Suspending readonly mode is only allowed when the world is not multithreaded. * When a world is multithreaded, it is not safe to (even temporarily) leave * readonly mode, so a multithreaded application should always explicitly * register components in advance. * * These operations also suspend deferred mode. */ typedef struct ecs_suspend_readonly_state_t { bool is_readonly; bool is_deferred; int32_t defer_count; ecs_entity_t scope; ecs_entity_t with; ecs_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); //////////////////////////////////////////////////////////////////////////////// //// 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, 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); flecs_bootstrap_entity(world, EcsOnTableEmpty, "OnTableEmpty", EcsFlecsCore); flecs_bootstrap_entity(world, EcsOnTableFill, "OnTableFilled", 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); flecs_process_pending_tables(world); ecs_iter_t it = { .real_world = ECS_CONST_CAST(ecs_world_t*, world), .world = ECS_CONST_CAST(ecs_world_t*, stage), .field_count = 1, .next = ecs_each_next }; ecs_id_record_t *idr = flecs_id_record_get(world, id); if (!idr) { return it; } ecs_each_iter_t *each_iter = &it.priv_.iter.each; each_iter->ids = id; each_iter->sizes = 0; if (idr->type_info) { each_iter->sizes = idr->type_info->size; } each_iter->sources = 0; 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; char *token_buffer; int32_t token_buffer_size; int32_t refcount; } ecs_script_impl_t; typedef struct ecs_script_parser_t ecs_script_parser_t; #define flecs_script_impl(script) ((ecs_script_impl_t*)script) /** * @file addons/script/tokenizer.h * @brief Script tokenizer. */ #ifndef FLECS_SCRIPT_TOKENIZER_H #define FLECS_SCRIPT_TOKENIZER_H /* Tokenizer */ typedef enum ecs_script_token_kind_t { EcsTokEnd = '\0', EcsTokUnknown, EcsTokScopeOpen = '{', EcsTokScopeClose = '}', EcsTokParenOpen = '(', EcsTokParenClose = ')', EcsTokBracketOpen = '[', EcsTokBracketClose = ']', EcsTokMul = '*', EcsTokComma = ',', EcsTokSemiColon = ';', EcsTokColon = ':', EcsTokAssign = '=', EcsTokBitwiseOr = '|', EcsTokNot = '!', EcsTokOptional = '?', EcsTokAnnotation = '@', EcsTokNewline = '\n', EcsTokEq, EcsTokNeq, EcsTokMatch, EcsTokOr, EcsTokIdentifier, EcsTokString, EcsTokNumber, EcsTokKeywordModule, EcsTokKeywordUsing, EcsTokKeywordWith, EcsTokKeywordIf, EcsTokKeywordElse, EcsTokKeywordTemplate, EcsTokKeywordProp, EcsTokKeywordConst, } ecs_script_token_kind_t; typedef struct ecs_script_token_t { const char *value; ecs_script_token_kind_t kind; } ecs_script_token_t; typedef struct ecs_script_tokens_t { int32_t count; ecs_script_token_t tokens[256]; } ecs_script_tokens_t; const char* flecs_script_expr( ecs_script_parser_t *parser, const char *ptr, ecs_script_token_t *out, char until); const char* flecs_script_until( ecs_script_parser_t *parser, const char *ptr, ecs_script_token_t *out, char until); const char* flecs_script_token_kind_str( ecs_script_token_kind_t kind); const char* flecs_script_token( ecs_script_parser_t *parser, const char *ptr, ecs_script_token_t *out, bool is_lookahead); const char* flecs_scan_whitespace( ecs_script_parser_t *parser, const char *pos); #endif struct ecs_script_parser_t { ecs_script_impl_t *script; ecs_script_scope_t *scope; const char *pos; char *token_cur; char *token_keep; bool significant_newline; /* For term parser */ ecs_term_t *term; ecs_oper_kind_t extra_oper; ecs_term_ref_t *extra_args; }; /** * @file addons/script/ast.h * @brief Script AST. */ #ifndef FLECS_SCRIPT_AST_H #define FLECS_SCRIPT_AST_H typedef enum ecs_script_node_kind_t { EcsAstScope, EcsAstTag, EcsAstComponent, EcsAstDefaultComponent, EcsAstVarComponent, EcsAstWithVar, EcsAstWithTag, EcsAstWithComponent, EcsAstWith, EcsAstUsing, EcsAstModule, EcsAstAnnotation, EcsAstTemplate, EcsAstProp, EcsAstConst, EcsAstEntity, EcsAstPairScope, EcsAstIf, } ecs_script_node_kind_t; typedef struct ecs_script_node_t { ecs_script_node_kind_t kind; const char *pos; } ecs_script_node_t; struct ecs_script_scope_t { ecs_script_node_t node; ecs_vec_t stmts; ecs_script_scope_t *parent; ecs_id_t default_component_eval; }; typedef struct ecs_script_id_t { const char *first; const char *second; ecs_id_t flag; ecs_id_t eval; } ecs_script_id_t; typedef struct ecs_script_tag_t { ecs_script_node_t node; ecs_script_id_t id; } ecs_script_tag_t; typedef struct ecs_script_component_t { ecs_script_node_t node; ecs_script_id_t id; const char *expr; ecs_value_t eval; bool is_collection; } ecs_script_component_t; typedef struct ecs_script_default_component_t { ecs_script_node_t node; const char *expr; ecs_value_t eval; } ecs_script_default_component_t; typedef struct ecs_script_var_component_t { ecs_script_node_t node; const char *name; } ecs_script_var_component_t; struct ecs_script_entity_t { ecs_script_node_t node; const char *kind; const char *name; bool name_is_var; bool kind_w_expr; ecs_script_scope_t *scope; // Populated during eval ecs_script_entity_t *parent; ecs_entity_t eval; ecs_entity_t eval_kind; }; typedef struct ecs_script_with_t { ecs_script_node_t node; ecs_script_scope_t *expressions; ecs_script_scope_t *scope; } ecs_script_with_t; typedef struct ecs_script_inherit_t { ecs_script_node_t node; ecs_script_scope_t *base_list; } ecs_script_inherit_t; typedef struct ecs_script_pair_scope_t { ecs_script_node_t node; ecs_script_id_t id; ecs_script_scope_t *scope; } ecs_script_pair_scope_t; typedef struct ecs_script_using_t { ecs_script_node_t node; const char *name; } ecs_script_using_t; typedef struct ecs_script_module_t { ecs_script_node_t node; const char *name; } ecs_script_module_t; typedef struct ecs_script_annot_t { ecs_script_node_t node; const char *name; const char *expr; } ecs_script_annot_t; typedef struct ecs_script_template_node_t { ecs_script_node_t node; const char *name; ecs_script_scope_t* scope; } ecs_script_template_node_t; typedef struct ecs_script_var_node_t { ecs_script_node_t node; const char *name; const char *type; const char *expr; } ecs_script_var_node_t; typedef struct ecs_script_if_t { ecs_script_node_t node; ecs_script_scope_t *if_true; ecs_script_scope_t *if_false; const char *expr; } ecs_script_if_t; #define ecs_script_node(kind, node)\ ((ecs_script_##kind##_t*)node) bool flecs_scope_is_empty( ecs_script_scope_t *scope); ecs_script_scope_t* flecs_script_insert_scope( ecs_script_parser_t *parser); ecs_script_entity_t* flecs_script_insert_entity( ecs_script_parser_t *parser, const char *name); ecs_script_pair_scope_t* flecs_script_insert_pair_scope( ecs_script_parser_t *parser, const char *first, const char *second); ecs_script_with_t* flecs_script_insert_with( ecs_script_parser_t *parser); ecs_script_using_t* flecs_script_insert_using( ecs_script_parser_t *parser, const char *name); ecs_script_module_t* flecs_script_insert_module( ecs_script_parser_t *parser, const char *name); ecs_script_template_node_t* flecs_script_insert_template( ecs_script_parser_t *parser, const char *name); ecs_script_annot_t* flecs_script_insert_annot( ecs_script_parser_t *parser, const char *name, const char *expr); ecs_script_var_node_t* flecs_script_insert_var( ecs_script_parser_t *parser, const char *name); ecs_script_tag_t* flecs_script_insert_tag( ecs_script_parser_t *parser, const char *name); ecs_script_tag_t* flecs_script_insert_pair_tag( ecs_script_parser_t *parser, const char *first, const char *second); ecs_script_component_t* flecs_script_insert_component( ecs_script_parser_t *parser, const char *name); ecs_script_component_t* flecs_script_insert_pair_component( ecs_script_parser_t *parser, const char *first, const char *second); ecs_script_default_component_t* flecs_script_insert_default_component( ecs_script_parser_t *parser); ecs_script_var_component_t* flecs_script_insert_var_component( ecs_script_parser_t *parser, const char *name); ecs_script_if_t* flecs_script_insert_if( ecs_script_parser_t *parser); #endif /** * @file addons/script/visit.h * @brief Script AST visitor utilities. */ #ifndef FLECS_SCRIPT_VISIT_H #define FLECS_SCRIPT_VISIT_H typedef struct ecs_script_visit_t ecs_script_visit_t; typedef int (*ecs_visit_action_t)( ecs_script_visit_t *visitor, ecs_script_node_t *node); struct ecs_script_visit_t { ecs_script_impl_t *script; ecs_visit_action_t visit; ecs_script_node_t* nodes[256]; ecs_script_node_t *prev, *next; int32_t depth; }; int ecs_script_visit_( ecs_script_visit_t *visitor, ecs_visit_action_t visit, ecs_script_impl_t *script); #define ecs_script_visit(script, visitor, visit) \ ecs_script_visit_((ecs_script_visit_t*)visitor,\ (ecs_visit_action_t)visit,\ script) int ecs_script_visit_node_( ecs_script_visit_t *v, ecs_script_node_t *node); #define ecs_script_visit_node(visitor, node) \ ecs_script_visit_node_((ecs_script_visit_t*)visitor, \ (ecs_script_node_t*)node) int ecs_script_visit_scope_( ecs_script_visit_t *v, ecs_script_scope_t *node); #define ecs_script_visit_scope(visitor, node) \ ecs_script_visit_scope_((ecs_script_visit_t*)visitor, node) ecs_script_node_t* ecs_script_parent_node_( ecs_script_visit_t *v); #define ecs_script_parent_node(visitor) \ ecs_script_parent_node_((ecs_script_visit_t*)visitor) ecs_script_scope_t* ecs_script_current_scope_( ecs_script_visit_t *v); #define ecs_script_current_scope(visitor) \ ecs_script_current_scope_((ecs_script_visit_t*)visitor) ecs_script_node_t* ecs_script_parent_( ecs_script_visit_t *v, ecs_script_node_t *node); #define ecs_script_parent(visitor, node) \ ecs_script_parent_((ecs_script_visit_t*)visitor, (ecs_script_node_t*)node) ecs_script_node_t* ecs_script_next_node_( ecs_script_visit_t *v); #define ecs_script_next_node(visitor) \ ecs_script_next_node_((ecs_script_visit_t*)visitor) int32_t ecs_script_node_line_number_( ecs_script_impl_t *script, ecs_script_node_t *node); #define ecs_script_node_line_number(script, node) \ ecs_script_node_line_number_(script, (ecs_script_node_t*)node) #endif /** * @file addons/script/visit_eval.h * @brief Script evaluation visitor. */ #ifndef FLECS_SCRIPT_VISIT_EVAL_H #define FLECS_SCRIPT_VISIT_EVAL_H typedef struct ecs_script_eval_visitor_t { ecs_script_visit_t base; ecs_world_t *world; ecs_allocator_t *allocator; ecs_script_template_t *template; ecs_entity_t module; ecs_entity_t parent; ecs_script_entity_t *entity; ecs_vec_t using; ecs_vec_t with; ecs_vec_t with_type_info; ecs_vec_t annot; ecs_entity_t with_relationship; int32_t with_relationship_sp; ecs_script_vars_t *vars; ecs_stack_t stack; } ecs_script_eval_visitor_t; void flecs_script_eval_error_( ecs_script_eval_visitor_t *v, ecs_script_node_t *node, const char *fmt, ...); #define flecs_script_eval_error(v, node, ...)\ flecs_script_eval_error_(v, (ecs_script_node_t*)node, __VA_ARGS__) ecs_entity_t flecs_script_find_entity( ecs_script_eval_visitor_t *v, ecs_entity_t from, const char *path); ecs_entity_t flecs_script_create_entity( ecs_script_eval_visitor_t *v, const char *name); const ecs_type_info_t* flecs_script_get_type_info( ecs_script_eval_visitor_t *v, void *node, ecs_id_t id); int flecs_script_eval_expr( ecs_script_eval_visitor_t *v, const char *expr, ecs_value_t *value); int flecs_script_eval_template( ecs_script_eval_visitor_t *v, ecs_script_template_node_t *template); ecs_script_template_t* flecs_script_template_init( ecs_script_impl_t *script); void flecs_script_template_fini( ecs_script_impl_t *script, ecs_script_template_t *template); void flecs_script_eval_visit_init( ecs_script_impl_t *script, ecs_script_eval_visitor_t *v); void flecs_script_eval_visit_fini( ecs_script_eval_visitor_t *v); int flecs_script_eval_node( ecs_script_eval_visitor_t *v, ecs_script_node_t *node); #endif struct ecs_script_template_t { /* Template handle */ ecs_entity_t entity; /* Template AST node */ ecs_script_template_node_t *node; /* Hoisted using statements */ ecs_vec_t using_; /* Hoisted variables */ ecs_script_vars_t *vars; /* Default values for props */ ecs_vec_t prop_defaults; /* Type info for template component */ const ecs_type_info_t *type_info; }; ecs_script_t* flecs_script_new( ecs_world_t *world); ecs_script_scope_t* flecs_script_scope_new( ecs_script_parser_t *parser); int flecs_script_visit_free( ecs_script_t *script); ecs_script_vars_t* flecs_script_vars_push( ecs_script_vars_t *parent, ecs_stack_t *stack, ecs_allocator_t *allocator); int flecs_terms_parse( ecs_script_t *script, ecs_term_t *terms, int32_t *term_count_out); const char* flecs_id_parse( const ecs_world_t *world, const char *name, const char *expr, ecs_id_t *id); const char* flecs_term_parse( ecs_world_t *world, const char *name, const char *expr, ecs_term_t *term, char *token_buffer); #endif // FLECS_SCRIPT #endif // FLECS_SCRIPT_PRIVATE_H #endif static const ecs_entity_t* flecs_bulk_new( ecs_world_t *world, ecs_table_t *table, const ecs_entity_t *entities, ecs_type_t *component_ids, int32_t count, void **c_info, bool move, int32_t *row_out, ecs_table_diff_t *diff); typedef struct { const ecs_type_info_t *ti; void *ptr; } flecs_component_ptr_t; static flecs_component_ptr_t flecs_table_get_component( ecs_table_t *table, int32_t column_index, int32_t row) { ecs_check(column_index < table->column_count, ECS_NOT_A_COMPONENT, NULL); ecs_column_t *column = &table->data.columns[column_index]; return (flecs_component_ptr_t){ .ti = column->ti, .ptr = ECS_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 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; } 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); } 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); } 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, desc->symbol); } else { ecs_set_symbol(world, result, desc->symbol); } } /* If a name is provided but not yet assigned, add the Name component */ if (name && !name_assigned) { ecs_add_path_w_sep(world, result, scope, name, sep, root_sep); } else if (new_entity && scope) { ecs_add_pair(world, result, EcsChildOf, scope); } /* Find existing table */ ecs_table_t *src_table = NULL, *table = NULL; ecs_record_t *r = flecs_entities_get(world, result); table = r->table; /* Add components from the 'add' array */ if (desc->add) { int32_t i = 0; ecs_id_t id; while ((id = desc->add[i ++])) { table = flecs_find_table_add(world, table, id, &diff); } } /* Add components from the 'set' array */ if (desc->set) { int32_t i = 0; ecs_id_t id; while ((id = desc->set[i ++].type)) { table = flecs_find_table_add(world, table, id, &diff); } } /* Add ids from .expr */ { int32_t i, count = ecs_vec_count(&ids); ecs_id_t *expr_ids = ecs_vec_first(&ids); for (i = 0; i < count; i ++) { table = flecs_find_table_add(world, table, expr_ids[i], &diff); } } /* Find destination table */ /* If this is a new entity without a name, add the scope. If a name is * provided, the scope will be added by the add_path_w_sep function */ if (new_entity) { if (new_entity && scope && !name && !name_assigned) { table = flecs_find_table_add( world, table, ecs_pair(EcsChildOf, scope), &diff); } if (with) { table = flecs_find_table_add(world, table, with, &diff); } } /* Commit entity to destination table */ if (src_table != table) { flecs_defer_begin(world, world->stages[0]); ecs_table_diff_t table_diff; flecs_table_diff_build_noalloc(&diff, &table_diff); flecs_commit(world, result, r, table, &table_diff, true, 0); flecs_table_diff_builder_fini(world, &diff); flecs_defer_end(world, world->stages[0]); } /* Set component values */ if (desc->set) { table = r->table; ecs_assert(r->table != NULL, ECS_INTERNAL_ERROR, NULL); int32_t i = 0, row = ECS_RECORD_TO_ROW(r->row); const ecs_value_t *v; flecs_defer_begin(world, world->stages[0]); while ((void)(v = &desc->set[i ++]), v->type) { if (!v->ptr) { continue; } ecs_assert(ECS_RECORD_TO_ROW(r->row) == row, ECS_INTERNAL_ERROR, NULL); ecs_id_record_t *idr = flecs_id_record_get(world, v->type); flecs_component_ptr_t ptr = flecs_get_component_ptr(table, row, idr); ecs_check(ptr.ptr != NULL, ECS_INTERNAL_ERROR, NULL); const ecs_type_info_t *ti = idr->type_info; flecs_copy_id(world, r, v->type, flecs_itosize(ti->size), ptr.ptr, v->ptr, ti); } flecs_defer_end(world, world->stages[0]); } flecs_table_diff_builder_fini(world, &diff); ecs_vec_fini_t(&world->allocator, &ids, ecs_id_t); return 0; error: ecs_vec_fini_t(&world->allocator, &ids, ecs_id_t); return -1; } /* When in deferred mode, we need to add/remove components one by one using * the regular operations. */ static void flecs_deferred_add_remove( ecs_world_t *world, ecs_entity_t entity, const char *name, const ecs_entity_desc_t *desc, ecs_entity_t scope, ecs_id_t with, bool flecs_new_entity, bool name_assigned) { const char *sep = desc->sep; const char *root_sep = desc->root_sep; /* If this is a new entity without a name, add the scope. If a name is * provided, the scope will be added by the add_path_w_sep function */ if (flecs_new_entity) { if (flecs_new_entity && scope && !name && !name_assigned) { ecs_add_id(world, entity, ecs_pair(EcsChildOf, scope)); } if (with) { ecs_add_id(world, entity, with); } } /* Add components from the 'add' id array */ if (desc->add) { int32_t i = 0; ecs_id_t id; while ((id = desc->add[i ++])) { bool defer = true; if (ECS_HAS_ID_FLAG(id, PAIR) && ECS_PAIR_FIRST(id) == EcsChildOf) { scope = ECS_PAIR_SECOND(id); if (name && (!desc->id || !name_assigned)) { /* New named entities are created by temporarily going out of * readonly mode to ensure no duplicates are created. */ defer = false; } } if (defer) { ecs_add_id(world, entity, id); } } } /* Set component values */ if (desc->set) { int32_t i = 0; const ecs_value_t *v; while ((void)(v = &desc->set[i ++]), v->type) { if (v->ptr) { ecs_set_id(world, entity, v->type, 0, v->ptr); } else { ecs_add_id(world, entity, v->type); } } } /* Add components from the 'add_expr' expression */ if (desc->add_expr) { flecs_defer_from_expr(world, entity, name, desc->add_expr); } int32_t thread_count = ecs_get_stage_count(world); /* Set symbol */ if (desc->symbol) { const char *sym = ecs_get_symbol(world, entity); if (!sym || ecs_os_strcmp(sym, desc->symbol)) { if (thread_count <= 1) { /* See above */ ecs_suspend_readonly_state_t state; ecs_world_t *real_world = flecs_suspend_readonly(world, &state); ecs_set_symbol(world, entity, desc->symbol); flecs_resume_readonly(real_world, &state); } else { ecs_set_symbol(world, entity, desc->symbol); } } } /* Set name */ if (name && !name_assigned) { ecs_add_path_w_sep(world, entity, scope, name, sep, root_sep); } } ecs_entity_t ecs_entity_init( ecs_world_t *world, const ecs_entity_desc_t *desc) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, "ecs_entity_desc_t was not initialized to zero"); ecs_stage_t *stage = flecs_stage_from_world(&world); ecs_entity_t scope = stage->scope; ecs_id_t with = ecs_get_with(world); ecs_entity_t result = desc->id; #ifdef FLECS_DEBUG if (desc->add) { ecs_id_t id; int32_t i = 0; while ((id = desc->add[i ++])) { if (ECS_HAS_ID_FLAG(id, PAIR) && (ECS_PAIR_FIRST(id) == EcsChildOf)) { if (desc->name) { ecs_check(false, ECS_INVALID_PARAMETER, "%s: cannot set parent in " "ecs_entity_desc_t::add, use ecs_entity_desc_t::parent", desc->name); } else { ecs_check(false, ECS_INVALID_PARAMETER, "cannot set parent in " "ecs_entity_desc_t::add, use ecs_entity_desc_t::parent"); } } } } #endif const char *name = desc->name; const char *sep = desc->sep; if (!sep) { sep = "."; } if (name) { if (!name[0]) { name = NULL; } else if (flecs_name_is_id(name)){ ecs_entity_t id = flecs_name_to_id(name); if (!id) { return 0; } if (result && (id != result)) { ecs_err("name id conflicts with provided id"); return 0; } name = NULL; result = id; } } const char *root_sep = desc->root_sep; bool flecs_new_entity = false; bool name_assigned = false; /* Remove optional prefix from name. Entity names can be derived from * language identifiers, such as components (typenames) and systems * function names). Because C does not have namespaces, such identifiers * often encode the namespace as a prefix. * To ensure interoperability between C and C++ (and potentially other * languages with namespacing) the entity must be stored without this prefix * and with the proper namespace, which is what the name_prefix is for */ const char *prefix = world->info.name_prefix; if (name && prefix) { ecs_size_t len = ecs_os_strlen(prefix); if (!ecs_os_strncmp(name, prefix, len) && (isupper(name[len]) || name[len] == '_')) { if (name[len] == '_') { name = name + len + 1; } else { name = name + len; } } } /* Parent field takes precedence over scope */ if (desc->parent) { scope = desc->parent; } /* Find or create entity */ if (!result) { if (name) { /* If add array contains a ChildOf pair, use it as scope instead */ result = ecs_lookup_path_w_sep( world, scope, name, sep, root_sep, false); if (result) { name_assigned = true; } } if (!result) { if (desc->use_low_id) { result = ecs_new_low_id(world); } else { result = ecs_new(world); } flecs_new_entity = true; ecs_assert(ecs_get_type(world, result) == NULL, ECS_INTERNAL_ERROR, NULL); } } else { /* Make sure provided id is either alive or revivable */ ecs_make_alive(world, result); name_assigned = ecs_has_pair( world, result, ecs_id(EcsIdentifier), EcsName); if (name && name_assigned) { /* If entity has name, verify that name matches. The name provided * to the function could either have been relative to the current * scope, or fully qualified. */ char *path; ecs_size_t root_sep_len = root_sep ? ecs_os_strlen(root_sep) : 0; if (root_sep && !ecs_os_strncmp(name, root_sep, root_sep_len)) { /* Fully qualified name was provided, so make sure to * compare with fully qualified name */ path = ecs_get_path_w_sep(world, 0, result, sep, root_sep); } else { /* Relative name was provided, so make sure to compare with * relative name */ if (!sep || sep[0]) { path = ecs_get_path_w_sep(world, scope, result, sep, ""); } else { /* Safe, only freed when sep is valid */ path = ECS_CONST_CAST(char*, ecs_get_name(world, result)); } } if (path) { if (ecs_os_strcmp(path, name)) { /* Mismatching name */ ecs_err("existing entity '%s' is initialized with " "conflicting name '%s'", path, name); if (!sep || sep[0]) { ecs_os_free(path); } return 0; } if (!sep || sep[0]) { ecs_os_free(path); } } } } ecs_assert(name_assigned == ecs_has_pair( world, result, ecs_id(EcsIdentifier), EcsName), ECS_INTERNAL_ERROR, NULL); if (stage->defer) { flecs_deferred_add_remove((ecs_world_t*)stage, result, name, desc, scope, with, flecs_new_entity, name_assigned); } else { if (flecs_traverse_add(world, result, name, desc, scope, with, flecs_new_entity, name_assigned)) { return 0; } } return result; error: return 0; } const ecs_entity_t* ecs_bulk_init( ecs_world_t *world, const ecs_bulk_desc_t *desc) { flecs_poly_assert(world, ecs_world_t); ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INTERNAL_ERROR, NULL); ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, "ecs_bulk_desc_t was not initialized to zero"); const ecs_entity_t *entities = desc->entities; int32_t count = desc->count; int32_t sparse_count = 0; if (!entities) { sparse_count = flecs_entities_count(world); entities = flecs_entities_new_ids(world, count); ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL); } else { int i; for (i = 0; i < count; i ++) { ecs_make_alive(world, entities[i]); } } ecs_type_t ids; ecs_table_t *table = desc->table; if (!table) { ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT; flecs_table_diff_builder_init(world, &diff); int32_t i = 0; ecs_id_t id; while ((id = desc->ids[i])) { table = flecs_find_table_add(world, table, id, &diff); i ++; } ids.array = ECS_CONST_CAST(ecs_id_t*, desc->ids); ids.count = i; ecs_table_diff_t table_diff; flecs_table_diff_build_noalloc(&diff, &table_diff); flecs_bulk_new(world, table, entities, &ids, count, desc->data, true, NULL, &table_diff); flecs_table_diff_builder_fini(world, &diff); } else { ecs_table_diff_t diff = { .added.array = table->type.array, .added.count = table->type.count }; ids = (ecs_type_t){.array = diff.added.array, .count = diff.added.count}; flecs_bulk_new(world, table, entities, &ids, count, desc->data, true, NULL, &diff); } if (!sparse_count) { return entities; } else { /* Refetch entity ids, in case the underlying array was reallocated */ entities = flecs_entities_ids(world); return &entities[sparse_count]; } error: return NULL; } static void flecs_check_component( ecs_world_t *world, ecs_entity_t result, const EcsComponent *ptr, ecs_size_t size, ecs_size_t alignment) { if (ptr->size != size) { char *path = ecs_get_path(world, result); ecs_abort(ECS_INVALID_COMPONENT_SIZE, path); ecs_os_free(path); } if (ptr->alignment != alignment) { char *path = ecs_get_path(world, result); ecs_abort(ECS_INVALID_COMPONENT_ALIGNMENT, path); ecs_os_free(path); } } ecs_entity_t ecs_component_init( ecs_world_t *world, const ecs_component_desc_t *desc) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, "ecs_component_desc_t was not initialized to 0"); /* If existing entity is provided, check if it is already registered as a * component and matches the size/alignment. This can prevent having to * suspend readonly mode, and increases the number of scenarios in which * this function can be called in multithreaded mode. */ ecs_entity_t result = desc->entity; if (result && ecs_is_alive(world, result)) { const EcsComponent *const_ptr = ecs_get(world, result, EcsComponent); if (const_ptr) { flecs_check_component(world, result, const_ptr, desc->type.size, desc->type.alignment); return result; } } ecs_suspend_readonly_state_t readonly_state; world = flecs_suspend_readonly(world, &readonly_state); bool new_component = true; if (!result) { result = ecs_new_low_id(world); } else { ecs_make_alive(world, result); new_component = ecs_has(world, result, EcsComponent); } EcsComponent *ptr = ecs_ensure(world, result, EcsComponent); if (!ptr->size) { ecs_assert(ptr->alignment == 0, ECS_INTERNAL_ERROR, NULL); ptr->size = desc->type.size; ptr->alignment = desc->type.alignment; if (!new_component || ptr->size != desc->type.size) { if (!ptr->size) { ecs_trace("#[green]tag#[reset] %s created", ecs_get_name(world, result)); } else { ecs_trace("#[green]component#[reset] %s created", ecs_get_name(world, result)); } } } else { flecs_check_component(world, result, ptr, desc->type.size, desc->type.alignment); } if (desc->type.name && new_component) { ecs_entity(world, { .id = result, .name = desc->type.name }); } ecs_modified(world, result, EcsComponent); if (desc->type.size && !ecs_id_in_use(world, result) && !ecs_id_in_use(world, ecs_pair(result, EcsWildcard))) { ecs_set_hooks_id(world, result, &desc->type.hooks); } if (result >= world->info.last_component_id && result < FLECS_HI_COMPONENT_ID) { world->info.last_component_id = result + 1; } flecs_resume_readonly(world, &readonly_state); ecs_assert(result != 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(ecs_has(world, result, EcsComponent), ECS_INTERNAL_ERROR, NULL); return result; error: return 0; } const ecs_entity_t* ecs_bulk_new_w_id( ecs_world_t *world, ecs_id_t id, int32_t count) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); const ecs_entity_t *ids; if (flecs_defer_bulk_new(world, stage, count, id, &ids)) { return ids; } ecs_table_t *table = &world->store.root; ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT; flecs_table_diff_builder_init(world, &diff); if (id) { table = flecs_find_table_add(world, table, id, &diff); } ecs_table_diff_t td; flecs_table_diff_build_noalloc(&diff, &td); ids = flecs_bulk_new(world, table, NULL, NULL, count, NULL, false, NULL, &td); flecs_table_diff_builder_fini(world, &diff); flecs_defer_end(world, stage); return ids; error: return NULL; } void ecs_clear( ecs_world_t *world, ecs_entity_t entity) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_clear(stage, entity)) { return; } ecs_record_t *r = flecs_entities_get(world, entity); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_t *table = r->table; if (table) { ecs_table_diff_t diff = { .removed = table->type, .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); /* Make sure we're evaluating a consistent list of non-empty tables */ ecs_run_aperiodic(world, EcsAperiodicEmptyTables); /* Collect all ids that need to be deleted */ flecs_on_delete_mark(world, id, action, delete_id); /* Only perform cleanup if we're the first stack frame doing it */ if (!count && ecs_vec_count(&world->store.marked_ids)) { ecs_dbg_2("#[red]delete#[reset]"); ecs_log_push_2(); /* Empty tables with all the to be deleted ids */ flecs_on_delete_clear_tables(world); /* All marked tables are empty, ensure they're in the right list */ ecs_run_aperiodic(world, EcsAperiodicEmptyTables); /* Release remaining references to the ids */ flecs_on_delete_clear_ids(world); /* Verify deleted ids are no longer in use */ #ifdef FLECS_DEBUG ecs_marked_id_t *ids = ecs_vec_first(&world->store.marked_ids); 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 (!flecs_table_record_get(world, table, id)) { flecs_defer_end(world, stage); return; } ecs_type_t ids = { .array = &id, .count = 1 }; flecs_notify_on_set(world, table, ECS_RECORD_TO_ROW(r->row), 1, &ids, owned); flecs_table_mark_dirty(world, table, id); flecs_defer_end(world, stage); error: return; } void ecs_modified_id( ecs_world_t *world, ecs_entity_t entity, ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_modified(stage, entity, id)) { return; } /* If the entity does not have the component, calling ecs_modified is * invalid. The assert needs to happen after the defer statement, as the * entity may not have the component when this function is called while * operations are being deferred. */ ecs_check(ecs_has_id(world, entity, id), ECS_INVALID_PARAMETER, NULL); ecs_record_t *r = flecs_entities_get(world, entity); ecs_table_t *table = r->table; ecs_type_t ids = { .array = &id, .count = 1 }; flecs_notify_on_set(world, table, ECS_RECORD_TO_ROW(r->row), 1, &ids, true); flecs_table_mark_dirty(world, table, id); flecs_defer_end(world, stage); error: return; } static void flecs_set_id_copy( ecs_world_t *world, ecs_stage_t *stage, ecs_entity_t entity, ecs_id_t id, size_t size, void *ptr) { if (flecs_defer_cmd(stage)) { flecs_defer_set(world, stage, EcsCmdSet, entity, id, flecs_utosize(size), ptr, NULL); return; } ecs_record_t *r = flecs_entities_get(world, entity); flecs_component_ptr_t dst = flecs_ensure(world, entity, id, r); flecs_copy_id(world, r, id, size, dst.ptr, ptr, dst.ti); flecs_defer_end(world, stage); } static void flecs_set_id_move( ecs_world_t *world, ecs_stage_t *stage, ecs_entity_t entity, ecs_id_t id, size_t size, void *ptr, ecs_cmd_kind_t cmd_kind) { if (flecs_defer_cmd(stage)) { flecs_defer_set(world, stage, cmd_kind, entity, id, flecs_utosize(size), ptr, NULL); return; } ecs_record_t *r = flecs_entities_get(world, entity); flecs_component_ptr_t dst = flecs_ensure(world, entity, id, r); ecs_check(dst.ptr != NULL, ECS_INVALID_PARAMETER, NULL); const ecs_type_info_t *ti = dst.ti; ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); ecs_move_t move; if (cmd_kind != EcsCmdEmplace) { /* ctor will have happened by ensure */ move = ti->hooks.move_dtor; } else { move = ti->hooks.ctor_move_dtor; } if (move) { move(dst.ptr, ptr, 1, ti); } else { ecs_os_memcpy(dst.ptr, ptr, flecs_utosize(size)); } flecs_table_mark_dirty(world, r->table, id); if (cmd_kind == EcsCmdSet) { ecs_table_t *table = r->table; if (table->flags & EcsTableHasOnSet || ti->hooks.on_set) { ecs_type_t ids = { .array = &id, .count = 1 }; flecs_notify_on_set( world, table, ECS_RECORD_TO_ROW(r->row), 1, &ids, true); } } flecs_defer_end(world, stage); error: return; } void ecs_set_id( ecs_world_t *world, ecs_entity_t entity, ecs_id_t id, size_t size, const void *ptr) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); /* Safe to cast away const: function won't modify if move arg is false */ flecs_set_id_copy(world, stage, entity, id, size, ECS_CONST_CAST(void*, ptr)); error: return; } #if defined(FLECS_DEBUG) || defined(FLECS_KEEP_ASSERT) static bool flecs_can_toggle( ecs_world_t *world, ecs_id_t id) { ecs_id_record_t *idr = flecs_id_record_get(world, id); if (!idr) { return false; } return (idr->flags & EcsIdCanToggle) != 0; } #endif void ecs_enable_id( ecs_world_t *world, ecs_entity_t entity, ecs_id_t id, bool enable) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); ecs_check(flecs_can_toggle(world, id), ECS_INVALID_OPERATION, "add CanToggle trait to component"); if (flecs_defer_enable(stage, entity, id, enable)) { return; } ecs_record_t *r = flecs_entities_get(world, entity); ecs_entity_t bs_id = id | ECS_TOGGLE; ecs_table_t *table = r->table; int32_t index = -1; if (table) { index = ecs_table_get_type_index(world, table, bs_id); } if (index == -1) { ecs_add_id(world, entity, bs_id); flecs_defer_end(world, stage); ecs_enable_id(world, entity, id, enable); return; } ecs_assert(table->_ != NULL, ECS_INTERNAL_ERROR, NULL); index -= table->_->bs_offset; ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL); /* Data cannot be NULL, since entity is stored in the table */ ecs_bitset_t *bs = &table->_->bs_columns[index]; ecs_assert(bs != NULL, ECS_INTERNAL_ERROR, NULL); flecs_bitset_set(bs, ECS_RECORD_TO_ROW(r->row), enable); flecs_defer_end(world, stage); error: return; } bool ecs_is_enabled_id( const ecs_world_t *world, ecs_entity_t entity, ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); /* Make sure we're not working with a stage */ world = ecs_get_world(world); ecs_record_t *r = flecs_entities_get(world, entity); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_t *table = r->table; if (!table) { return false; } ecs_entity_t bs_id = id | ECS_TOGGLE; int32_t index = ecs_table_get_type_index(world, table, bs_id); if (index == -1) { /* If table does not have TOGGLE column for component, component is * always enabled, if the entity has it */ return ecs_has_id(world, entity, id); } ecs_assert(table->_ != NULL, ECS_INTERNAL_ERROR, NULL); index -= table->_->bs_offset; ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL); ecs_bitset_t *bs = &table->_->bs_columns[index]; return flecs_bitset_get(bs, ECS_RECORD_TO_ROW(r->row)); error: return false; } bool ecs_has_id( const ecs_world_t *world, ecs_entity_t entity, ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_check(id != 0, ECS_INVALID_PARAMETER, NULL); /* Make sure we're not working with a stage */ world = ecs_get_world(world); ecs_record_t *r = flecs_entities_get_any(world, entity); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_t *table = r->table; if (!table) { return false; } 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); table = flecs_find_table_add(world, table, id, diff); if (diff->added.count == (added_index + 1)) { /* Single id was added, must be at the end of the array */ ecs_assert(ids[added_index] == id, ECS_INTERNAL_ERROR, NULL); set_mask |= (1llu << added_index); } else { /* Id was already added or multiple ids got added. Do a linear * search to find the index we need to set the set_mask. */ int32_t i; for (i = 0; i < diff->added.count; i ++) { if (ids[i] == id) { break; } } ecs_assert(i != diff->added.count, ECS_INTERNAL_ERROR, NULL); set_mask |= (1llu << i); } world->info.cmd.batched_command_count ++; break; } case EcsCmdEmplace: /* Don't add for emplace, as this requires a special call to ensure * the constructor is not invoked for the component */ break; case EcsCmdRemove: table = flecs_find_table_remove(world, table, id, diff); world->info.cmd.batched_command_count ++; break; case EcsCmdClear: if (table) { ecs_id_t *ids = ecs_vec_grow_t(&world->allocator, &diff->removed, ecs_id_t, table->type.count); ecs_os_memcpy_n(ids, table->type.array, ecs_id_t, table->type.count); 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); cmd->is._1.value = NULL; } } else { ecs_os_memcpy(ptr.ptr, cmd->is._1.value, ti->size); } if (cmd->kind == EcsCmdSet) { /* A set operation is add + copy + modified. We just did * the add the copy, so the only thing that's left is a * modified command, which will call the OnSet * observers. */ cmd->kind = EcsCmdModified; } else { /* If this was an ensure, nothing's left to be done */ cmd->kind = EcsCmdSkip; } } else { /* The entity no longer has the component which means that * there was a remove command for the component in the * command queue. In that case skip the command. */ cmd->kind = EcsCmdSkip; } break; } case EcsCmdClone: case EcsCmdBulkNew: case EcsCmdAdd: case EcsCmdRemove: case EcsCmdEmplace: case EcsCmdModified: case EcsCmdModifiedNoHook: case EcsCmdAddModified: case EcsCmdPath: case EcsCmdDelete: case EcsCmdClear: case EcsCmdOnDeleteAction: case EcsCmdEnable: case EcsCmdDisable: case EcsCmdEvent: case EcsCmdSkip: break; } } while ((cur = next_for_entity)); } if (added.count && 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) { 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; } ecs_stage_t *dst_stage = flecs_stage_from_world(&world); ecs_commands_t *commands = stage->cmd; ecs_vec_t *queue = &commands->queue; if (ecs_vec_count(queue)) { /* Internal callback for capturing commands */ if (world->on_commands_active) { world->on_commands_active(stage, queue, world->on_commands_ctx_active); } ecs_cmd_t *cmds = ecs_vec_first(queue); int32_t i, count = ecs_vec_count(queue); ecs_table_diff_builder_t diff; flecs_table_diff_builder_init(world, &diff); flecs_commands_push(stage); for (i = 0; i < count; i ++) { ecs_cmd_t *cmd = &cmds[i]; ecs_entity_t e = cmd->entity; bool is_alive = flecs_entities_is_alive(world, e); /* A negative index indicates the first command for an entity */ if (merge_to_world && (cmd->next_for_entity < 0)) { /* Batch commands for entity to limit archetype moves */ if (is_alive) { flecs_cmd_batch_for_entity(world, &diff, e, cmds, i); } else { world->info.cmd.discard_count ++; } } /* Invalidate entry */ if (cmd->entry) { cmd->entry->first = -1; } /* If entity is no longer alive, this could be because the queue * contained both a delete and a subsequent add/remove/set which * should be ignored. */ ecs_cmd_kind_t kind = cmd->kind; if ((kind != EcsCmdPath) && ((kind == EcsCmdSkip) || (e && !is_alive))) { world->info.cmd.discard_count ++; flecs_discard_cmd(world, cmd); continue; } ecs_id_t id = cmd->id; switch(kind) { case EcsCmdAdd: ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); if (flecs_remove_invalid(world, id, &id)) { if (id) { world->info.cmd.add_count ++; flecs_add_id(world, e, id); } else { world->info.cmd.discard_count ++; } } else { world->info.cmd.discard_count ++; ecs_delete(world, e); } break; case EcsCmdRemove: flecs_remove_id(world, e, id); world->info.cmd.remove_count ++; break; case EcsCmdClone: ecs_clone(world, e, id, cmd->is._1.clone_value); world->info.cmd.other_count ++; break; case EcsCmdSet: flecs_set_id_move(world, dst_stage, e, cmd->id, flecs_itosize(cmd->is._1.size), cmd->is._1.value, kind); world->info.cmd.set_count ++; break; case EcsCmdEmplace: if (merge_to_world) { bool is_new; ecs_emplace_id(world, e, id, &is_new); if (!is_new) { kind = EcsCmdEnsure; } } flecs_set_id_move(world, dst_stage, e, cmd->id, flecs_itosize(cmd->is._1.size), cmd->is._1.value, kind); world->info.cmd.ensure_count ++; break; case EcsCmdEnsure: flecs_set_id_move(world, dst_stage, e, cmd->id, flecs_itosize(cmd->is._1.size), cmd->is._1.value, kind); world->info.cmd.ensure_count ++; break; case EcsCmdModified: flecs_modified_id_if(world, e, id, true); world->info.cmd.modified_count ++; break; case EcsCmdModifiedNoHook: flecs_modified_id_if(world, e, id, false); world->info.cmd.modified_count ++; break; case EcsCmdAddModified: flecs_add_id(world, e, id); flecs_modified_id_if(world, e, id, true); world->info.cmd.set_count ++; break; case EcsCmdDelete: { ecs_delete(world, e); world->info.cmd.delete_count ++; break; } case EcsCmdClear: ecs_clear(world, e); world->info.cmd.clear_count ++; break; case EcsCmdOnDeleteAction: ecs_defer_begin(world); flecs_on_delete(world, id, e, false); ecs_defer_end(world); world->info.cmd.other_count ++; break; case EcsCmdEnable: ecs_enable_id(world, e, id, true); world->info.cmd.other_count ++; break; case EcsCmdDisable: ecs_enable_id(world, e, id, false); world->info.cmd.other_count ++; break; case EcsCmdBulkNew: flecs_flush_bulk_new(world, cmd); world->info.cmd.other_count ++; continue; case EcsCmdPath: { bool keep_alive = true; ecs_make_alive(world, e); if (cmd->id) { if (ecs_is_alive(world, cmd->id)) { ecs_add_pair(world, e, EcsChildOf, cmd->id); } else { ecs_delete(world, e); keep_alive = false; } } if (keep_alive) { ecs_set_name(world, e, cmd->is._1.value); } ecs_os_free(cmd->is._1.value); cmd->is._1.value = NULL; world->info.cmd.other_count ++; break; } case EcsCmdEvent: { ecs_event_desc_t *desc = cmd->is._1.value; ecs_assert(desc != NULL, ECS_INTERNAL_ERROR, NULL); ecs_emit((ecs_world_t*)stage, desc); flecs_free_cmd_event(world, desc); world->info.cmd.event_count ++; break; } case EcsCmdSkip: break; } if (cmd->is._1.value) { flecs_stack_free(cmd->is._1.value, cmd->is._1.size); } } flecs_stack_reset(&commands->stack); ecs_vec_clear(queue); flecs_commands_pop(stage); flecs_table_diff_builder_fini(world, &diff); /* Internal callback for capturing commands, signal queue is done */ if (world->on_commands_active) { world->on_commands_active(stage, NULL, world->on_commands_ctx_active); } } 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); return flecs_ito(uint64_t, atoll(name + 1)); } static ecs_entity_t flecs_get_builtin( const char *name) { if (name[0] == '.' && name[1] == '\0') { return EcsThis; } else if (name[0] == '*' && name[1] == '\0') { return EcsWildcard; } else if (name[0] == '_' && name[1] == '\0') { return EcsAny; } else if (name[0] == '$' && name[1] == '\0') { return EcsVariable; } return 0; } static bool flecs_is_sep( const char **ptr, const char *sep) { ecs_size_t len = ecs_os_strlen(sep); if (!ecs_os_strncmp(*ptr, sep, len)) { *ptr += len; return true; } else { return false; } } static const char* flecs_path_elem( const char *path, const char *sep, char **buffer_out, ecs_size_t *size_out) { char *buffer = NULL; if (buffer_out) { buffer = *buffer_out; } const char *ptr; char ch; int32_t template_nesting = 0; int32_t pos = 0; ecs_size_t size = size_out ? *size_out : 0; for (ptr = path; (ch = *ptr); ptr ++) { if (ch == '<') { template_nesting ++; } else if (ch == '>') { template_nesting --; } else if (ch == '\\') { ptr ++; if (buffer) { buffer[pos] = ptr[0]; } pos ++; continue; } ecs_check(template_nesting >= 0, ECS_INVALID_PARAMETER, path); if (!template_nesting && flecs_is_sep(&ptr, sep)) { break; } if (buffer) { if (pos == (size - 1)) { if (size == ECS_NAME_BUFFER_LENGTH) { /* stack buffer */ char *new_buffer = ecs_os_malloc(size * 2 + 1); ecs_os_memcpy(new_buffer, buffer, size); buffer = new_buffer; } else { /* heap buffer */ buffer = ecs_os_realloc(buffer, size * 2 + 1); } size *= 2; } buffer[pos] = ch; } pos ++; } if (buffer) { buffer[pos] = '\0'; *buffer_out = buffer; *size_out = size; } if (pos) { return ptr; } else { return NULL; } error: return NULL; } static bool flecs_is_root_path( const char *path, const char *prefix) { if (prefix) { return !ecs_os_strncmp(path, prefix, ecs_os_strlen(prefix)); } else { return false; } } static ecs_entity_t flecs_get_parent_from_path( const ecs_world_t *world, ecs_entity_t parent, const char **path_ptr, const char *sep, const char *prefix, bool new_entity) { bool start_from_root = false; const char *path = *path_ptr; if (flecs_is_root_path(path, prefix)) { path += ecs_os_strlen(prefix); parent = 0; start_from_root = true; } if (path[0] == '#') { parent = flecs_name_to_id(path); path ++; while (path[0] && isdigit(path[0])) { path ++; /* Skip id part of path */ } /* Skip next separator so that the returned path points to the next * name element. */ ecs_size_t sep_len = ecs_os_strlen(sep); if (!ecs_os_strncmp(path, sep, ecs_os_strlen(sep))) { path += sep_len; } start_from_root = true; } if (!start_from_root && !parent && new_entity) { parent = ecs_get_scope(world); } *path_ptr = path; return parent; } static void flecs_on_set_symbol(ecs_iter_t *it) { EcsIdentifier *n = ecs_field(it, EcsIdentifier, 0); ecs_world_t *world = it->real_world; int i; for (i = 0; i < it->count; i ++) { ecs_entity_t e = it->entities[i]; flecs_name_index_ensure( &world->symbols, e, n[i].value, n[i].length, n[i].hash); } } void flecs_bootstrap_hierarchy(ecs_world_t *world) { ecs_observer(world, { .entity = ecs_entity(world, { .parent = EcsFlecsInternals }), .query.terms[0] = { .id = ecs_pair(ecs_id(EcsIdentifier), EcsSymbol) }, .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); const ecs_world_t *stage = world; world = ecs_get_world(world); ecs_entity_t e = flecs_get_builtin(path); if (e) { return e; } e = flecs_name_index_find(&world->aliases, path, 0, 0); if (e) { return e; } char buff[ECS_NAME_BUFFER_LENGTH], *elem = buff; const char *ptr; int32_t size = ECS_NAME_BUFFER_LENGTH; ecs_entity_t cur; bool lookup_path_search = false; const ecs_entity_t *lookup_path = ecs_get_lookup_path(stage); const ecs_entity_t *lookup_path_cur = lookup_path; while (lookup_path_cur && *lookup_path_cur) { lookup_path_cur ++; } if (!sep) { sep = "."; } parent = flecs_get_parent_from_path( stage, parent, &path, sep, prefix, true); if (parent && !(parent = ecs_get_alive(world, parent))) { return 0; } if (!path[0]) { return parent; } if (!sep[0]) { return ecs_lookup_child(world, parent, path); } retry: cur = parent; ptr = path; while ((ptr = flecs_path_elem(ptr, sep, &elem, &size))) { cur = ecs_lookup_child(world, cur, elem); if (!cur) { goto tail; } } tail: if (!cur && recursive) { if (!lookup_path_search) { if (parent) { parent = ecs_get_target(world, parent, EcsChildOf, 0); goto retry; } else { lookup_path_search = true; } } if (lookup_path_search) { if (lookup_path_cur != lookup_path) { lookup_path_cur --; parent = lookup_path_cur[0]; goto retry; } } } if (elem != buff) { ecs_os_free(elem); } return cur; error: return 0; } ecs_entity_t ecs_set_scope( ecs_world_t *world, ecs_entity_t scope) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); ecs_entity_t cur = stage->scope; stage->scope = scope; return cur; error: return 0; } ecs_entity_t ecs_get_scope( const ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); const ecs_stage_t *stage = flecs_stage_from_readonly_world(world); return stage->scope; error: return 0; } ecs_entity_t* ecs_set_lookup_path( ecs_world_t *world, const ecs_entity_t *lookup_path) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); /* Safe: application owns lookup path */ ecs_entity_t *cur = ECS_CONST_CAST(ecs_entity_t*, stage->lookup_path); stage->lookup_path = lookup_path; return cur; error: return NULL; } ecs_entity_t* ecs_get_lookup_path( const ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); const ecs_stage_t *stage = flecs_stage_from_readonly_world(world); /* Safe: application owns lookup path */ return ECS_CONST_CAST(ecs_entity_t*, stage->lookup_path); error: return NULL; } const char* ecs_set_name_prefix( ecs_world_t *world, const char *prefix) { flecs_poly_assert(world, ecs_world_t); const char *old_prefix = world->info.name_prefix; world->info.name_prefix = prefix; return old_prefix; } static void flecs_add_path( ecs_world_t *world, bool defer_suspend, ecs_entity_t parent, ecs_entity_t entity, const char *name) { ecs_suspend_readonly_state_t srs; ecs_world_t *real_world = NULL; if (defer_suspend) { real_world = flecs_suspend_readonly(world, &srs); ecs_assert(real_world != NULL, ECS_INTERNAL_ERROR, NULL); } if (parent) { ecs_add_pair(world, entity, EcsChildOf, parent); } ecs_assert(name[0] != '#', ECS_INVALID_PARAMETER, "path should not contain identifier with #"); ecs_set_name(world, entity, name); if (defer_suspend) { ecs_stage_t *stage = flecs_stage_from_world(&world); flecs_resume_readonly(real_world, &srs); flecs_defer_path(stage, parent, entity, name); } } ecs_entity_t ecs_add_path_w_sep( ecs_world_t *world, ecs_entity_t entity, ecs_entity_t parent, const char *path, const char *sep, const char *prefix) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); if (!sep) { sep = "."; } if (!path) { if (!entity) { entity = ecs_new(world); } if (parent) { ecs_add_pair(world, entity, EcsChildOf, entity); } return entity; } bool root_path = flecs_is_root_path(path, prefix); parent = flecs_get_parent_from_path( world, parent, &path, sep, prefix, !entity); char buff[ECS_NAME_BUFFER_LENGTH]; const char *ptr = path; char *elem = buff; int32_t size = ECS_NAME_BUFFER_LENGTH; /* If we're in deferred/readonly mode suspend it, so that the name index is * immediately updated. Without this, we could create multiple entities for * the same name in a single command queue. */ bool suspend_defer = ecs_is_deferred(world) && !(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: 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); } /* Treat as new event as this could invoke observers again for * different tables. */ it->event_cur = ++ world->event_id; int32_t ider_i; for (ider_i = 0; ider_i < ider_count; ider_i ++) { ecs_event_id_record_t *ider = iders[ider_i]; flecs_observers_invoke(world, &ider->up, it, table, trav); if (!owned) { /* Owned takes precedence */ flecs_observers_invoke(world, &ider->self_up, it, table, trav); } } if (!table->_->traversable_count) { continue; } 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); 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) { 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; } /* Id has the same relationship, traverse to find ids for forwarding */ if (ECS_PAIR_FIRST(id) == trav || ECS_PAIR_FIRST(id) == EcsIsA) { ecs_table_t **t = ecs_vec_append_t(&world->allocator, stack, ecs_table_t*); t[0] = tgt_table; ecs_reachable_cache_t *idr_rc = &idr->reachable; if (idr_rc->current == idr_rc->generation) { /* Cache hit, use cached ids to prevent traversing the same * hierarchy multiple times. This especially speeds up code * where (deep) hierarchies are created. */ if (ecs_should_log_3()) { char *idstr = ecs_id_str(world, id); ecs_dbg_3("forward cached for %s", idstr); ecs_os_free(idstr); } ecs_log_push_3(); flecs_emit_forward_cached_ids(world, er, er_onset, emit_ids, it, table, idr_rc, reachable_ids, stack, trav); ecs_log_pop_3(); } else { /* Cache is dirty, traverse upwards */ do { flecs_emit_forward_up(world, er, er_onset, emit_ids, it, table, idr, stack, reachable_ids); if (++i >= id_count) { break; } id = ids[i]; if (ECS_PAIR_FIRST(id) != trav) { break; } } while (true); } ecs_vec_remove_last(stack); continue; } int32_t stack_at = flecs_emit_stack_at(stack, idr); if (parent_revalidate && (stack_at == (stack_count - 1))) { /* If parent id record needs to be revalidated, add id */ ecs_reachable_elem_t *elem = ecs_vec_append_t(a, &rc->ids, ecs_reachable_elem_t); elem->tr = tgt_tr; elem->record = tgt_record; elem->src = tgt; elem->id = idr->id; #ifndef NDEBUG elem->table = tgt_table; #endif } /* Skip id if it's masked by a lower table in the tree */ if (stack_at != stack_count) { continue; } flecs_emit_forward_and_cache_id(world, er, er_onset, emit_ids, it, table, idr, tgt, tgt_record, tgt_table, tgt_tr, i, reachable_ids, trav); } if (parent_revalidate) { /* If this is not the current cache being updated, but it's marked * as out of date, use intermediate results to populate cache. */ int32_t rc_parent_offset = ecs_vec_count(&rc->ids); /* Only add ids that were added for this table */ int32_t count = ecs_vec_count(reachable_ids); count -= rc_child_offset; /* Append ids to any ids that already were added /*/ if (count) { ecs_vec_grow_t(a, &rc->ids, ecs_reachable_elem_t, count); ecs_reachable_elem_t *dst = ecs_vec_get_t(&rc->ids, ecs_reachable_elem_t, rc_parent_offset); ecs_reachable_elem_t *src = ecs_vec_get_t(reachable_ids, ecs_reachable_elem_t, rc_child_offset); ecs_os_memcpy_n(dst, src, ecs_reachable_elem_t, count); } rc->current = rc->generation; if (ecs_should_log_3()) { char *idstr = ecs_id_str(world, tgt_idr->id); ecs_dbg_3("cache revalidated for %s:", idstr); ecs_os_free(idstr); flecs_emit_dump_cache(world, &rc->ids); } } ecs_log_pop_3(); } static void flecs_emit_forward_up( ecs_world_t *world, const ecs_event_record_t *er, const ecs_event_record_t *er_onset, const ecs_type_t *emit_ids, ecs_iter_t *it, ecs_table_t *table, ecs_id_record_t *idr, ecs_vec_t *stack, ecs_vec_t *reachable_ids) { ecs_id_t id = idr->id; ecs_entity_t tgt = ECS_PAIR_SECOND(id); tgt = flecs_entities_get_alive(world, tgt); ecs_assert(tgt != 0, ECS_INTERNAL_ERROR, NULL); ecs_record_t *tgt_record = flecs_entities_try(world, tgt); ecs_table_t *tgt_table; if (!tgt_record || !(tgt_table = tgt_record->table)) { return; } flecs_emit_forward_table_up(world, er, er_onset, emit_ids, it, table, tgt, tgt_table, tgt_record, idr, stack, reachable_ids); } static void flecs_emit_forward( ecs_world_t *world, const ecs_event_record_t *er, const ecs_event_record_t *er_onset, const ecs_type_t *emit_ids, ecs_iter_t *it, ecs_table_t *table, ecs_id_record_t *idr) { ecs_reachable_cache_t *rc = &idr->reachable; if (rc->current != rc->generation) { /* Cache miss, iterate the tree to find ids to forward */ if (ecs_should_log_3()) { char *idstr = ecs_id_str(world, idr->id); ecs_dbg_3("reachable cache miss for %s", idstr); ecs_os_free(idstr); } ecs_log_push_3(); ecs_vec_t stack; ecs_vec_init_t(&world->allocator, &stack, ecs_table_t*, 0); ecs_vec_reset_t(&world->allocator, &rc->ids, ecs_reachable_elem_t); flecs_emit_forward_up(world, er, er_onset, emit_ids, it, table, idr, &stack, &rc->ids); it->sources[0] = 0; ecs_vec_fini_t(&world->allocator, &stack, ecs_table_t*); if (it->event == EcsOnAdd || it->event == EcsOnRemove) { /* Only OnAdd/OnRemove events can validate top-level cache, which * is for the id for which the event is emitted. * The reason for this is that we don't want to validate the cache * while the administration for the mutated entity isn't up to * date yet. */ rc->current = rc->generation; } if (ecs_should_log_3()) { ecs_dbg_3("cache after rebuild:"); flecs_emit_dump_cache(world, &rc->ids); } ecs_log_pop_3(); } else { /* Cache hit, use cached values instead of walking the tree */ if (ecs_should_log_3()) { char *idstr = ecs_id_str(world, idr->id); ecs_dbg_3("reachable cache hit for %s", idstr); ecs_os_free(idstr); flecs_emit_dump_cache(world, &rc->ids); } ecs_entity_t trav = ECS_PAIR_FIRST(idr->id); ecs_reachable_elem_t *elems = ecs_vec_first_t(&rc->ids, ecs_reachable_elem_t); int32_t i, count = ecs_vec_count(&rc->ids); for (i = 0; i < count; i ++) { ecs_reachable_elem_t *elem = &elems[i]; const ecs_table_record_t *tr = elem->tr; ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_id_record_t *rc_idr = (ecs_id_record_t*)tr->hdr.cache; ecs_record_t *r = elem->record; ecs_assert(rc_idr->id == elem->id, ECS_INTERNAL_ERROR, NULL); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(flecs_entities_get(world, elem->src) == r, ECS_INTERNAL_ERROR, NULL); ecs_dbg_assert(r->table == elem->table, ECS_INTERNAL_ERROR, NULL); 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; 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 == EcsOnTableFill) { return EcsIdHasOnTableFill; } if (e == EcsOnTableEmpty) { return EcsIdHasOnTableEmpty; } if (e == EcsOnTableCreate) { return EcsIdHasOnTableCreate; } if (e == EcsOnTableDelete) { return EcsIdHasOnTableDelete; } if (e == EcsWildcard) { return EcsIdHasOnAdd|EcsIdHasOnRemove|EcsIdHasOnSet| EcsIdHasOnTableFill|EcsIdHasOnTableEmpty| EcsIdHasOnTableCreate|EcsIdHasOnTableDelete; } return 0; } static void flecs_inc_observer_count( ecs_world_t *world, ecs_entity_t event, ecs_event_record_t *evt, ecs_id_t id, int32_t value) { ecs_event_id_record_t *idt = flecs_event_id_record_ensure(world, evt, id); ecs_assert(idt != NULL, ECS_INTERNAL_ERROR, NULL); int32_t result = idt->observer_count += value; if (result == 1) { /* Notify framework that there are observers for the event/id. This * allows parts of the code to skip event evaluation early */ flecs_notify_tables(world, id, &(ecs_table_event_t){ .kind = EcsTableTriggersForId, .event = event }); ecs_flags32_t flags = flecs_id_flag_for_event(event); if (flags) { ecs_id_record_t *idr = flecs_id_record_get(world, id); if (idr) { idr->flags |= flags; } } } else if (result == 0) { /* Ditto, but the reverse */ flecs_notify_tables(world, id, &(ecs_table_event_t){ .kind = EcsTableNoTriggersForId, .event = event }); ecs_flags32_t flags = flecs_id_flag_for_event(event); if (flags) { ecs_id_record_t *idr = flecs_id_record_get(world, id); if (idr) { idr->flags &= ~flags; } } flecs_event_id_record_remove(evt, id); ecs_os_free(idt); } } static ecs_id_t flecs_observer_id( ecs_id_t id) { if (ECS_IS_PAIR(id)) { if (ECS_PAIR_FIRST(id) == EcsAny) { id = ecs_pair(EcsWildcard, ECS_PAIR_SECOND(id)); } if (ECS_PAIR_SECOND(id) == EcsAny) { id = ecs_pair(ECS_PAIR_FIRST(id), EcsWildcard); } } return id; } static void flecs_register_observer_for_id( ecs_world_t *world, ecs_observable_t *observable, ecs_observer_t *o, size_t offset) { ecs_observer_impl_t *impl = flecs_observer_impl(o); ecs_id_t term_id = flecs_observer_id(impl->register_id); ecs_term_t *term = &o->query->terms[0]; ecs_entity_t trav = term->trav; int i; for (i = 0; i < o->event_count; i ++) { ecs_entity_t event = flecs_get_observer_event( term, o->events[i]); /* Get observers for event */ ecs_event_record_t *er = flecs_event_record_ensure(observable, event); ecs_assert(er != NULL, ECS_INTERNAL_ERROR, NULL); /* Get observers for (component) id for event */ ecs_event_id_record_t *idt = flecs_event_id_record_ensure( world, er, term_id); ecs_assert(idt != NULL, ECS_INTERNAL_ERROR, NULL); ecs_map_t *observers = ECS_OFFSET(idt, offset); ecs_map_init_w_params_if(observers, &world->allocators.ptr); ecs_map_insert_ptr(observers, impl->id, o); flecs_inc_observer_count(world, event, er, term_id, 1); if (trav) { flecs_inc_observer_count(world, event, er, ecs_pair(trav, EcsWildcard), 1); } } } static void flecs_uni_observer_register( ecs_world_t *world, ecs_observable_t *observable, ecs_observer_t *o) { ecs_term_t *term = &o->query->terms[0]; ecs_flags64_t flags = ECS_TERM_REF_FLAGS(&term->src); if ((flags & (EcsSelf|EcsUp)) == (EcsSelf|EcsUp)) { flecs_register_observer_for_id(world, observable, o, offsetof(ecs_event_id_record_t, self_up)); } else if (flags & EcsSelf) { flecs_register_observer_for_id(world, observable, o, offsetof(ecs_event_id_record_t, self)); } else if (flags & EcsUp) { ecs_assert(term->trav != 0, ECS_INTERNAL_ERROR, NULL); flecs_register_observer_for_id(world, observable, o, offsetof(ecs_event_id_record_t, up)); } } static void flecs_unregister_observer_for_id( ecs_world_t *world, ecs_observable_t *observable, ecs_observer_t *o, size_t offset) { ecs_observer_impl_t *impl = flecs_observer_impl(o); ecs_id_t term_id = flecs_observer_id(impl->register_id); ecs_term_t *term = &o->query->terms[0]; ecs_entity_t trav = term->trav; int i; for (i = 0; i < o->event_count; i ++) { ecs_entity_t event = flecs_get_observer_event( term, o->events[i]); /* Get observers for event */ ecs_event_record_t *er = flecs_event_record_get(observable, event); ecs_assert(er != NULL, ECS_INTERNAL_ERROR, NULL); /* Get observers for (component) id */ ecs_event_id_record_t *idt = flecs_event_id_record_get(er, term_id); ecs_assert(idt != NULL, ECS_INTERNAL_ERROR, NULL); ecs_map_t *id_observers = ECS_OFFSET(idt, offset); ecs_map_remove(id_observers, impl->id); if (!ecs_map_count(id_observers)) { ecs_map_fini(id_observers); } flecs_inc_observer_count(world, event, er, term_id, -1); if (trav) { flecs_inc_observer_count(world, event, er, ecs_pair(trav, EcsWildcard), -1); } } } static void flecs_unregister_observer( ecs_world_t *world, ecs_observable_t *observable, ecs_observer_t *o) { ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL); if (o->query->term_count == 0) { return; } ecs_term_t *term = &o->query->terms[0]; ecs_flags64_t flags = ECS_TERM_REF_FLAGS(&term->src); if ((flags & (EcsSelf|EcsUp)) == (EcsSelf|EcsUp)) { flecs_unregister_observer_for_id(world, observable, o, offsetof(ecs_event_id_record_t, self_up)); } else if (flags & EcsSelf) { flecs_unregister_observer_for_id(world, observable, o, offsetof(ecs_event_id_record_t, self)); } else if (flags & EcsUp) { flecs_unregister_observer_for_id(world, observable, o, offsetof(ecs_event_id_record_t, up)); } } static bool flecs_ignore_observer( ecs_observer_t *o, ecs_table_t *table, ecs_iter_t *it) { ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_observer_impl_t *impl = flecs_observer_impl(o); int32_t *last_event_id = impl->last_event_id; if (last_event_id && last_event_id[0] == it->event_cur) { return true; } if (impl->flags & (EcsObserverIsDisabled|EcsObserverIsParentDisabled)) { return true; } ecs_flags32_t table_flags = table->flags, query_flags = o->query->flags; bool result = (table_flags & EcsTableIsPrefab) && !(query_flags & EcsQueryMatchPrefab); result = result || ((table_flags & EcsTableIsDisabled) && !(query_flags & EcsQueryMatchDisabled)); return result; } static 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; } impl->last_event_id[0] = world->event_id; 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; } } /* 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_run_aperiodic(world, EcsAperiodicEmptyTables); ecs_defer_begin(world); /* If yield existing is enabled, invoke for each thing that matches * the event, if the event is iterable. */ int i, count = o->event_count; for (i = 0; i < count; i ++) { ecs_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 && e != EcsOnTableEmpty && e != EcsOnTableFill) { 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; ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, "ecs_observer_desc_t was not initialized to zero"); ecs_check(!(world->flags & EcsWorldFini), ECS_INVALID_OPERATION, "cannot create observer while world is being deleted"); entity = desc->entity; if (!entity) { entity = ecs_entity(world, {0}); } EcsPoly *poly = flecs_poly_bind(world, entity, ecs_observer_t); if (!poly->poly) { ecs_observer_t *o = flecs_observer_init(world, entity, desc); ecs_assert(o->entity == entity, ECS_INTERNAL_ERROR, NULL); poly->poly = o; if (ecs_get_name(world, entity)) { ecs_trace("#[green]observer#[reset] %s created", ecs_get_name(world, entity)); } } else { flecs_poly_assert(poly->poly, ecs_observer_t); ecs_observer_t *o = (ecs_observer_t*)poly->poly; if (o->ctx_free) { if (o->ctx && o->ctx != desc->ctx) { o->ctx_free(o->ctx); } } if (o->callback_ctx_free) { if (o->callback_ctx && o->callback_ctx != desc->callback_ctx) { o->callback_ctx_free(o->callback_ctx); o->callback_ctx_free = NULL; o->callback_ctx = NULL; } } if (o->run_ctx_free) { if (o->run_ctx && o->run_ctx != desc->run_ctx) { o->run_ctx_free(o->run_ctx); o->run_ctx_free = NULL; o->run_ctx = NULL; } } if (desc->run) { o->run = desc->run; if (!desc->callback) { o->callback = NULL; } } if (desc->callback) { o->callback = desc->callback; if (!desc->run) { o->run = NULL; } } if (desc->ctx) { o->ctx = desc->ctx; } if (desc->callback_ctx) { o->callback_ctx = desc->callback_ctx; } if (desc->run_ctx) { o->run_ctx = desc->run_ctx; } if (desc->ctx_free) { o->ctx_free = desc->ctx_free; } if (desc->callback_ctx_free) { o->callback_ctx_free = desc->callback_ctx_free; } if (desc->run_ctx_free) { o->run_ctx_free = desc->run_ctx_free; } } flecs_poly_modified(world, entity, ecs_observer_t); return entity; error: if (entity) { ecs_delete(world, entity); } return 0; } const ecs_observer_t* ecs_observer_get( const ecs_world_t *world, ecs_entity_t observer) { return flecs_poly_get(world, observer, ecs_observer_t); } void flecs_observer_fini( ecs_observer_t *o) { ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(o->query != NULL, ECS_INTERNAL_ERROR, NULL); ecs_world_t *world = o->query->world; ecs_observer_impl_t *impl = flecs_observer_impl(o); if (impl->flags & 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->next_for_entity = 0; cmd->entry = NULL; cmd->system = stage->system; return cmd; } static ecs_cmd_t* flecs_cmd_new_batched( ecs_stage_t *stage, ecs_entity_t e) { ecs_vec_t *cmds = &stage->cmd->queue; ecs_cmd_entry_t *entry = flecs_sparse_get_any_t( &stage->cmd->entries, ecs_cmd_entry_t, e); int32_t cur = ecs_vec_count(cmds); ecs_cmd_t *cmd = flecs_cmd_new(stage); if (entry) { if (entry->first == -1) { /* Existing but invalidated entry */ entry->first = cur; cmd->entry = entry; } else { int32_t last = entry->last; ecs_cmd_t *arr = ecs_vec_first_t(cmds, ecs_cmd_t); ecs_assert(arr[last].entity == e, ECS_INTERNAL_ERROR, NULL); ecs_cmd_t *last_op = &arr[last]; last_op->next_for_entity = cur; if (last == entry->first) { /* Flip sign bit so flush logic can tell which command * is the first for an entity */ last_op->next_for_entity *= -1; } } } else { cmd->entry = entry = flecs_sparse_ensure_fast_t( &stage->cmd->entries, ecs_cmd_entry_t, e); entry->first = cur; } entry->last = cur; return cmd; } static void flecs_stage_merge( ecs_world_t *world) { bool is_stage = flecs_poly_is(world, ecs_stage_t); ecs_stage_t *stage = flecs_stage_from_world(&world); bool measure_frame_time = ECS_BIT_IS_SET(world->flags, EcsWorldMeasureFrameTime); ecs_time_t t_start = {0}; if (measure_frame_time) { ecs_os_get_time(&t_start); } ecs_dbg_3("#[magenta]merge"); ecs_log_push_3(); if (is_stage) { /* Check for consistency if force_merge is enabled. In practice this * function will never get called with force_merge disabled for just * a single stage. */ ecs_assert(stage->defer == 1, ECS_INVALID_OPERATION, "mismatching defer_begin/defer_end detected"); flecs_defer_end(world, stage); } else { /* Merge stages. Only merge if the stage has auto_merging turned on, or * if this is a forced merge (like when ecs_merge is called) */ int32_t i, count = ecs_get_stage_count(world); for (i = 0; i < count; i ++) { ecs_stage_t *s = (ecs_stage_t*)ecs_get_stage(world, i); flecs_poly_assert(s, ecs_stage_t); flecs_defer_end(world, s); } } flecs_eval_component_monitors(world); if (measure_frame_time) { world->info.merge_time_total += (ecs_ftime_t)ecs_time_measure(&t_start); } world->info.merge_count_total ++; /* If stage is unmanaged, deferring is always enabled */ if (stage->id == -1) { flecs_defer_begin(world, stage); } ecs_log_pop_3(); } bool flecs_defer_begin( ecs_world_t *world, ecs_stage_t *stage) { flecs_poly_assert(world, ecs_world_t); flecs_poly_assert(stage, ecs_stage_t); (void)world; if (stage->defer < 0) return false; return (++ stage->defer) == 1; } bool flecs_defer_cmd( ecs_stage_t *stage) { if (stage->defer) { return (stage->defer > 0); } stage->defer ++; return false; } bool flecs_defer_modified( ecs_stage_t *stage, ecs_entity_t entity, ecs_id_t id) { if (flecs_defer_cmd(stage)) { ecs_cmd_t *cmd = flecs_cmd_new_batched(stage, entity); if (cmd) { cmd->kind = EcsCmdModified; cmd->id = id; cmd->entity = entity; } return true; } return false; } bool flecs_defer_clone( ecs_stage_t *stage, ecs_entity_t entity, ecs_entity_t src, bool clone_value) { if (flecs_defer_cmd(stage)) { ecs_cmd_t *cmd = flecs_cmd_new(stage); cmd->kind = EcsCmdClone; cmd->id = src; cmd->entity = entity; cmd->is._1.clone_value = clone_value; return true; } return false; } bool flecs_defer_path( ecs_stage_t *stage, ecs_entity_t parent, ecs_entity_t entity, const char *name) { if (stage->defer > 0) { ecs_cmd_t *cmd = flecs_cmd_new(stage); cmd->kind = EcsCmdPath; cmd->entity = entity; cmd->id = parent; cmd->is._1.value = ecs_os_strdup(name); return true; } return false; } bool flecs_defer_delete( ecs_stage_t *stage, ecs_entity_t entity) { if (flecs_defer_cmd(stage)) { ecs_cmd_t *cmd = flecs_cmd_new(stage); cmd->kind = EcsCmdDelete; cmd->entity = entity; return true; } return false; } bool flecs_defer_clear( ecs_stage_t *stage, ecs_entity_t entity) { if (flecs_defer_cmd(stage)) { ecs_cmd_t *cmd = flecs_cmd_new_batched(stage, entity); cmd->kind = EcsCmdClear; cmd->entity = entity; return true; } return false; } bool flecs_defer_on_delete_action( ecs_stage_t *stage, ecs_id_t id, ecs_entity_t action) { if (flecs_defer_cmd(stage)) { ecs_cmd_t *cmd = flecs_cmd_new(stage); cmd->kind = EcsCmdOnDeleteAction; cmd->id = id; cmd->entity = action; return true; } return false; } bool flecs_defer_enable( ecs_stage_t *stage, ecs_entity_t entity, ecs_id_t id, bool enable) { if (flecs_defer_cmd(stage)) { ecs_cmd_t *cmd = flecs_cmd_new(stage); cmd->kind = enable ? EcsCmdEnable : EcsCmdDisable; cmd->entity = entity; cmd->id = id; return true; } return false; } bool flecs_defer_bulk_new( ecs_world_t *world, ecs_stage_t *stage, int32_t count, ecs_id_t id, const ecs_entity_t **ids_out) { if (flecs_defer_cmd(stage)) { ecs_entity_t *ids = ecs_os_malloc(count * ECS_SIZEOF(ecs_entity_t)); /* Use ecs_new_id as this is thread safe */ int i; for (i = 0; i < count; i ++) { ids[i] = ecs_new(world); } *ids_out = ids; /* Store data in op */ ecs_cmd_t *cmd = flecs_cmd_new(stage); cmd->kind = EcsCmdBulkNew; cmd->id = id; cmd->is._n.entities = ids; cmd->is._n.count = count; cmd->entity = 0; return true; } return false; } bool flecs_defer_add( ecs_stage_t *stage, ecs_entity_t entity, ecs_id_t id) { if (flecs_defer_cmd(stage)) { ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); ecs_cmd_t *cmd = flecs_cmd_new_batched(stage, entity); cmd->kind = EcsCmdAdd; cmd->id = id; cmd->entity = entity; return true; } return false; } bool flecs_defer_remove( ecs_stage_t *stage, ecs_entity_t entity, ecs_id_t id) { if (flecs_defer_cmd(stage)) { ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); ecs_cmd_t *cmd = flecs_cmd_new_batched(stage, entity); cmd->kind = EcsCmdRemove; cmd->id = id; cmd->entity = entity; return true; } return false; } void* flecs_defer_set( ecs_world_t *world, ecs_stage_t *stage, ecs_cmd_kind_t cmd_kind, ecs_entity_t entity, ecs_id_t id, ecs_size_t size, void *value, bool *is_new) { ecs_cmd_t *cmd = flecs_cmd_new_batched(stage, entity); /* Find type info for id */ const ecs_type_info_t *ti = NULL; ecs_id_record_t *idr = flecs_id_record_get(world, id); if (!idr) { /* If idr doesn't exist yet, create it but only if the * application is not multithreaded. */ if (world->flags & EcsWorldMultiThreaded) { ti = ecs_get_type_info(world, id); } else { /* When not in multi threaded mode, it's safe to find or * create the id record. */ idr = flecs_id_record_ensure(world, id); ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); /* Get type_info from id record. We could have called * ecs_get_type_info directly, but since this function can be * expensive for pairs, creating the id record ensures we can * find the type_info quickly for subsequent operations. */ ti = idr->type_info; } } else { ti = idr->type_info; } /* If the id isn't associated with a type, we can't set anything */ ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "provided component is not a type"); /* Make sure the size of the value equals the type size */ ecs_assert(!size || size == ti->size, ECS_INVALID_PARAMETER, "mismatching size specified for component in ensure/emplace/set"); size = ti->size; /* Find existing component. Make sure it's owned, so that we won't use the * component of a prefab. */ void *existing = NULL; ecs_table_t *table = NULL; if (idr) { /* Entity can only have existing component if id record exists */ ecs_record_t *r = flecs_entities_get(world, entity); table = r->table; if (r && table) { const ecs_table_record_t *tr = flecs_id_record_get_table( idr, table); if (tr) { if (tr->column != -1) { /* Entity has the component */ 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(&stage->cmd->queue) == 0, ECS_INTERNAL_ERROR, NULL); flecs_stack_fini(&cmd->stack); ecs_vec_fini_t(&stage->allocator, &cmd->queue, ecs_cmd_t); flecs_sparse_fini(&cmd->entries); } void flecs_commands_push( ecs_stage_t *stage) { int32_t sp = ++ stage->cmd_sp; ecs_assert(sp < ECS_MAX_DEFER_STACK, ECS_INTERNAL_ERROR, NULL); stage->cmd = &stage->cmd_stack[sp]; } void flecs_commands_pop( ecs_stage_t *stage) { int32_t sp = -- stage->cmd_sp; ecs_assert(sp >= 0, ECS_INTERNAL_ERROR, NULL); stage->cmd = &stage->cmd_stack[sp]; } ecs_entity_t flecs_stage_set_system( ecs_stage_t *stage, ecs_entity_t system) { ecs_entity_t old = stage->system; stage->system = system; return old; } static ecs_stage_t* flecs_stage_new( ecs_world_t *world) { flecs_poly_assert(world, ecs_world_t); ecs_stage_t *stage = ecs_os_calloc(sizeof(ecs_stage_t)); flecs_poly_init(stage, ecs_stage_t); stage->world = world; stage->thread_ctx = world; flecs_stack_init(&stage->allocators.iter_stack); flecs_stack_init(&stage->allocators.deser_stack); flecs_allocator_init(&stage->allocator); flecs_ballocator_init_n(&stage->allocators.cmd_entry_chunk, ecs_cmd_entry_t, FLECS_SPARSE_PAGE_SIZE); flecs_ballocator_init_t(&stage->allocators.query_impl, ecs_query_impl_t); flecs_ballocator_init_t(&stage->allocators.query_cache, ecs_query_cache_t); ecs_allocator_t *a = &stage->allocator; ecs_vec_init_t(a, &stage->post_frame_actions, ecs_action_elem_t, 0); int32_t i; for (i = 0; i < ECS_MAX_DEFER_STACK; i ++) { flecs_commands_init(stage, &stage->cmd_stack[i]); } stage->cmd = &stage->cmd_stack[0]; return stage; } static void flecs_stage_free( ecs_world_t *world, ecs_stage_t *stage) { (void)world; flecs_poly_assert(world, ecs_world_t); flecs_poly_assert(stage, ecs_stage_t); flecs_poly_fini(stage, ecs_stage_t); ecs_allocator_t *a = &stage->allocator; ecs_vec_fini_t(a, &stage->post_frame_actions, ecs_action_elem_t); ecs_vec_fini(NULL, &stage->variables, 0); ecs_vec_fini(NULL, &stage->operations, 0); int32_t i; for (i = 0; i < ECS_MAX_DEFER_STACK; i ++) { flecs_commands_fini(stage, &stage->cmd_stack[i]); } flecs_stack_fini(&stage->allocators.iter_stack); flecs_stack_fini(&stage->allocators.deser_stack); flecs_ballocator_fini(&stage->allocators.cmd_entry_chunk); flecs_ballocator_fini(&stage->allocators.query_impl); flecs_ballocator_fini(&stage->allocators.query_cache); flecs_allocator_fini(&stage->allocator); ecs_os_free(stage); } ecs_allocator_t* flecs_stage_get_allocator( ecs_world_t *world) { ecs_stage_t *stage = flecs_stage_from_world( ECS_CONST_CAST(ecs_world_t**, &world)); return &stage->allocator; } ecs_stack_t* flecs_stage_get_stack_allocator( ecs_world_t *world) { ecs_stage_t *stage = flecs_stage_from_world( ECS_CONST_CAST(ecs_world_t**, &world)); return &stage->allocators.iter_stack; } ecs_world_t* ecs_stage_new( ecs_world_t *world) { ecs_stage_t *stage = flecs_stage_new(world); stage->id = -1; flecs_defer_begin(world, stage); return (ecs_world_t*)stage; } void ecs_stage_free( ecs_world_t *world) { flecs_poly_assert(world, ecs_stage_t); ecs_stage_t *stage = (ecs_stage_t*)world; ecs_check(stage->id == -1, ECS_INVALID_PARAMETER, "cannot free stage that's owned by world"); flecs_stage_free(stage->world, stage); error: return; } void ecs_set_stage_count( ecs_world_t *world, int32_t stage_count) { flecs_poly_assert(world, ecs_world_t); /* World must have at least one default stage */ ecs_assert(stage_count >= 1 || (world->flags & EcsWorldFini), ECS_INTERNAL_ERROR, NULL); const ecs_entity_t *lookup_path = NULL; if (world->stage_count >= 1) { lookup_path = world->stages[0]->lookup_path; } int32_t i, count = world->stage_count; if (stage_count < count) { for (i = stage_count; i < count; i ++) { /* If stage contains a thread handle, ecs_set_threads was used to * create the stages. ecs_set_threads and ecs_set_stage_count should * not be mixed. */ ecs_stage_t *stage = world->stages[i]; flecs_poly_assert(stage, ecs_stage_t); ecs_check(stage->thread == 0, ECS_INVALID_OPERATION, "cannot mix using set_stage_count and set_threads"); flecs_stage_free(world, stage); } } if (stage_count) { world->stages = ecs_os_realloc_n( world->stages, ecs_stage_t*, stage_count); for (i = count; i < stage_count; i ++) { ecs_stage_t *stage = world->stages[i] = flecs_stage_new(world); stage->id = i; /* Set thread_ctx to stage, as this stage might be used in a * multithreaded context */ stage->thread_ctx = (ecs_world_t*)stage; stage->thread = 0; } } else { /* Set to NULL to prevent double frees */ ecs_os_free(world->stages); world->stages = NULL; } /* Regardless of whether the stage was just initialized or not, when the * ecs_set_stage_count function is called, all stages inherit the auto_merge * property from the world */ for (i = 0; i < stage_count; i ++) { world->stages[i]->lookup_path = lookup_path; } world->stage_count = stage_count; error: return; } int32_t ecs_get_stage_count( const ecs_world_t *world) { world = ecs_get_world(world); return world->stage_count; } int32_t ecs_stage_get_id( const ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); if (flecs_poly_is(world, ecs_stage_t)) { ecs_stage_t *stage = ECS_CONST_CAST(ecs_stage_t*, world); /* Index 0 is reserved for main stage */ return stage->id; } else if (flecs_poly_is(world, ecs_world_t)) { return 0; } else { ecs_throw(ECS_INTERNAL_ERROR, NULL); } error: return 0; } ecs_world_t* ecs_get_stage( const ecs_world_t *world, int32_t stage_id) { flecs_poly_assert(world, ecs_world_t); ecs_check(world->stage_count > stage_id, ECS_INVALID_PARAMETER, NULL); return (ecs_world_t*)world->stages[stage_id]; error: return NULL; } bool ecs_readonly_begin( ecs_world_t *world, bool multi_threaded) { flecs_poly_assert(world, ecs_world_t); flecs_process_pending_tables(world); ecs_dbg_3("#[bold]readonly"); ecs_log_push_3(); int32_t i, count = ecs_get_stage_count(world); for (i = 0; i < count; i ++) { ecs_stage_t *stage = world->stages[i]; stage->lookup_path = world->stages[0]->lookup_path; ecs_assert(stage->defer == 0, ECS_INVALID_OPERATION, "deferred mode cannot be enabled when entering readonly mode"); flecs_defer_begin(world, stage); } bool is_readonly = ECS_BIT_IS_SET(world->flags, EcsWorldReadonly); /* From this point on, the world is "locked" for mutations, and it is only * allowed to enqueue commands from stages */ ECS_BIT_SET(world->flags, EcsWorldReadonly); ECS_BIT_COND(world->flags, EcsWorldMultiThreaded, multi_threaded); return is_readonly; } void ecs_readonly_end( ecs_world_t *world) { flecs_poly_assert(world, ecs_world_t); ecs_check(world->flags & EcsWorldReadonly, ECS_INVALID_OPERATION, "world is not in readonly mode"); /* After this it is safe again to mutate the world directly */ ECS_BIT_CLEAR(world->flags, EcsWorldReadonly); ECS_BIT_CLEAR(world->flags, EcsWorldMultiThreaded); ecs_log_pop_3(); flecs_stage_merge(world); error: return; } void ecs_merge( ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(flecs_poly_is(world, ecs_world_t) || flecs_poly_is(world, ecs_stage_t), ECS_INVALID_PARAMETER, NULL); flecs_stage_merge(world); error: return; } bool ecs_stage_is_readonly( const ecs_world_t *stage) { const ecs_world_t *world = ecs_get_world(stage); if (flecs_poly_is(stage, ecs_stage_t)) { if (((const ecs_stage_t*)stage)->id == -1) { /* Stage is not owned by world, so never readonly */ return false; } } if (world->flags & EcsWorldReadonly) { if (flecs_poly_is(stage, ecs_world_t)) { return true; } } else { if (flecs_poly_is(stage, ecs_stage_t)) { return true; } } return false; } bool ecs_is_deferred( const ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); const ecs_stage_t *stage = flecs_stage_from_readonly_world(world); return stage->defer > 0; error: return false; } /** * @file value.c * @brief Utility functions to work with non-trivial pointers of user types. */ int ecs_value_init_w_type_info( const ecs_world_t *world, const ecs_type_info_t *ti, void *ptr) { flecs_poly_assert(world, ecs_world_t); ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL); (void)world; ecs_xtor_t ctor; if ((ctor = ti->hooks.ctor)) { ctor(ptr, 1, ti); } else { ecs_os_memset(ptr, 0, ti->size); } return 0; error: return -1; } int ecs_value_init( const ecs_world_t *world, ecs_entity_t type, void *ptr) { flecs_poly_assert(world, ecs_world_t); const ecs_type_info_t *ti = ecs_get_type_info(world, type); ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "entity is not a type"); return ecs_value_init_w_type_info(world, ti, ptr); error: return -1; } void* ecs_value_new_w_type_info( ecs_world_t *world, const ecs_type_info_t *ti) { flecs_poly_assert(world, ecs_world_t); ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL); (void)world; void *result = flecs_alloc(&world->allocator, ti->size); if (ecs_value_init_w_type_info(world, ti, result) != 0) { flecs_free(&world->allocator, ti->size, result); goto error; } return result; error: return NULL; } void* ecs_value_new( ecs_world_t *world, ecs_entity_t type) { flecs_poly_assert(world, ecs_world_t); const ecs_type_info_t *ti = ecs_get_type_info(world, type); ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "entity is not a type"); return ecs_value_new_w_type_info(world, ti); error: return NULL; } int ecs_value_fini_w_type_info( const ecs_world_t *world, const ecs_type_info_t *ti, void *ptr) { flecs_poly_assert(world, ecs_world_t); ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL); (void)world; ecs_xtor_t dtor; if ((dtor = ti->hooks.dtor)) { dtor(ptr, 1, ti); } return 0; error: return -1; } int ecs_value_fini( const ecs_world_t *world, ecs_entity_t type, void* ptr) { flecs_poly_assert(world, ecs_world_t); (void)world; const ecs_type_info_t *ti = ecs_get_type_info(world, type); ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "entity is not a type"); return ecs_value_fini_w_type_info(world, ti, ptr); error: return -1; } int ecs_value_free( ecs_world_t *world, ecs_entity_t type, void* ptr) { flecs_poly_assert(world, ecs_world_t); const ecs_type_info_t *ti = ecs_get_type_info(world, type); ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "entity is not a type"); if (ecs_value_fini_w_type_info(world, ti, ptr) != 0) { goto error; } flecs_free(&world->allocator, ti->size, ptr); return 0; error: return -1; } int ecs_value_copy_w_type_info( const ecs_world_t *world, const ecs_type_info_t *ti, void* dst, const void *src) { flecs_poly_assert(world, ecs_world_t); ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL); (void)world; ecs_copy_t copy; if ((copy = ti->hooks.copy)) { copy(dst, src, 1, ti); } else { ecs_os_memcpy(dst, src, ti->size); } return 0; error: return -1; } int ecs_value_copy( const ecs_world_t *world, ecs_entity_t type, void* dst, const void *src) { flecs_poly_assert(world, ecs_world_t); const ecs_type_info_t *ti = ecs_get_type_info(world, type); ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "entity is not a type"); return ecs_value_copy_w_type_info(world, ti, dst, src); error: return -1; } int ecs_value_move_w_type_info( const ecs_world_t *world, const ecs_type_info_t *ti, void* dst, void *src) { flecs_poly_assert(world, ecs_world_t); ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL); (void)world; ecs_move_t move; if ((move = ti->hooks.move)) { move(dst, src, 1, ti); } else { ecs_os_memcpy(dst, src, ti->size); } return 0; error: return -1; } int ecs_value_move( const ecs_world_t *world, ecs_entity_t type, void* dst, void *src) { flecs_poly_assert(world, ecs_world_t); const ecs_type_info_t *ti = ecs_get_type_info(world, type); ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "entity is not a type"); return ecs_value_move_w_type_info(world, ti, dst, src); error: return -1; } int ecs_value_move_ctor_w_type_info( const ecs_world_t *world, const ecs_type_info_t *ti, void* dst, void *src) { flecs_poly_assert(world, ecs_world_t); ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL); (void)world; ecs_move_t move; if ((move = ti->hooks.move_ctor)) { move(dst, src, 1, ti); } else { ecs_os_memcpy(dst, src, ti->size); } return 0; error: return -1; } int ecs_value_move_ctor( const ecs_world_t *world, ecs_entity_t type, void* dst, void *src) { flecs_poly_assert(world, ecs_world_t); const ecs_type_info_t *ti = ecs_get_type_info(world, type); ecs_check(ti != NULL, ECS_INVALID_PARAMETER, "entity is not a type"); return ecs_value_move_w_type_info(world, ti, dst, src); error: return -1; } /** * @file world.c * @brief World-level API. */ /* Id flags */ const ecs_id_t ECS_PAIR = (1ull << 63); const ecs_id_t ECS_AUTO_OVERRIDE = (1ull << 62); const ecs_id_t ECS_TOGGLE = (1ull << 61); /** Builtin component ids */ const ecs_entity_t ecs_id(EcsComponent) = 1; const ecs_entity_t ecs_id(EcsIdentifier) = 2; const ecs_entity_t ecs_id(EcsPoly) = 3; /* Poly target components */ const ecs_entity_t EcsQuery = 5; const ecs_entity_t EcsObserver = 6; const ecs_entity_t EcsSystem = 7; /* Core scopes & entities */ const ecs_entity_t EcsWorld = FLECS_HI_COMPONENT_ID + 0; const ecs_entity_t EcsFlecs = FLECS_HI_COMPONENT_ID + 1; const ecs_entity_t EcsFlecsCore = FLECS_HI_COMPONENT_ID + 2; const ecs_entity_t EcsFlecsInternals = FLECS_HI_COMPONENT_ID + 3; const ecs_entity_t EcsModule = FLECS_HI_COMPONENT_ID + 4; const ecs_entity_t EcsPrivate = FLECS_HI_COMPONENT_ID + 5; const ecs_entity_t EcsPrefab = FLECS_HI_COMPONENT_ID + 6; const ecs_entity_t EcsDisabled = FLECS_HI_COMPONENT_ID + 7; const ecs_entity_t EcsNotQueryable = FLECS_HI_COMPONENT_ID + 8; const ecs_entity_t EcsSlotOf = FLECS_HI_COMPONENT_ID + 9; const ecs_entity_t EcsFlag = FLECS_HI_COMPONENT_ID + 10; /* Marker entities for query encoding */ const ecs_entity_t EcsWildcard = FLECS_HI_COMPONENT_ID + 11; const ecs_entity_t EcsAny = FLECS_HI_COMPONENT_ID + 12; const ecs_entity_t EcsThis = FLECS_HI_COMPONENT_ID + 13; const ecs_entity_t EcsVariable = FLECS_HI_COMPONENT_ID + 14; /* Traits */ const ecs_entity_t EcsTransitive = FLECS_HI_COMPONENT_ID + 15; const ecs_entity_t EcsReflexive = FLECS_HI_COMPONENT_ID + 16; const ecs_entity_t EcsSymmetric = FLECS_HI_COMPONENT_ID + 17; const ecs_entity_t EcsFinal = FLECS_HI_COMPONENT_ID + 18; const ecs_entity_t EcsOnInstantiate = FLECS_HI_COMPONENT_ID + 19; const ecs_entity_t EcsOverride = FLECS_HI_COMPONENT_ID + 20; const ecs_entity_t EcsInherit = FLECS_HI_COMPONENT_ID + 21; const ecs_entity_t EcsDontInherit = FLECS_HI_COMPONENT_ID + 22; const ecs_entity_t EcsPairIsTag = FLECS_HI_COMPONENT_ID + 23; const ecs_entity_t EcsExclusive = FLECS_HI_COMPONENT_ID + 24; const ecs_entity_t EcsAcyclic = FLECS_HI_COMPONENT_ID + 25; const ecs_entity_t EcsTraversable = FLECS_HI_COMPONENT_ID + 26; const ecs_entity_t EcsWith = FLECS_HI_COMPONENT_ID + 27; const ecs_entity_t EcsOneOf = FLECS_HI_COMPONENT_ID + 28; const ecs_entity_t EcsCanToggle = FLECS_HI_COMPONENT_ID + 29; const ecs_entity_t EcsTrait = FLECS_HI_COMPONENT_ID + 30; const ecs_entity_t EcsRelationship = FLECS_HI_COMPONENT_ID + 31; const ecs_entity_t EcsTarget = FLECS_HI_COMPONENT_ID + 32; /* Builtin relationships */ const ecs_entity_t EcsChildOf = FLECS_HI_COMPONENT_ID + 33; const ecs_entity_t EcsIsA = FLECS_HI_COMPONENT_ID + 34; const ecs_entity_t EcsDependsOn = FLECS_HI_COMPONENT_ID + 35; /* Identifier tags */ const ecs_entity_t EcsName = FLECS_HI_COMPONENT_ID + 36; const ecs_entity_t EcsSymbol = FLECS_HI_COMPONENT_ID + 37; const ecs_entity_t EcsAlias = FLECS_HI_COMPONENT_ID + 38; /* Events */ const ecs_entity_t EcsOnAdd = FLECS_HI_COMPONENT_ID + 39; const ecs_entity_t EcsOnRemove = FLECS_HI_COMPONENT_ID + 40; const ecs_entity_t EcsOnSet = FLECS_HI_COMPONENT_ID + 41; const ecs_entity_t EcsOnDelete = FLECS_HI_COMPONENT_ID + 43; const ecs_entity_t EcsOnDeleteTarget = FLECS_HI_COMPONENT_ID + 44; const ecs_entity_t EcsOnTableCreate = FLECS_HI_COMPONENT_ID + 45; const ecs_entity_t EcsOnTableDelete = FLECS_HI_COMPONENT_ID + 46; const ecs_entity_t EcsOnTableEmpty = FLECS_HI_COMPONENT_ID + 47; const ecs_entity_t EcsOnTableFill = FLECS_HI_COMPONENT_ID + 48; /* Timers */ const ecs_entity_t ecs_id(EcsTickSource) = FLECS_HI_COMPONENT_ID + 49; const ecs_entity_t ecs_id(EcsTimer) = FLECS_HI_COMPONENT_ID + 50; const ecs_entity_t ecs_id(EcsRateFilter) = FLECS_HI_COMPONENT_ID + 51; /* Actions */ const ecs_entity_t EcsRemove = FLECS_HI_COMPONENT_ID + 52; const ecs_entity_t EcsDelete = FLECS_HI_COMPONENT_ID + 53; const ecs_entity_t EcsPanic = FLECS_HI_COMPONENT_ID + 54; /* Storage */ const ecs_entity_t EcsSparse = FLECS_HI_COMPONENT_ID + 55; const ecs_entity_t EcsUnion = FLECS_HI_COMPONENT_ID + 56; /* Misc */ const ecs_entity_t ecs_id(EcsDefaultChildComponent) = FLECS_HI_COMPONENT_ID + 57; /* Builtin predicate ids (used by query engine) */ const ecs_entity_t EcsPredEq = FLECS_HI_COMPONENT_ID + 58; const ecs_entity_t EcsPredMatch = FLECS_HI_COMPONENT_ID + 59; const ecs_entity_t EcsPredLookup = FLECS_HI_COMPONENT_ID + 60; const ecs_entity_t EcsScopeOpen = FLECS_HI_COMPONENT_ID + 61; const ecs_entity_t EcsScopeClose = FLECS_HI_COMPONENT_ID + 62; /* Systems */ const ecs_entity_t EcsMonitor = FLECS_HI_COMPONENT_ID + 63; const ecs_entity_t EcsEmpty = FLECS_HI_COMPONENT_ID + 64; const ecs_entity_t ecs_id(EcsPipeline) = FLECS_HI_COMPONENT_ID + 65; const ecs_entity_t EcsOnStart = FLECS_HI_COMPONENT_ID + 66; const ecs_entity_t EcsPreFrame = FLECS_HI_COMPONENT_ID + 67; const ecs_entity_t EcsOnLoad = FLECS_HI_COMPONENT_ID + 68; const ecs_entity_t EcsPostLoad = FLECS_HI_COMPONENT_ID + 69; const ecs_entity_t EcsPreUpdate = FLECS_HI_COMPONENT_ID + 70; const ecs_entity_t EcsOnUpdate = FLECS_HI_COMPONENT_ID + 71; const ecs_entity_t EcsOnValidate = FLECS_HI_COMPONENT_ID + 72; const ecs_entity_t EcsPostUpdate = FLECS_HI_COMPONENT_ID + 73; const ecs_entity_t EcsPreStore = FLECS_HI_COMPONENT_ID + 74; const ecs_entity_t EcsOnStore = FLECS_HI_COMPONENT_ID + 75; const ecs_entity_t EcsPostFrame = FLECS_HI_COMPONENT_ID + 76; const ecs_entity_t EcsPhase = FLECS_HI_COMPONENT_ID + 77; /* Meta primitive components (don't use low ids to save id space) */ #ifdef FLECS_META const ecs_entity_t ecs_id(ecs_bool_t) = FLECS_HI_COMPONENT_ID + 80; const ecs_entity_t ecs_id(ecs_char_t) = FLECS_HI_COMPONENT_ID + 81; const ecs_entity_t ecs_id(ecs_byte_t) = FLECS_HI_COMPONENT_ID + 82; const ecs_entity_t ecs_id(ecs_u8_t) = FLECS_HI_COMPONENT_ID + 83; const ecs_entity_t ecs_id(ecs_u16_t) = FLECS_HI_COMPONENT_ID + 84; const ecs_entity_t ecs_id(ecs_u32_t) = FLECS_HI_COMPONENT_ID + 85; const ecs_entity_t ecs_id(ecs_u64_t) = FLECS_HI_COMPONENT_ID + 86; const ecs_entity_t ecs_id(ecs_uptr_t) = FLECS_HI_COMPONENT_ID + 87; const ecs_entity_t ecs_id(ecs_i8_t) = FLECS_HI_COMPONENT_ID + 88; const ecs_entity_t ecs_id(ecs_i16_t) = FLECS_HI_COMPONENT_ID + 89; const ecs_entity_t ecs_id(ecs_i32_t) = FLECS_HI_COMPONENT_ID + 90; const ecs_entity_t ecs_id(ecs_i64_t) = FLECS_HI_COMPONENT_ID + 91; const ecs_entity_t ecs_id(ecs_iptr_t) = FLECS_HI_COMPONENT_ID + 92; const ecs_entity_t ecs_id(ecs_f32_t) = FLECS_HI_COMPONENT_ID + 93; const ecs_entity_t ecs_id(ecs_f64_t) = FLECS_HI_COMPONENT_ID + 94; const ecs_entity_t ecs_id(ecs_string_t) = FLECS_HI_COMPONENT_ID + 95; const ecs_entity_t ecs_id(ecs_entity_t) = FLECS_HI_COMPONENT_ID + 96; const ecs_entity_t ecs_id(ecs_id_t) = FLECS_HI_COMPONENT_ID + 97; /** Meta module component ids */ const ecs_entity_t ecs_id(EcsType) = FLECS_HI_COMPONENT_ID + 98; const ecs_entity_t ecs_id(EcsTypeSerializer) = FLECS_HI_COMPONENT_ID + 99; const ecs_entity_t ecs_id(EcsPrimitive) = FLECS_HI_COMPONENT_ID + 100; const ecs_entity_t ecs_id(EcsEnum) = FLECS_HI_COMPONENT_ID + 101; const ecs_entity_t ecs_id(EcsBitmask) = FLECS_HI_COMPONENT_ID + 102; const ecs_entity_t ecs_id(EcsMember) = FLECS_HI_COMPONENT_ID + 103; const ecs_entity_t ecs_id(EcsMemberRanges) = FLECS_HI_COMPONENT_ID + 104; const ecs_entity_t ecs_id(EcsStruct) = FLECS_HI_COMPONENT_ID + 105; const ecs_entity_t ecs_id(EcsArray) = FLECS_HI_COMPONENT_ID + 106; const ecs_entity_t ecs_id(EcsVector) = FLECS_HI_COMPONENT_ID + 107; const ecs_entity_t ecs_id(EcsOpaque) = FLECS_HI_COMPONENT_ID + 108; const ecs_entity_t ecs_id(EcsUnit) = FLECS_HI_COMPONENT_ID + 109; const ecs_entity_t ecs_id(EcsUnitPrefix) = FLECS_HI_COMPONENT_ID + 110; const ecs_entity_t EcsConstant = FLECS_HI_COMPONENT_ID + 111; const ecs_entity_t EcsQuantity = FLECS_HI_COMPONENT_ID + 112; #endif /* Doc module components */ #ifdef FLECS_DOC const ecs_entity_t ecs_id(EcsDocDescription) = FLECS_HI_COMPONENT_ID + 113; const ecs_entity_t EcsDocBrief = FLECS_HI_COMPONENT_ID + 114; const ecs_entity_t EcsDocDetail = FLECS_HI_COMPONENT_ID + 115; const ecs_entity_t EcsDocLink = FLECS_HI_COMPONENT_ID + 116; const ecs_entity_t EcsDocColor = FLECS_HI_COMPONENT_ID + 117; 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; /* Silence readonly checks */ world->flags &= ~EcsWorldReadonly; /* Hack around safety checks (this ought to look ugly) */ state->defer_count = stage->defer; state->cmd = *stage->cmd; flecs_commands_init(stage, stage->cmd); 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; flecs_commands_fini(stage, stage->cmd); *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)); ecs_run_aperiodic(world, EcsAperiodicEmptyTables); /* Delete root entities that are not modules. This prioritizes deleting * regular entities first, which reduces the chance of components getting * destructed in random order because it got deleted before entities, * thereby bypassing the OnDeleteTarget policy. */ flecs_defer_begin(world, world->stages[0]); flecs_fini_root_tables(world, idr, true); flecs_defer_end(world, world->stages[0]); flecs_defer_begin(world, world->stages[0]); flecs_fini_root_tables(world, idr, false); flecs_defer_end(world, world->stages[0]); } static void flecs_fini_store(ecs_world_t *world) { flecs_clean_tables(world); flecs_sparse_fini(&world->store.tables); flecs_table_fini(world, &world->store.root); flecs_entities_clear(world); flecs_hashmap_fini(&world->store.table_map); ecs_assert(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); world->pending_tables = ecs_os_calloc_t(ecs_sparse_t); flecs_sparse_init_t(world->pending_tables, a, &world->allocators.sparse_chunk, ecs_table_t*); world->pending_buffer = ecs_os_calloc_t(ecs_sparse_t); flecs_sparse_init_t(world->pending_buffer, a, &world->allocators.sparse_chunk, ecs_table_t*); flecs_name_index_init(&world->aliases, a); flecs_name_index_init(&world->symbols, a); ecs_vec_init_t(a, &world->fini_actions, ecs_action_elem_t, 0); world->info.time_scale = 1.0; if (ecs_os_has_time()) { ecs_os_get_time(&world->world_start_time); } ecs_set_stage_count(world, 1); ecs_default_lookup_path[0] = EcsFlecsCore; ecs_set_lookup_path(world, ecs_default_lookup_path); flecs_init_store(world); flecs_bootstrap(world); world->flags &= ~EcsWorldInit; ecs_trace("world ready!"); ecs_log_pop(); return world; } ecs_world_t *ecs_init(void) { ecs_world_t *world = ecs_mini(); #ifdef FLECS_MODULE_H ecs_trace("#[bold]import addons"); ecs_log_push(); ecs_trace("use ecs_mini to create world without importing addons"); #ifdef FLECS_SYSTEM ECS_IMPORT(world, FlecsSystem); #endif #ifdef FLECS_PIPELINE ECS_IMPORT(world, FlecsPipeline); #endif #ifdef FLECS_TIMER ECS_IMPORT(world, FlecsTimer); #endif #ifdef FLECS_META ECS_IMPORT(world, FlecsMeta); #endif #ifdef FLECS_DOC ECS_IMPORT(world, FlecsDoc); #endif #ifdef FLECS_SCRIPT ECS_IMPORT(world, FlecsScript); #endif #ifdef FLECS_REST ECS_IMPORT(world, FlecsRest); #endif #ifdef FLECS_UNITS ecs_trace("#[green]module#[reset] flecs.units is not automatically imported"); #endif ecs_trace("addons imported!"); ecs_log_pop(); #endif return world; } ecs_world_t* ecs_init_w_args( int argc, char *argv[]) { ecs_world_t *world = ecs_init(); (void)argc; (void)argv; #ifdef FLECS_DOC if (argc) { char *app = argv[0]; char *last_elem = strrchr(app, '/'); if (!last_elem) { last_elem = strrchr(app, '\\'); } if (last_elem) { app = last_elem + 1; } ecs_set_pair(world, EcsWorld, EcsDocDescription, EcsName, {app}); } #endif return world; } void ecs_quit( ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); flecs_stage_from_world(&world); world->flags |= EcsWorldQuit; error: return; } bool ecs_should_quit( const ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); return ECS_BIT_IS_SET(world->flags, EcsWorldQuit); error: return true; } void flecs_notify_tables( ecs_world_t *world, ecs_id_t id, ecs_table_event_t *event) { flecs_poly_assert(world, ecs_world_t); /* If no id is specified, broadcast to all tables */ if (!id) { ecs_sparse_t *tables = &world->store.tables; int32_t i, count = flecs_sparse_count(tables); for (i = 0; i < count; i ++) { ecs_table_t *table = flecs_sparse_get_dense_t(tables, ecs_table_t, i); flecs_table_notify(world, table, 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_sparse_fini(world->pending_tables); flecs_sparse_fini(world->pending_buffer); ecs_os_free(world->pending_tables); ecs_os_free(world->pending_buffer); flecs_fini_id_records(world); flecs_fini_type_info(world); flecs_observable_fini(&world->observable); flecs_name_index_fini(&world->aliases); flecs_name_index_fini(&world->symbols); ecs_set_stage_count(world, 0); ecs_log_pop_1(); flecs_world_allocators_fini(world); /* End of the world */ flecs_poly_free(world, ecs_world_t); ecs_os_fini(); ecs_trace("world destroyed, bye!"); ecs_log_pop(); return 0; } bool ecs_is_fini( const ecs_world_t *world) { ecs_assert(world != NULL, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); return ECS_BIT_IS_SET(world->flags, EcsWorldFini); } void ecs_dim( ecs_world_t *world, int32_t entity_count) { flecs_poly_assert(world, ecs_world_t); flecs_entities_set_size(world, entity_count + FLECS_HI_COMPONENT_ID); } void flecs_eval_component_monitors( ecs_world_t *world) { flecs_poly_assert(world, ecs_world_t); flecs_process_pending_tables(world); flecs_eval_component_monitor(world); } void ecs_measure_frame_time( ecs_world_t *world, bool enable) { flecs_poly_assert(world, ecs_world_t); ecs_check(ecs_os_has_time(), ECS_MISSING_OS_API, NULL); if (ECS_EQZERO(world->info.target_fps) || enable) { ECS_BIT_COND(world->flags, EcsWorldMeasureFrameTime, enable); } error: return; } void ecs_measure_system_time( ecs_world_t *world, bool enable) { flecs_poly_assert(world, ecs_world_t); ecs_check(ecs_os_has_time(), ECS_MISSING_OS_API, NULL); ECS_BIT_COND(world->flags, EcsWorldMeasureSystemTime, enable); error: return; } void ecs_set_target_fps( ecs_world_t *world, ecs_ftime_t fps) { flecs_poly_assert(world, ecs_world_t); ecs_check(ecs_os_has_time(), ECS_MISSING_OS_API, NULL); ecs_measure_frame_time(world, true); world->info.target_fps = fps; error: return; } void ecs_set_default_query_flags( ecs_world_t *world, ecs_flags32_t flags) { flecs_poly_assert(world, ecs_world_t); flecs_process_pending_tables(world); world->default_query_flags = flags; } void* ecs_get_ctx( const ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); return world->ctx; error: return NULL; } void* ecs_get_binding_ctx( const ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); return world->binding_ctx; error: return NULL; } void ecs_set_ctx( ecs_world_t *world, void *ctx, ecs_ctx_free_t ctx_free) { flecs_poly_assert(world, ecs_world_t); world->ctx = ctx; world->ctx_free = ctx_free; } void ecs_set_binding_ctx( ecs_world_t *world, void *ctx, ecs_ctx_free_t ctx_free) { flecs_poly_assert(world, ecs_world_t); world->binding_ctx = ctx; world->binding_ctx_free = ctx_free; } void ecs_set_entity_range( ecs_world_t *world, ecs_entity_t id_start, ecs_entity_t id_end) { flecs_poly_assert(world, ecs_world_t); ecs_check(!id_end || id_end > id_start, ECS_INVALID_PARAMETER, NULL); 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) { 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; } ecs_run_aperiodic(world, EcsAperiodicEmptyTables); /* Make sure that we defer adding the inactive tags until after iterating * the query */ flecs_defer_begin(world, world->stages[0]); ecs_table_cache_iter_t it; const ecs_table_record_t *tr; if (flecs_table_cache_iter(&idr->cache, &it)) { while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { ecs_table_t *table = tr->hdr.table; EcsPoly *queries = ecs_table_get_column(table, tr->column, 0); int32_t i, count = ecs_table_count(table); for (i = 0; i < count; i ++) { ecs_query_t *query = queries[i].poly; 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]); } /** Walk over tables that had a state change which requires bookkeeping */ void flecs_process_pending_tables( const ecs_world_t *world_r) { flecs_poly_assert(world_r, ecs_world_t); /* We can't update the administration while in readonly mode, but we can * ensure that when this function is called there are no pending events. */ if (world_r->flags & EcsWorldReadonly) { ecs_assert(flecs_sparse_count(world_r->pending_tables) == 0, ECS_INTERNAL_ERROR, NULL); return; } /* Safe to cast, world is not readonly */ ecs_world_t *world = ECS_CONST_CAST(ecs_world_t*, world_r); /* If pending buffer is NULL there already is a stackframe that's iterating * the table list. This can happen when an observer for a table event results * in a mutation that causes another table to change state. A typical * example of this is a system that becomes active/inactive as the result of * a query (and as a result, its matched tables) becoming empty/non empty */ if (!world->pending_buffer) { return; } /* Swap buffer. The logic could in theory have been implemented with a * single sparse set, but that would've complicated (and slowed down) the * iteration. Additionally, by using a double buffer approach we can still * keep most of the original ordering of events intact, which is desirable * as it means that the ordering of tables in the internal data structures is * more predictable. */ int32_t i, count = flecs_sparse_count(world->pending_tables); if (!count) { return; } ecs_os_perf_trace_push("flecs.process_pending_tables"); flecs_journal_begin(world, EcsJournalTableEvents, 0, 0, 0); do { ecs_sparse_t *pending_tables = world->pending_tables; world->pending_tables = world->pending_buffer; world->pending_buffer = NULL; /* Make sure that any ECS operations that occur while delivering the * events does not cause inconsistencies, like sending an Empty * notification for a table that just became non-empty. */ flecs_defer_begin(world, world->stages[0]); for (i = 0; i < count; i ++) { ecs_table_t *table = flecs_sparse_get_dense_t( pending_tables, ecs_table_t*, i)[0]; if (!table->id) { /* Table is being deleted, ignore empty events */ continue; } /* For each id in the table, add it to the empty/non empty list * based on its current state */ if (flecs_table_records_update_empty(table)) { int32_t table_count = ecs_table_count(table); if (table->flags & (EcsTableHasOnTableFill|EcsTableHasOnTableEmpty)) { /* Only emit an event when there was a change in the * administration. It is possible that a table ended up in the * pending_tables list by going from empty->non-empty, but then * became empty again. By the time we run this code, no changes * in the administration would actually be made. */ ecs_entity_t evt = table_count ? EcsOnTableFill : EcsOnTableEmpty; if (ecs_should_log_3()) { ecs_dbg_3("table %u state change (%s)", (uint32_t)table->id, table_count ? "non-empty" : "empty"); } ecs_log_push_3(); flecs_table_emit(world, table, evt); ecs_log_pop_3(); } world->info.empty_table_count += (table_count == 0) * 2 - 1; } } flecs_sparse_clear(pending_tables); ecs_defer_end(world); world->pending_buffer = pending_tables; } while ((count = flecs_sparse_count(world->pending_tables))); flecs_journal_end(); ecs_os_perf_trace_pop("flecs.process_pending_tables"); } void flecs_table_set_empty( ecs_world_t *world, ecs_table_t *table) { flecs_poly_assert(world, ecs_world_t); ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INTERNAL_ERROR, NULL); ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); if (ecs_table_count(table)) { table->_->generation = 0; } flecs_sparse_ensure_fast_t(world->pending_tables, ecs_table_t*, (uint32_t)table->id)[0] = table; } bool ecs_id_in_use( const ecs_world_t *world, ecs_id_t id) { ecs_id_record_t *idr = flecs_id_record_get(world, id); if (!idr) { return false; } return (flecs_table_cache_count(&idr->cache) != 0) || (flecs_table_cache_empty_count(&idr->cache) != 0); } void ecs_run_aperiodic( ecs_world_t *world, ecs_flags32_t flags) { flecs_poly_assert(world, ecs_world_t); if (!flags || (flags & EcsAperiodicEmptyTables)) { flecs_process_pending_tables(world); } if ((flags & EcsAperiodicEmptyQueries)) { flecs_process_empty_queries(world); } if (!flags || (flags & EcsAperiodicComponentMonitors)) { flecs_eval_component_monitors(world); } } int32_t ecs_delete_empty_tables( ecs_world_t *world, ecs_id_t id, uint16_t clear_generation, uint16_t delete_generation, int32_t min_id_count, double time_budget_seconds) { flecs_poly_assert(world, ecs_world_t); ecs_os_perf_trace_push("flecs.delete_empty_tables"); /* Make sure empty tables are in the empty table lists */ ecs_run_aperiodic(world, EcsAperiodicEmptyTables); ecs_time_t start = {0}, cur = {0}; int32_t delete_count = 0; bool time_budget = false; if (ECS_NEQZERO(time_budget_seconds) || (ecs_should_log_1() && ecs_os_has_time())) { ecs_time_measure(&start); } if (ECS_NEQZERO(time_budget_seconds)) { time_budget = true; } if (!id) { id = EcsAny; /* Iterate all empty tables */ } ecs_id_record_t *idr = flecs_id_record_get(world, id); ecs_table_cache_iter_t it; if (idr && flecs_table_cache_empty_iter((ecs_table_cache_t*)idr, &it)) { ecs_table_record_t *tr; while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { if (time_budget) { cur = start; if (ecs_time_measure(&cur) > time_budget_seconds) { goto done; } } ecs_table_t *table = tr->hdr.table; ecs_assert(ecs_table_count(table) == 0, ECS_INTERNAL_ERROR, NULL); if (table->type.count < min_id_count) { continue; } uint16_t gen = ++ table->_->generation; if (delete_generation && (gen > delete_generation)) { flecs_table_fini(world, table); delete_count ++; } else if (clear_generation && (gen > clear_generation)) { 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) { flecs_poly_assert(world, ecs_world_t); return world->flags; } /** * @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; EMSCRIPTEN_KEEPALIVE char* flecs_explorer_request(const char *method, char *request) { ecs_http_reply_t reply = ECS_HTTP_REPLY_INIT; ecs_http_server_request(flecs_wasm_rest_server, method, request, &reply); if (reply.code == 200) { return ecs_strbuf_get(&reply.body); } else { char *body = ecs_strbuf_get(&reply.body); if (body) { return body; } else { return flecs_asprintf( "{\"error\": \"bad request\", \"status\": %d}", reply.code); } } } #endif int ecs_app_run( ecs_world_t *world, ecs_app_desc_t *desc) { ecs_app_desc = *desc; /* Don't set FPS & threads if using emscripten */ #ifndef ECS_TARGET_EM if (ECS_NEQZERO(ecs_app_desc.target_fps)) { ecs_set_target_fps(world, ecs_app_desc.target_fps); } if (ecs_app_desc.threads) { ecs_set_threads(world, ecs_app_desc.threads); } #endif /* REST server enables connecting to app with explorer */ if (desc->enable_rest) { #ifdef FLECS_REST #ifdef ECS_TARGET_EM flecs_wasm_rest_server = ecs_rest_server_init(world, NULL); #else ECS_IMPORT(world, FlecsRest); ecs_set(world, EcsWorld, EcsRest, {.port = desc->port }); #endif #else ecs_warn("cannot enable remote API, REST addon not available"); #endif } /* Monitoring periodically collects statistics */ if (desc->enable_stats) { #ifdef FLECS_STATS ECS_IMPORT(world, FlecsStats); #else ecs_warn("cannot enable monitoring, MONITOR addon not available"); #endif } return run_action(world, &ecs_app_desc); } int ecs_app_run_frame( ecs_world_t *world, const ecs_app_desc_t *desc) { return frame_action(world, desc); } int ecs_app_set_run_action( ecs_app_run_action_t callback) { if (run_action != flecs_default_run_action && run_action != callback) { ecs_err("run action already set"); return -1; } run_action = callback; return 0; } int ecs_app_set_frame_action( ecs_app_frame_action_t callback) { if (frame_action != flecs_default_frame_action && frame_action != callback) { ecs_err("frame action already set"); return -1; } frame_action = callback; return 0; } #endif /** * @file addons/doc.c * @brief Doc addon. */ #ifdef FLECS_DOC static ECS_COPY(EcsDocDescription, dst, src, { ecs_os_strset((char**)&dst->value, src->value); }) static ECS_MOVE(EcsDocDescription, dst, src, { ecs_os_free((char*)dst->value); dst->value = src->value; src->value = NULL; }) static ECS_DTOR(EcsDocDescription, ptr, { ecs_os_free((char*)ptr->value); }) static void flecs_doc_set( ecs_world_t *world, ecs_entity_t entity, ecs_entity_t kind, const char *value) { if (value) { ecs_set_pair(world, entity, EcsDocDescription, kind, { /* Safe, value gets copied by copy hook */ .value = ECS_CONST_CAST(char*, value) }); } else { ecs_remove_pair(world, entity, ecs_id(EcsDocDescription), kind); } } void ecs_doc_set_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, EcsOnTableFill, "Event emitted when table becomes non-empty"); ecs_doc_set_brief(world, EcsOnTableEmpty, "Event emitted when table becomes empty"); ecs_doc_set_brief(world, EcsOnTableCreate, "Event emitted when table is created"); ecs_doc_set_brief(world, EcsOnTableDelete, "Event emitted when table is deleted"); ecs_doc_set_brief(world, EcsThis, "Query marker to express $this variable"); ecs_doc_set_brief(world, EcsWildcard, "Query marker to express match all wildcard"); ecs_doc_set_brief(world, EcsAny, "Query marker to express match at least one wildcard"); ecs_doc_set_brief(world, EcsPredEq, "Query marker to express == operator"); ecs_doc_set_brief(world, EcsPredMatch, "Query marker to express ~= operator"); ecs_doc_set_brief(world, EcsPredLookup, "Query marker to express by-name lookup"); ecs_doc_set_brief(world, EcsScopeOpen, "Query marker to express scope open"); ecs_doc_set_brief(world, EcsScopeClose, "Query marker to express scope close"); ecs_doc_set_brief(world, EcsEmpty, "Tag used to indicate a query has no results"); } /* Doc definitions for doc components */ static void flecs_doc_import_doc_definitions( ecs_world_t *world) { ecs_entity_t doc = ecs_lookup(world, "flecs.doc"); ecs_doc_set_brief(world, doc, "Flecs module with documentation components"); ecs_doc_set_brief(world, EcsDocBrief, "Brief description"); ecs_doc_set_brief(world, EcsDocDetail, "Detailed description"); ecs_doc_set_brief(world, EcsDocLink, "Link to additional documentation"); ecs_doc_set_brief(world, EcsDocColor, "Color hint for entity"); ecs_doc_set_brief(world, ecs_id(EcsDocDescription), "Component used to add documentation"); ecs_doc_set_brief(world, EcsDocBrief, "Used as (Description, Brief) to add a brief description"); ecs_doc_set_brief(world, EcsDocDetail, "Used as (Description, Detail) to add a detailed description"); ecs_doc_set_brief(world, EcsDocLink, "Used as (Description, Link) to add a link"); } void FlecsDocImport( ecs_world_t *world) { ECS_MODULE(world, FlecsDoc); ecs_set_name_prefix(world, "EcsDoc"); flecs_bootstrap_component(world, EcsDocDescription); flecs_bootstrap_tag(world, 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; } // Validate registered component void ecs_cpp_component_validate( ecs_world_t *world, ecs_entity_t id, const char *name, const char *symbol, size_t size, size_t alignment, bool implicit_name) { /* If entity has a name check if it matches */ if (ecs_is_valid(world, id) && ecs_get_name(world, id) != NULL) { if (!implicit_name && id >= EcsFirstUserComponentId) { #ifndef FLECS_NDEBUG char *path = ecs_get_path_w_sep( world, 0, id, "::", NULL); if (ecs_os_strcmp(path, name)) { ecs_abort(ECS_INCONSISTENT_NAME, "component '%s' already registered with name '%s'", name, path); } ecs_os_free(path); #endif } if (symbol) { const char *existing_symbol = ecs_get_symbol(world, id); if (existing_symbol) { if (ecs_os_strcmp(symbol, existing_symbol)) { ecs_abort(ECS_INCONSISTENT_NAME, "component '%s' with symbol '%s' already registered with symbol '%s'", name, symbol, existing_symbol); } } } } else { /* Ensure that the entity id valid */ if (!ecs_is_alive(world, id)) { ecs_make_alive(world, id); } /* Register name with entity, so that when the entity is created the * correct id will be resolved from the name. Only do this when the * entity is empty. */ ecs_add_path_w_sep(world, id, 0, name, "::", "::"); } /* If a component was already registered with this id but with a * different size, the ecs_component_init function will fail. */ /* We need to explicitly call ecs_component_init here again. Even though * the component was already registered, it may have been registered * with a different world. This ensures that the component is registered * with the same id for the current world. * If the component was registered already, nothing will change. */ ecs_entity_t ent = ecs_component_init(world, &(ecs_component_desc_t){ .entity = id, .type.size = flecs_uto(int32_t, size), .type.alignment = flecs_uto(int32_t, alignment) }); (void)ent; ecs_assert(ent == id, ECS_INTERNAL_ERROR, NULL); } ecs_entity_t ecs_cpp_component_register( ecs_world_t *world, ecs_entity_t id, const char *name, const char *symbol, ecs_size_t size, ecs_size_t alignment, bool implicit_name, bool *existing_out) { (void)size; (void)alignment; /* If the component is not yet registered, ensure no other component * or entity has been registered with this name. Ensure component is * looked up from root. */ bool existing = false; ecs_entity_t prev_scope = ecs_set_scope(world, 0); ecs_entity_t ent; if (id) { ent = id; } else { ent = ecs_lookup_path_w_sep(world, 0, name, "::", "::", false); existing = ent != 0 && ecs_has(world, ent, EcsComponent); } ecs_set_scope(world, prev_scope); /* If entity exists, compare symbol name to ensure that the component * we are trying to register under this name is the same */ if (ent) { const EcsComponent *component = ecs_get(world, ent, EcsComponent); if (component != NULL) { const char *sym = ecs_get_symbol(world, ent); if (sym && ecs_os_strcmp(sym, symbol)) { /* Application is trying to register a type with an entity that * was already associated with another type. In most cases this * is an error, with the exception of a scenario where the * application is wrapping a C type with a C++ type. * * In this case the C++ type typically inherits from the C type, * and adds convenience methods to the derived class without * changing anything that would change the size or layout. * * To meet this condition, the new type must have the same size * and alignment as the existing type, and the name of the type * type must be equal to the registered name (not symbol). * * The latter ensures that it was the intent of the application * to alias the type, vs. accidentally registering an unrelated * type with the same size/alignment. */ char *type_path = ecs_get_path(world, ent); if (ecs_os_strcmp(type_path, symbol) || component->size != size || component->alignment != alignment) { ecs_err( "component with name '%s' is already registered for"\ " type '%s' (trying to register for type '%s')", name, sym, symbol); ecs_abort(ECS_NAME_IN_USE, NULL); } ecs_os_free(type_path); } else if (!sym) { ecs_set_symbol(world, ent, symbol); } } /* If no entity is found, lookup symbol to check if the component was * registered under a different name. */ } else if (!implicit_name) { ent = ecs_lookup_symbol(world, symbol, false, false); ecs_assert(ent == 0 || (ent == id), ECS_INCONSISTENT_COMPONENT_ID, symbol); } if (existing_out) { *existing_out = existing; } return ent; } ecs_entity_t ecs_cpp_component_register_explicit( ecs_world_t *world, ecs_entity_t s_id, ecs_entity_t id, const char *name, const char *type_name, const char *symbol, size_t size, size_t alignment, bool is_component, bool *existing_out) { char *existing_name = NULL; if (existing_out) *existing_out = false; // If an explicit id is provided, it is possible that the symbol and // name differ from the actual type, as the application may alias // one type to another. if (!id) { if (!name) { // If no name was provided first check if a type with the provided // symbol was already registered. id = ecs_lookup_symbol(world, symbol, false, false); if (id) { existing_name = ecs_get_path_w_sep(world, 0, id, "::", "::"); name = existing_name; if (existing_out) *existing_out = true; } else { // If type is not yet known, derive from type name name = ecs_cpp_trim_module(world, type_name); } } } else { // If an explicit id is provided but it has no name, inherit // the name from the type. if (!ecs_is_valid(world, id) || !ecs_get_name(world, id)) { name = ecs_cpp_trim_module(world, type_name); } } ecs_entity_t entity; if (is_component || size != 0) { entity = ecs_entity(world, { .id = s_id, .name = name, .sep = "::", .root_sep = "::", .symbol = symbol, .use_low_id = true }); ecs_assert(entity != 0, ECS_INVALID_OPERATION, "registration failed for component %s", name); entity = ecs_component_init(world, &(ecs_component_desc_t){ .entity = entity, .type.size = flecs_uto(int32_t, size), .type.alignment = flecs_uto(int32_t, alignment) }); ecs_assert(entity != 0, ECS_INVALID_OPERATION, "registration failed for component %s", name); } else { entity = ecs_entity(world, { .id = s_id, .name = name, .sep = "::", .root_sep = "::", .symbol = symbol, .use_low_id = true }); } ecs_assert(entity != 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(!s_id || s_id == entity, ECS_INTERNAL_ERROR, NULL); ecs_os_free(existing_name); return entity; } void ecs_cpp_enum_init( ecs_world_t *world, ecs_entity_t id) { (void)world; (void)id; #ifdef FLECS_META ecs_suspend_readonly_state_t readonly_state; world = flecs_suspend_readonly(world, &readonly_state); ecs_set(world, id, EcsEnum, {0}); flecs_resume_readonly(world, &readonly_state); #endif } ecs_entity_t ecs_cpp_enum_constant_register( ecs_world_t *world, ecs_entity_t parent, ecs_entity_t id, const char *name, int value) { ecs_suspend_readonly_state_t readonly_state; world = flecs_suspend_readonly(world, &readonly_state); const char *parent_name = ecs_get_name(world, parent); ecs_size_t parent_name_len = ecs_os_strlen(parent_name); if (!ecs_os_strncmp(name, parent_name, parent_name_len)) { name += parent_name_len; if (name[0] == '_') { name ++; } } ecs_entity_t prev = ecs_set_scope(world, parent); id = ecs_entity(world, { .id = id, .name = name }); ecs_assert(id != 0, ECS_INVALID_OPERATION, name); ecs_set_scope(world, prev); #ifdef FLECS_DEBUG const EcsComponent *cptr = ecs_get(world, parent, EcsComponent); ecs_assert(cptr != NULL, ECS_INVALID_PARAMETER, "enum is not a component"); ecs_assert(cptr->size == ECS_SIZEOF(int32_t), ECS_UNSUPPORTED, "enum component must have 32bit size"); #endif #ifdef FLECS_META ecs_set_id(world, id, ecs_pair(EcsConstant, ecs_id(ecs_i32_t)), sizeof(ecs_i32_t), &value); #endif flecs_resume_readonly(world, &readonly_state); ecs_trace("#[green]constant#[reset] %s.%s created with value %d", ecs_get_name(world, parent), name, value); return id; } static int32_t flecs_reset_count = 0; int32_t ecs_cpp_reset_count_get(void) { return flecs_reset_count; } int32_t ecs_cpp_reset_count_inc(void) { return ++flecs_reset_count; } #ifdef FLECS_META const ecs_member_t* ecs_cpp_last_member( const ecs_world_t *world, ecs_entity_t type) { const EcsStruct *st = ecs_get(world, type, EcsStruct); if (!st) { char *type_str = ecs_get_path(world, type); ecs_err("entity '%s' is not a struct", type_str); ecs_os_free(type_str); return 0; } ecs_member_t *m = ecs_vec_get_t(&st->members, ecs_member_t, ecs_vec_count(&st->members) - 1); ecs_assert(m != NULL, ECS_INTERNAL_ERROR, NULL); return m; } #endif #endif /** * @file addons/http.c * @brief HTTP addon. * * This is a heavily modified version of the EmbeddableWebServer (see copyright * below). This version has been stripped from everything not strictly necessary * for receiving/replying to simple HTTP requests, and has been modified to use * the Flecs OS API. * * EmbeddableWebServer Copyright (c) 2016, 2019, 2020 Forrest Heller, and * CONTRIBUTORS (see below) - All rights reserved. * * CONTRIBUTORS: * Martin Pulec - bug fixes, warning fixes, IPv6 support * Daniel Barry - bug fix (ifa_addr != NULL) * * Released under the BSD 2-clause license: * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. THIS SOFTWARE IS * PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN * NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifdef FLECS_HTTP #ifdef ECS_TARGET_MSVC #pragma comment(lib, "Ws2_32.lib") #endif #if defined(ECS_TARGET_WINDOWS) #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #ifndef NOMINMAX #define NOMINMAX #endif #include #include #include typedef SOCKET ecs_http_socket_t; #else #include #include #include #include #include #include #include #include #ifdef __FreeBSD__ #include #endif typedef int ecs_http_socket_t; #if !defined(MSG_NOSIGNAL) #define MSG_NOSIGNAL (0) #endif #endif /* Max length of request method */ #define ECS_HTTP_METHOD_LEN_MAX (8) /* Timeout (s) before connection purge */ #define ECS_HTTP_CONNECTION_PURGE_TIMEOUT (1.0) /* Number of dequeues before purging */ #define ECS_HTTP_CONNECTION_PURGE_RETRY_COUNT (5) /* Number of retries receiving request */ #define ECS_HTTP_REQUEST_RECV_RETRY (10) /* Minimum interval between dequeueing requests (ms) */ #define ECS_HTTP_MIN_DEQUEUE_INTERVAL (50) /* Minimum interval between printing statistics (ms) */ #define ECS_HTTP_MIN_STATS_INTERVAL (10 * 1000) /* Receive buffer size */ #define ECS_HTTP_SEND_RECV_BUFFER_SIZE (16 * 1024) /* Max length of request (path + query + headers + body) */ #define ECS_HTTP_REQUEST_LEN_MAX (10 * 1024 * 1024) /* Total number of outstanding send requests */ #define ECS_HTTP_SEND_QUEUE_MAX (256) /* Global statistics */ int64_t ecs_http_request_received_count = 0; int64_t ecs_http_request_invalid_count = 0; int64_t ecs_http_request_handled_ok_count = 0; int64_t ecs_http_request_handled_error_count = 0; int64_t ecs_http_request_not_handled_count = 0; int64_t ecs_http_request_preflight_count = 0; int64_t ecs_http_send_ok_count = 0; int64_t ecs_http_send_error_count = 0; int64_t ecs_http_busy_count = 0; /* Send request queue */ typedef struct ecs_http_send_request_t { ecs_http_socket_t sock; char *headers; int32_t header_length; char *content; int32_t content_length; } ecs_http_send_request_t; typedef struct ecs_http_send_queue_t { ecs_http_send_request_t requests[ECS_HTTP_SEND_QUEUE_MAX]; int32_t head; int32_t tail; ecs_os_thread_t thread; int32_t wait_ms; } ecs_http_send_queue_t; typedef struct ecs_http_request_key_t { const char *array; ecs_size_t count; } ecs_http_request_key_t; typedef struct ecs_http_request_entry_t { char *content; int32_t content_length; int code; double time; } ecs_http_request_entry_t; /* HTTP server struct */ struct ecs_http_server_t { bool should_run; bool running; ecs_http_socket_t sock; ecs_os_mutex_t lock; ecs_os_thread_t thread; ecs_http_reply_action_t callback; void *ctx; double cache_timeout; double cache_purge_timeout; ecs_sparse_t connections; /* sparse */ ecs_sparse_t requests; /* sparse */ bool initialized; uint16_t port; const char *ipaddr; double dequeue_timeout; /* used to not lock request queue too often */ double stats_timeout; /* used for periodic reporting of statistics */ double request_time; /* time spent on requests in last stats interval */ double request_time_total; /* total time spent on requests */ int32_t requests_processed; /* requests processed in last stats interval */ int32_t requests_processed_total; /* total requests processed */ int32_t dequeue_count; /* number of dequeues in last stats interval */ ecs_http_send_queue_t send_queue; ecs_hashmap_t request_cache; }; /** Fragment state, used by HTTP request parser */ typedef enum { HttpFragStateBegin, HttpFragStateMethod, HttpFragStatePath, HttpFragStateVersion, HttpFragStateHeaderStart, HttpFragStateHeaderName, HttpFragStateHeaderValueStart, HttpFragStateHeaderValue, HttpFragStateCR, HttpFragStateCRLF, HttpFragStateCRLFCR, HttpFragStateBody, HttpFragStateDone } HttpFragState; /** A fragment is a partially received HTTP request */ typedef struct { HttpFragState state; ecs_strbuf_t buf; ecs_http_method_t method; int32_t body_offset; int32_t query_offset; int32_t header_offsets[ECS_HTTP_HEADER_COUNT_MAX]; int32_t header_value_offsets[ECS_HTTP_HEADER_COUNT_MAX]; int32_t header_count; int32_t param_offsets[ECS_HTTP_QUERY_PARAM_COUNT_MAX]; int32_t param_value_offsets[ECS_HTTP_QUERY_PARAM_COUNT_MAX]; int32_t param_count; int32_t content_length; char *header_buf_ptr; char header_buf[32]; bool parse_content_length; bool invalid; } ecs_http_fragment_t; /** Extend public connection type with fragment data */ typedef struct { ecs_http_connection_t pub; ecs_http_socket_t sock; /* Connection is purged after both timeout expires and connection has * exceeded retry count. This ensures that a connection does not immediately * timeout when a frame takes longer than usual */ double dequeue_timeout; int32_t dequeue_retries; } ecs_http_connection_impl_t; typedef struct { ecs_http_request_t pub; uint64_t conn_id; /* for sanity check */ char *res; int32_t req_len; } ecs_http_request_impl_t; static ecs_size_t http_send( ecs_http_socket_t sock, const void *buf, ecs_size_t size, int flags) { ecs_assert(size >= 0, ECS_INTERNAL_ERROR, NULL); #ifdef ECS_TARGET_POSIX ssize_t send_bytes = send(sock, buf, flecs_itosize(size), flags | MSG_NOSIGNAL); return flecs_itoi32(send_bytes); #else int send_bytes = send(sock, buf, size, flags); return flecs_itoi32(send_bytes); #endif } static ecs_size_t http_recv( ecs_http_socket_t sock, void *buf, ecs_size_t size, int flags) { ecs_size_t ret; #ifdef ECS_TARGET_POSIX ssize_t recv_bytes = recv(sock, buf, flecs_itosize(size), flags); ret = flecs_itoi32(recv_bytes); #else int recv_bytes = recv(sock, buf, size, flags); ret = flecs_itoi32(recv_bytes); #endif if (ret == -1) { ecs_dbg("recv failed: %s (sock = %d)", ecs_os_strerror(errno), sock); } else if (ret == 0) { ecs_dbg("recv: received 0 bytes (sock = %d)", sock); } return ret; } static void http_sock_set_timeout( ecs_http_socket_t sock, int32_t timeout_ms) { int r; #ifdef ECS_TARGET_POSIX struct timeval tv; tv.tv_sec = timeout_ms * 1000; tv.tv_usec = 0; r = setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof tv); #else DWORD t = (DWORD)timeout_ms; r = setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&t, sizeof t); #endif if (r) { ecs_warn("http: failed to set socket timeout: %s", ecs_os_strerror(errno)); } } static void http_sock_keep_alive( ecs_http_socket_t sock) { int v = 1; if (setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (const char*)&v, sizeof v)) { ecs_warn("http: failed to set socket KEEPALIVE: %s", ecs_os_strerror(errno)); } } static void http_sock_nonblock(ecs_http_socket_t sock, bool enable) { (void)sock; (void)enable; #ifdef ECS_TARGET_POSIX int flags; flags = fcntl(sock,F_GETFL,0); if (flags == -1) { ecs_warn("http: failed to set socket NONBLOCK: %s", ecs_os_strerror(errno)); return; } if (enable) { flags = fcntl(sock, F_SETFL, flags | O_NONBLOCK); } else { flags = fcntl(sock, F_SETFL, flags & ~O_NONBLOCK); } if (flags == -1) { ecs_warn("http: failed to set socket NONBLOCK: %s", ecs_os_strerror(errno)); return; } #endif } static int http_getnameinfo( const struct sockaddr* addr, ecs_size_t addr_len, char *host, ecs_size_t host_len, char *port, ecs_size_t port_len, int flags) { ecs_assert(addr_len > 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(host_len > 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(port_len > 0, ECS_INTERNAL_ERROR, NULL); #if defined(ECS_TARGET_WINDOWS) return getnameinfo(addr, addr_len, host, flecs_ito(uint32_t, host_len), port, flecs_ito(uint32_t, port_len), flags); #else return getnameinfo(addr, flecs_ito(uint32_t, addr_len), host, flecs_ito(uint32_t, host_len), port, flecs_ito(uint32_t, port_len), flags); #endif } static int http_bind( ecs_http_socket_t sock, const struct sockaddr* addr, ecs_size_t addr_len) { ecs_assert(addr_len > 0, ECS_INTERNAL_ERROR, NULL); #if defined(ECS_TARGET_WINDOWS) return bind(sock, addr, addr_len); #else return bind(sock, addr, flecs_ito(uint32_t, addr_len)); #endif } static bool http_socket_is_valid( ecs_http_socket_t sock) { #if defined(ECS_TARGET_WINDOWS) return sock != INVALID_SOCKET; #else return sock >= 0; #endif } #if defined(ECS_TARGET_WINDOWS) #define HTTP_SOCKET_INVALID INVALID_SOCKET #else #define HTTP_SOCKET_INVALID (-1) #endif static void http_close( ecs_http_socket_t *sock) { ecs_assert(sock != NULL, ECS_INTERNAL_ERROR, NULL); #if defined(ECS_TARGET_WINDOWS) closesocket(*sock); #else ecs_dbg_2("http: closing socket %u", *sock); shutdown(*sock, SHUT_RDWR); close(*sock); #endif *sock = HTTP_SOCKET_INVALID; } static ecs_http_socket_t http_accept( ecs_http_socket_t sock, struct sockaddr* addr, ecs_size_t *addr_len) { socklen_t len = (socklen_t)addr_len[0]; ecs_http_socket_t result = accept(sock, addr, &len); addr_len[0] = (ecs_size_t)len; return result; } static void http_reply_fini(ecs_http_reply_t* reply) { ecs_assert(reply != NULL, ECS_INTERNAL_ERROR, NULL); ecs_os_free(reply->body.content); } static void http_request_fini(ecs_http_request_impl_t *req) { ecs_assert(req != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(req->pub.conn != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(req->pub.conn->server != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(req->pub.conn->id == req->conn_id, ECS_INTERNAL_ERROR, NULL); ecs_os_free(req->res); flecs_sparse_remove_t(&req->pub.conn->server->requests, ecs_http_request_impl_t, req->pub.id); } static void http_connection_free(ecs_http_connection_impl_t *conn) { ecs_assert(conn != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(conn->pub.id != 0, ECS_INTERNAL_ERROR, NULL); uint64_t conn_id = conn->pub.id; if (http_socket_is_valid(conn->sock)) { http_close(&conn->sock); } flecs_sparse_remove_t(&conn->pub.server->connections, ecs_http_connection_impl_t, conn_id); } // https://stackoverflow.com/questions/10156409/convert-hex-string-char-to-int static char http_hex_2_int(char a, char b){ a = (a <= '9') ? (char)(a - '0') : (char)((a & 0x7) + 9); b = (b <= '9') ? (char)(b - '0') : (char)((b & 0x7) + 9); return (char)((a << 4) + b); } static void http_decode_url_str( char *str) { char ch, *ptr, *dst = str; for (ptr = str; (ch = *ptr); ptr++) { if (ch == '%') { dst[0] = http_hex_2_int(ptr[1], ptr[2]); dst ++; ptr += 2; } else { dst[0] = ptr[0]; dst ++; } } dst[0] = '\0'; } static void http_parse_method( ecs_http_fragment_t *frag) { char *method = ecs_strbuf_get_small(&frag->buf); if (!ecs_os_strcmp(method, "GET")) frag->method = EcsHttpGet; else if (!ecs_os_strcmp(method, "POST")) frag->method = EcsHttpPost; else if (!ecs_os_strcmp(method, "PUT")) frag->method = EcsHttpPut; else if (!ecs_os_strcmp(method, "DELETE")) frag->method = EcsHttpDelete; else if (!ecs_os_strcmp(method, "OPTIONS")) frag->method = EcsHttpOptions; else { frag->method = EcsHttpMethodUnsupported; frag->invalid = true; } ecs_strbuf_reset(&frag->buf); } static bool http_header_writable( ecs_http_fragment_t *frag) { return frag->header_count < ECS_HTTP_HEADER_COUNT_MAX; } static void http_header_buf_reset( ecs_http_fragment_t *frag) { frag->header_buf[0] = '\0'; frag->header_buf_ptr = frag->header_buf; } static void http_header_buf_append( ecs_http_fragment_t *frag, char ch) { if ((frag->header_buf_ptr - frag->header_buf) < ECS_SIZEOF(frag->header_buf)) { frag->header_buf_ptr[0] = ch; frag->header_buf_ptr ++; } else { frag->header_buf_ptr[0] = '\0'; } } static uint64_t http_request_key_hash(const void *ptr) { const ecs_http_request_key_t *key = ptr; const char *array = key->array; int32_t count = key->count; return flecs_hash(array, count * ECS_SIZEOF(char)); } static int http_request_key_compare(const void *ptr_1, const void *ptr_2) { const ecs_http_request_key_t *type_1 = ptr_1; const ecs_http_request_key_t *type_2 = ptr_2; int32_t count_1 = type_1->count; int32_t count_2 = type_2->count; if (count_1 != count_2) { return (count_1 > count_2) - (count_1 < count_2); } return ecs_os_memcmp(type_1->array, type_2->array, count_1); } static ecs_http_request_entry_t* http_find_request_entry( ecs_http_server_t *srv, const char *array, int32_t count) { ecs_http_request_key_t key; key.array = array; key.count = count; ecs_time_t t = {0, 0}; ecs_http_request_entry_t *entry = flecs_hashmap_get( &srv->request_cache, &key, ecs_http_request_entry_t); if (entry) { double tf = ecs_time_measure(&t); if ((tf - entry->time) < srv->cache_timeout) { return entry; } } return NULL; } static void http_insert_request_entry( ecs_http_server_t *srv, ecs_http_request_impl_t *req, ecs_http_reply_t *reply) { int32_t content_length = ecs_strbuf_written(&reply->body); if (!content_length) { return; } ecs_http_request_key_t key; key.array = req->res; key.count = req->req_len; ecs_http_request_entry_t *entry = flecs_hashmap_get( &srv->request_cache, &key, ecs_http_request_entry_t); if (!entry) { flecs_hashmap_result_t elem = flecs_hashmap_ensure( &srv->request_cache, &key, ecs_http_request_entry_t); ecs_http_request_key_t *elem_key = elem.key; elem_key->array = ecs_os_memdup_n(key.array, char, key.count); entry = elem.value; } else { ecs_os_free(entry->content); } ecs_time_t t = {0, 0}; entry->time = ecs_time_measure(&t); entry->content_length = ecs_strbuf_written(&reply->body); entry->content = ecs_strbuf_get(&reply->body); entry->code = reply->code; ecs_strbuf_appendstrn(&reply->body, entry->content, entry->content_length); } static char* http_decode_request( ecs_http_request_impl_t *req, ecs_http_fragment_t *frag) { ecs_os_zeromem(req); ecs_size_t req_len = frag->buf.length; char *res = ecs_strbuf_get(&frag->buf); if (!res) { return NULL; } req->pub.method = frag->method; req->pub.path = res + 1; http_decode_url_str(req->pub.path); if (frag->body_offset) { req->pub.body = &res[frag->body_offset]; } int32_t i, count = frag->header_count; for (i = 0; i < count; i ++) { req->pub.headers[i].key = &res[frag->header_offsets[i]]; req->pub.headers[i].value = &res[frag->header_value_offsets[i]]; } count = frag->param_count; for (i = 0; i < count; i ++) { req->pub.params[i].key = &res[frag->param_offsets[i]]; req->pub.params[i].value = &res[frag->param_value_offsets[i]]; /* Safe, member is only const so that end-user can't change it */ http_decode_url_str(ECS_CONST_CAST(char*, req->pub.params[i].value)); } req->pub.header_count = frag->header_count; req->pub.param_count = frag->param_count; req->res = res; req->req_len = frag->header_offsets[0]; if (!req->req_len) { req->req_len = req_len; } return res; } static ecs_http_request_entry_t* http_enqueue_request( ecs_http_connection_impl_t *conn, uint64_t conn_id, ecs_http_fragment_t *frag) { ecs_http_server_t *srv = conn->pub.server; ecs_os_mutex_lock(srv->lock); bool is_alive = conn->pub.id == conn_id; if (!is_alive || frag->invalid) { /* Don't enqueue invalid requests or requests for purged connections */ ecs_strbuf_reset(&frag->buf); } else { ecs_http_request_impl_t req; char *res = http_decode_request(&req, frag); if (res) { req.pub.conn = (ecs_http_connection_t*)conn; /* Check cache for GET requests */ if (frag->method == EcsHttpGet) { ecs_http_request_entry_t *entry = http_find_request_entry(srv, res, frag->header_offsets[0]); if (entry) { /* If an entry is found, don't enqueue a request. Instead * return the cached response immediately. */ ecs_os_free(res); return entry; } } ecs_http_request_impl_t *req_ptr = flecs_sparse_add_t( &srv->requests, ecs_http_request_impl_t); *req_ptr = req; req_ptr->pub.id = flecs_sparse_last_id(&srv->requests); req_ptr->conn_id = conn->pub.id; ecs_os_linc(&ecs_http_request_received_count); } } ecs_os_mutex_unlock(srv->lock); return NULL; } static bool http_parse_request( ecs_http_fragment_t *frag, const char* req_frag, ecs_size_t req_frag_len) { int32_t i; for (i = 0; i < req_frag_len; i++) { char c = req_frag[i]; switch (frag->state) { case HttpFragStateBegin: ecs_os_memset_t(frag, 0, ecs_http_fragment_t); frag->state = HttpFragStateMethod; frag->header_buf_ptr = frag->header_buf; /* fall through */ case HttpFragStateMethod: if (c == ' ') { http_parse_method(frag); ecs_strbuf_reset(&frag->buf); frag->state = HttpFragStatePath; frag->buf.content = NULL; } else { ecs_strbuf_appendch(&frag->buf, c); } break; case HttpFragStatePath: if (c == ' ') { frag->state = HttpFragStateVersion; ecs_strbuf_appendch(&frag->buf, '\0'); } else { if (c == '?' || c == '=' || c == '&') { ecs_strbuf_appendch(&frag->buf, '\0'); int32_t offset = ecs_strbuf_written(&frag->buf); if (c == '?' || c == '&') { frag->param_offsets[frag->param_count] = offset; } else { frag->param_value_offsets[frag->param_count] = offset; frag->param_count ++; } } else { ecs_strbuf_appendch(&frag->buf, c); } } break; case HttpFragStateVersion: if (c == '\r') { frag->state = HttpFragStateCR; } /* version is not stored */ break; case HttpFragStateHeaderStart: if (http_header_writable(frag)) { frag->header_offsets[frag->header_count] = ecs_strbuf_written(&frag->buf); } http_header_buf_reset(frag); frag->state = HttpFragStateHeaderName; /* fall through */ case HttpFragStateHeaderName: if (c == ':') { frag->state = HttpFragStateHeaderValueStart; http_header_buf_append(frag, '\0'); frag->parse_content_length = !ecs_os_strcmp( frag->header_buf, "Content-Length"); if (http_header_writable(frag)) { ecs_strbuf_appendch(&frag->buf, '\0'); frag->header_value_offsets[frag->header_count] = ecs_strbuf_written(&frag->buf); } } else if (c == '\r') { frag->state = HttpFragStateCR; } else { http_header_buf_append(frag, c); if (http_header_writable(frag)) { ecs_strbuf_appendch(&frag->buf, c); } } break; case HttpFragStateHeaderValueStart: http_header_buf_reset(frag); frag->state = HttpFragStateHeaderValue; if (c == ' ') { /* skip first space */ break; } /* fall through */ case HttpFragStateHeaderValue: if (c == '\r') { if (frag->parse_content_length) { http_header_buf_append(frag, '\0'); int32_t len = atoi(frag->header_buf); if (len < 0) { frag->invalid = true; } else { frag->content_length = len; } frag->parse_content_length = false; } if (http_header_writable(frag)) { int32_t cur = ecs_strbuf_written(&frag->buf); if (frag->header_offsets[frag->header_count] < cur && frag->header_value_offsets[frag->header_count] < cur) { ecs_strbuf_appendch(&frag->buf, '\0'); frag->header_count ++; } } frag->state = HttpFragStateCR; } else { if (frag->parse_content_length) { http_header_buf_append(frag, c); } if (http_header_writable(frag)) { ecs_strbuf_appendch(&frag->buf, c); } } break; case HttpFragStateCR: if (c == '\n') { frag->state = HttpFragStateCRLF; } else { frag->state = HttpFragStateHeaderStart; } break; case HttpFragStateCRLF: if (c == '\r') { frag->state = HttpFragStateCRLFCR; } else { frag->state = HttpFragStateHeaderStart; i--; } break; case HttpFragStateCRLFCR: if (c == '\n') { if (frag->content_length != 0) { frag->body_offset = ecs_strbuf_written(&frag->buf); frag->state = HttpFragStateBody; } else { frag->state = HttpFragStateDone; } } else { frag->state = HttpFragStateHeaderStart; } break; case HttpFragStateBody: { ecs_strbuf_appendch(&frag->buf, c); if ((ecs_strbuf_written(&frag->buf) - frag->body_offset) == frag->content_length) { frag->state = HttpFragStateDone; } } break; case HttpFragStateDone: break; } } if (frag->state == HttpFragStateDone) { return true; } else { return false; } } static ecs_http_send_request_t* http_send_queue_post( ecs_http_server_t *srv) { /* This function should only be called while the server is locked. Before * the lock is released, the returned element should be populated. */ ecs_http_send_queue_t *sq = &srv->send_queue; int32_t next = (sq->head + 1) % ECS_HTTP_SEND_QUEUE_MAX; if (next == sq->tail) { return NULL; } /* Don't enqueue new requests if server is shutting down */ if (!srv->should_run) { return NULL; } /* Return element at end of the queue */ ecs_http_send_request_t *result = &sq->requests[sq->head]; sq->head = next; return result; } static ecs_http_send_request_t* http_send_queue_get( ecs_http_server_t *srv) { ecs_os_mutex_lock(srv->lock); ecs_http_send_queue_t *sq = &srv->send_queue; if (sq->tail == sq->head) { return NULL; } int32_t next = (sq->tail + 1) % ECS_HTTP_SEND_QUEUE_MAX; ecs_http_send_request_t *result = &sq->requests[sq->tail]; sq->tail = next; return result; } static void* http_server_send_queue(void* arg) { ecs_http_server_t *srv = arg; int32_t wait_ms = srv->send_queue.wait_ms; /* Run for as long as the server is running or there are messages. When the * server is stopping, no new messages will be enqueued */ while (srv->should_run || (srv->send_queue.head != srv->send_queue.tail)) { ecs_http_send_request_t* r = http_send_queue_get(srv); if (!r) { ecs_os_mutex_unlock(srv->lock); /* If the queue is empty, wait so we don't run too fast */ if (srv->should_run) { ecs_os_sleep(0, wait_ms * 1000 * 1000); } } else { ecs_http_socket_t sock = r->sock; char *headers = r->headers; int32_t headers_length = r->header_length; char *content = r->content; int32_t content_length = r->content_length; ecs_os_mutex_unlock(srv->lock); if (http_socket_is_valid(sock)) { bool error = false; http_sock_nonblock(sock, false); /* Write headers */ ecs_size_t written = http_send(sock, headers, headers_length, 0); if (written != headers_length) { ecs_err("http: failed to write HTTP response headers: %s", ecs_os_strerror(errno)); ecs_os_linc(&ecs_http_send_error_count); error = true; } else if (content_length >= 0) { /* Write content */ written = http_send(sock, content, content_length, 0); if (written != content_length) { ecs_err("http: failed to write HTTP response body: %s", ecs_os_strerror(errno)); ecs_os_linc(&ecs_http_send_error_count); error = true; } } if (!error) { ecs_os_linc(&ecs_http_send_ok_count); } http_close(&sock); } else { ecs_err("http: invalid socket\n"); } ecs_os_free(content); ecs_os_free(headers); } } return NULL; } static void http_append_send_headers( ecs_strbuf_t *hdrs, int code, const char* status, const char* content_type, ecs_strbuf_t *extra_headers, ecs_size_t content_len, bool preflight) { ecs_strbuf_appendlit(hdrs, "HTTP/1.1 "); ecs_strbuf_appendint(hdrs, code); ecs_strbuf_appendch(hdrs, ' '); ecs_strbuf_appendstr(hdrs, status); ecs_strbuf_appendlit(hdrs, "\r\n"); if (content_type) { ecs_strbuf_appendlit(hdrs, "Content-Type: "); ecs_strbuf_appendstr(hdrs, content_type); ecs_strbuf_appendlit(hdrs, "\r\n"); } if (content_len >= 0) { ecs_strbuf_appendlit(hdrs, "Content-Length: "); ecs_strbuf_append(hdrs, "%d", content_len); ecs_strbuf_appendlit(hdrs, "\r\n"); } ecs_strbuf_appendlit(hdrs, "Access-Control-Allow-Origin: *\r\n"); if (preflight) { ecs_strbuf_appendlit(hdrs, "Access-Control-Allow-Private-Network: true\r\n"); ecs_strbuf_appendlit(hdrs, "Access-Control-Allow-Methods: GET, PUT, DELETE, OPTIONS\r\n"); ecs_strbuf_appendlit(hdrs, "Access-Control-Max-Age: 600\r\n"); } ecs_strbuf_mergebuff(hdrs, extra_headers); ecs_strbuf_appendlit(hdrs, "\r\n"); } static void http_send_reply( ecs_http_connection_impl_t* conn, ecs_http_reply_t* reply, bool preflight) { ecs_strbuf_t hdrs = ECS_STRBUF_INIT; int32_t content_length = reply->body.length; char *content = ecs_strbuf_get(&reply->body); /* Use asynchronous send queue for outgoing data so send operations won't * hold up main thread */ ecs_http_send_request_t *req = NULL; if (!preflight) { req = http_send_queue_post(conn->pub.server); if (!req) { reply->code = 503; /* queue full, server is busy */ ecs_os_linc(&ecs_http_busy_count); } } http_append_send_headers(&hdrs, reply->code, reply->status, reply->content_type, &reply->headers, content_length, preflight); ecs_size_t headers_length = ecs_strbuf_written(&hdrs); char *headers = ecs_strbuf_get(&hdrs); if (!req) { ecs_size_t written = http_send(conn->sock, headers, headers_length, 0); if (written != headers_length) { ecs_err("http: failed to send reply to '%s:%s': %s", conn->pub.host, conn->pub.port, ecs_os_strerror(errno)); ecs_os_linc(&ecs_http_send_error_count); } ecs_os_free(content); ecs_os_free(headers); http_close(&conn->sock); return; } /* Second, enqueue send request for response body */ req->sock = conn->sock; req->headers = headers; req->header_length = headers_length; req->content = content; req->content_length = content_length; /* Take ownership of values */ reply->body.content = NULL; conn->sock = HTTP_SOCKET_INVALID; } static void http_recv_connection( ecs_http_server_t *srv, ecs_http_connection_impl_t *conn, uint64_t conn_id, ecs_http_socket_t sock) { ecs_size_t bytes_read; char recv_buf[ECS_HTTP_SEND_RECV_BUFFER_SIZE]; ecs_http_fragment_t frag = {0}; int32_t retries = 0; do { if ((bytes_read = http_recv( sock, recv_buf, ECS_SIZEOF(recv_buf), 0)) > 0) { bool is_alive = conn->pub.id == conn_id; if (!is_alive) { /* Connection has been purged by main thread */ goto done; } if (http_parse_request(&frag, recv_buf, bytes_read)) { if (frag.method == EcsHttpOptions) { ecs_http_reply_t reply; reply.body = ECS_STRBUF_INIT; reply.code = 200; reply.content_type = NULL; reply.headers = ECS_STRBUF_INIT; reply.status = "OK"; http_send_reply(conn, &reply, true); ecs_os_linc(&ecs_http_request_preflight_count); } else { ecs_http_request_entry_t *entry = http_enqueue_request(conn, conn_id, &frag); if (entry) { ecs_http_reply_t reply; reply.body = ECS_STRBUF_INIT; reply.code = entry->code; reply.content_type = "application/json"; reply.headers = ECS_STRBUF_INIT; reply.status = "OK"; ecs_strbuf_appendstrn(&reply.body, entry->content, entry->content_length); http_send_reply(conn, &reply, false); http_connection_free(conn); /* Lock was transferred from enqueue_request */ ecs_os_mutex_unlock(srv->lock); } } } else { ecs_os_linc(&ecs_http_request_invalid_count); } } ecs_os_sleep(0, 10 * 1000 * 1000); } while ((bytes_read == -1) && (++retries < ECS_HTTP_REQUEST_RECV_RETRY)); if (retries == ECS_HTTP_REQUEST_RECV_RETRY) { http_close(&sock); } done: ecs_strbuf_reset(&frag.buf); } typedef struct { ecs_http_connection_impl_t *conn; uint64_t id; } http_conn_res_t; static http_conn_res_t http_init_connection( ecs_http_server_t *srv, ecs_http_socket_t sock_conn, struct sockaddr_storage *remote_addr, ecs_size_t remote_addr_len) { http_sock_set_timeout(sock_conn, 100); http_sock_keep_alive(sock_conn); http_sock_nonblock(sock_conn, true); /* Create new connection */ ecs_os_mutex_lock(srv->lock); ecs_http_connection_impl_t *conn = flecs_sparse_add_t( &srv->connections, ecs_http_connection_impl_t); uint64_t conn_id = conn->pub.id = flecs_sparse_last_id(&srv->connections); conn->pub.server = srv; conn->sock = sock_conn; ecs_os_mutex_unlock(srv->lock); char *remote_host = conn->pub.host; char *remote_port = conn->pub.port; /* Fetch name & port info */ if (http_getnameinfo((struct sockaddr*) remote_addr, remote_addr_len, remote_host, ECS_SIZEOF(conn->pub.host), remote_port, ECS_SIZEOF(conn->pub.port), NI_NUMERICHOST | NI_NUMERICSERV)) { ecs_os_strcpy(remote_host, "unknown"); ecs_os_strcpy(remote_port, "unknown"); } ecs_dbg_2("http: connection established from '%s:%s' (socket %u)", remote_host, remote_port, sock_conn); return (http_conn_res_t){ .conn = conn, .id = conn_id }; } static 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->code = 404; reply->status = "Resource not found"; ecs_os_linc(&ecs_http_request_not_handled_count); } else { if (reply->code >= 400) { ecs_os_linc(&ecs_http_request_handled_error_count); } else { ecs_os_linc(&ecs_http_request_handled_ok_count); } } error: return; } static void http_handle_request( ecs_http_server_t *srv, ecs_http_request_impl_t *req) { ecs_http_reply_t reply = ECS_HTTP_REPLY_INIT; ecs_http_connection_impl_t *conn = (ecs_http_connection_impl_t*)req->pub.conn; if (req->pub.method != EcsHttpOptions) { if (srv->callback((ecs_http_request_t*)req, &reply, srv->ctx) == false) { reply.code = 404; reply.status = "Resource not found"; ecs_os_linc(&ecs_http_request_not_handled_count); } else { if (reply.code >= 400) { ecs_os_linc(&ecs_http_request_handled_error_count); } else { ecs_os_linc(&ecs_http_request_handled_ok_count); } } if (req->pub.method == EcsHttpGet) { http_insert_request_entry(srv, req, &reply); } http_send_reply(conn, &reply, false); ecs_dbg_2("http: reply sent to '%s:%s'", conn->pub.host, conn->pub.port); } else { /* Already taken care of */ } http_reply_fini(&reply); http_request_fini(req); http_connection_free(conn); } static void http_purge_request_cache( ecs_http_server_t *srv, bool fini) { ecs_time_t t = {0, 0}; double time = ecs_time_measure(&t); ecs_map_iter_t it = ecs_map_iter(&srv->request_cache.impl); while (ecs_map_next(&it)) { ecs_hm_bucket_t *bucket = ecs_map_ptr(&it); int32_t i, count = ecs_vec_count(&bucket->values); ecs_http_request_key_t *keys = ecs_vec_first(&bucket->keys); ecs_http_request_entry_t *entries = ecs_vec_first(&bucket->values); for (i = count - 1; i >= 0; i --) { ecs_http_request_entry_t *entry = &entries[i]; if (fini || ((time - entry->time) > srv->cache_purge_timeout)) { ecs_http_request_key_t *key = &keys[i]; /* Safe, code owns the value */ ecs_os_free(ECS_CONST_CAST(char*, key->array)); ecs_os_free(entry->content); flecs_hm_bucket_remove(&srv->request_cache, bucket, ecs_map_key(&it), i); } } } if (fini) { flecs_hashmap_fini(&srv->request_cache); } } static int32_t http_dequeue_requests( ecs_http_server_t *srv, double delta_time) { ecs_os_mutex_lock(srv->lock); int32_t i, request_count = flecs_sparse_count(&srv->requests); for (i = request_count - 1; i >= 1; i --) { ecs_http_request_impl_t *req = flecs_sparse_get_dense_t( &srv->requests, ecs_http_request_impl_t, i); http_handle_request(srv, req); } int32_t connections_count = flecs_sparse_count(&srv->connections); for (i = connections_count - 1; i >= 1; i --) { ecs_http_connection_impl_t *conn = flecs_sparse_get_dense_t( &srv->connections, ecs_http_connection_impl_t, i); conn->dequeue_timeout += delta_time; conn->dequeue_retries ++; if ((conn->dequeue_timeout > (double)ECS_HTTP_CONNECTION_PURGE_TIMEOUT) && (conn->dequeue_retries > ECS_HTTP_CONNECTION_PURGE_RETRY_COUNT)) { ecs_dbg("http: purging connection '%s:%s' (sock = %d)", conn->pub.host, conn->pub.port, conn->sock); http_connection_free(conn); } } http_purge_request_cache(srv, false); ecs_os_mutex_unlock(srv->lock); return request_count - 1; } const char* ecs_http_get_header( const ecs_http_request_t* req, const char* name) { for (ecs_size_t i = 0; i < req->header_count; i++) { if (!ecs_os_strcmp(req->headers[i].key, name)) { return req->headers[i].value; } } return NULL; } const char* ecs_http_get_param( const ecs_http_request_t* req, const char* name) { for (ecs_size_t i = 0; i < req->param_count; i++) { if (!ecs_os_strcmp(req->params[i].key, name)) { return req->params[i].value; } } return NULL; } ecs_http_server_t* ecs_http_server_init( const ecs_http_server_desc_t *desc) { ecs_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, ecs_http_reply_t *reply_out) { const char *http_ver = " HTTP/1.1\r\n\r\n"; int32_t method_len = ecs_os_strlen(method); int32_t req_len = ecs_os_strlen(req); int32_t http_ver_len = ecs_os_strlen(http_ver); char reqbuf[1024], *reqstr = reqbuf; int32_t len = method_len + req_len + http_ver_len + 1; if (method_len + req_len + http_ver_len >= 1024) { reqstr = ecs_os_malloc(len + 1); } char *ptr = reqstr; ecs_os_memcpy(ptr, method, method_len); ptr += method_len; ptr[0] = ' '; ptr ++; ecs_os_memcpy(ptr, req, req_len); ptr += req_len; ecs_os_memcpy(ptr, http_ver, http_ver_len); ptr += http_ver_len; ptr[0] = '\n'; int result = ecs_http_server_http_request(srv, reqstr, len, reply_out); if (reqbuf != reqstr) { ecs_os_free(reqstr); } return result; } void* ecs_http_server_ctx( ecs_http_server_t* srv) { return srv->ctx; } #endif /** * @file addons/journal.c * @brief Journal addon. */ #ifdef FLECS_JOURNAL static char* flecs_journal_entitystr( ecs_world_t *world, ecs_entity_t entity) { char *path; const char *_path = ecs_get_symbol(world, entity); if (_path && !strchr(_path, '.')) { path = flecs_asprintf("#[blue]%s", _path); } else { uint32_t gen = entity >> 32; if (gen) { path = flecs_asprintf("#[normal]_%u_%u", (uint32_t)entity, gen); } else { path = flecs_asprintf("#[normal]_%u", (uint32_t)entity); } } return path; } static char* flecs_journal_idstr( ecs_world_t *world, ecs_id_t id) { if (ECS_IS_PAIR(id)) { char *first_path = flecs_journal_entitystr(world, ecs_pair_first(world, id)); char *second_path = flecs_journal_entitystr(world, ecs_pair_second(world, id)); char *result = flecs_asprintf("#[cyan]ecs_pair#[normal](%s, %s)", first_path, second_path); ecs_os_free(first_path); ecs_os_free(second_path); return result; } else if (!(id & ECS_ID_FLAGS_MASK)) { return flecs_journal_entitystr(world, id); } else { return ecs_id_str(world, id); } } static int flecs_journal_sp = 0; void flecs_journal_begin( ecs_world_t *world, ecs_journal_kind_t kind, ecs_entity_t entity, ecs_type_t *add, ecs_type_t *remove) { flecs_journal_sp ++; if (ecs_os_api.log_level_ < FLECS_JOURNAL_LOG_LEVEL) { return; } char *path = NULL; char *var_id = NULL; if (entity) { if (kind != EcsJournalDeleteWith && kind != EcsJournalRemoveAll) { path = ecs_get_path(world, entity); var_id = flecs_journal_entitystr(world, entity); } else { path = ecs_id_str(world, entity); var_id = flecs_journal_idstr(world, entity); } } if (kind == EcsJournalNew) { ecs_print(4, "#[magenta]#ifndef #[normal]_var_%s", var_id); ecs_print(4, "#[magenta]#define #[normal]_var_%s", var_id); ecs_print(4, "#[green]ecs_entity_t %s;", var_id); ecs_print(4, "#[magenta]#endif"); ecs_print(4, "%s = #[cyan]ecs_new_id#[reset](world); " "#[grey] // %s = new()", var_id, path); } if (add) { for (int i = 0; i < add->count; i ++) { char *jidstr = flecs_journal_idstr(world, add->array[i]); char *idstr = ecs_id_str(world, add->array[i]); ecs_print(4, "#[cyan]ecs_add_id#[reset](world, %s, %s); " "#[grey] // add(%s, %s)", var_id, jidstr, path, idstr); ecs_os_free(idstr); ecs_os_free(jidstr); } } if (remove) { for (int i = 0; i < remove->count; i ++) { char *jidstr = flecs_journal_idstr(world, remove->array[i]); char *idstr = ecs_id_str(world, remove->array[i]); ecs_print(4, "#[cyan]ecs_remove_id#[reset](world, %s, %s); " "#[grey] // remove(%s, %s)", var_id, jidstr, path, idstr); ecs_os_free(idstr); ecs_os_free(jidstr); } } if (kind == EcsJournalClear) { ecs_print(4, "#[cyan]ecs_clear#[reset](world, %s); " "#[grey] // clear(%s)", var_id, path); } else if (kind == EcsJournalDelete) { ecs_print(4, "#[cyan]ecs_delete#[reset](world, %s); " "#[grey] // delete(%s)", var_id, path); } else if (kind == EcsJournalDeleteWith) { ecs_print(4, "#[cyan]ecs_delete_with#[reset](world, %s); " "#[grey] // delete_with(%s)", var_id, path); } else if (kind == EcsJournalRemoveAll) { ecs_print(4, "#[cyan]ecs_remove_all#[reset](world, %s); " "#[grey] // remove_all(%s)", var_id, path); } else if (kind == EcsJournalTableEvents) { ecs_print(4, "#[cyan]ecs_run_aperiodic#[reset](world, " "EcsAperiodicEmptyTables);"); } ecs_os_free(var_id); ecs_os_free(path); ecs_log_push(); } void flecs_journal_end(void) { flecs_journal_sp --; ecs_assert(flecs_journal_sp >= 0, ECS_INTERNAL_ERROR, NULL); ecs_log_pop(); } #endif /** * @file addons/log.c * @brief Log addon. */ #ifdef FLECS_LOG #include void flecs_colorize_buf( char *msg, bool enable_colors, ecs_strbuf_t *buf) { ecs_assert(msg != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(buf != NULL, ECS_INTERNAL_ERROR, NULL); char *ptr, ch, prev = '\0'; bool isNum = false; char isStr = '\0'; bool isVar = false; bool overrideColor = false; bool autoColor = true; bool dontAppend = false; for (ptr = msg; (ch = *ptr); ptr++) { dontAppend = false; if (!overrideColor) { if (isNum && !isdigit(ch) && !isalpha(ch) && (ch != '.') && (ch != '%')) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); isNum = false; } if (isStr && (isStr == ch) && prev != '\\') { isStr = '\0'; } else if (((ch == '\'') || (ch == '"')) && !isStr && !isalpha(prev) && (prev != '\\')) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_CYAN); isStr = ch; } if ((isdigit(ch) || (ch == '%' && isdigit(prev)) || (ch == '-' && isdigit(ptr[1]))) && !isNum && !isStr && !isVar && !isalpha(prev) && !isdigit(prev) && (prev != '_') && (prev != '.')) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_GREEN); isNum = true; } if (isVar && !isalpha(ch) && !isdigit(ch) && ch != '_') { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); isVar = false; } if (!isStr && !isVar && ch == '$' && isalpha(ptr[1])) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_CYAN); isVar = true; } } if (!isVar && !isStr && !isNum && ch == '#' && ptr[1] == '[') { bool isColor = true; overrideColor = true; /* Custom colors */ if (!ecs_os_strncmp(&ptr[2], "]", ecs_os_strlen("]"))) { autoColor = false; } else if (!ecs_os_strncmp(&ptr[2], "green]", ecs_os_strlen("green]"))) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_GREEN); } else if (!ecs_os_strncmp(&ptr[2], "red]", ecs_os_strlen("red]"))) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_RED); } else if (!ecs_os_strncmp(&ptr[2], "blue]", ecs_os_strlen("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); } } void ecs_parser_errorv_( const char *name, const char *expr, int64_t column_arg, const char *fmt, va_list args) { if (column_arg > 65536) { /* Limit column size, which prevents the code from throwing up when the * function is called with (expr - ptr), and expr is NULL. */ column_arg = 0; } int32_t column = flecs_itoi32(column_arg); if (ecs_os_api.log_level_ >= -2) { ecs_strbuf_t msg_buf = ECS_STRBUF_INIT; /* Count number of newlines up until column_arg */ int32_t i, line = 1; if (expr) { for (i = 0; i < column; i ++) { if (expr[i] == '\n') { line ++; } } ecs_strbuf_append(&msg_buf, "%d: ", line); } ecs_strbuf_vappend(&msg_buf, fmt, args); if (expr) { ecs_strbuf_appendch(&msg_buf, '\n'); /* Find start of line by taking column and looking for the * last occurring newline */ if (column != -1) { const char *ptr = &expr[column]; if (ptr[0] == '\n') { ptr --; } while (ptr[0] != '\n' && ptr > expr) { ptr --; } if (ptr[0] == '\n') { ptr ++; } if (ptr == expr) { /* ptr is already at start of line */ } else { column -= (int32_t)(ptr - expr); expr = ptr; } } /* Strip newlines from current statement, if any */ char *newline_ptr = strchr(expr, '\n'); if (newline_ptr) { /* Strip newline from expr */ ecs_strbuf_appendstrn(&msg_buf, expr, (int32_t)(newline_ptr - expr)); } else { ecs_strbuf_appendstr(&msg_buf, expr); } ecs_strbuf_appendch(&msg_buf, '\n'); if (column != -1) { int32_t c; for (c = 0; c < column; c ++) { ecs_strbuf_appendch(&msg_buf, ' '); } ecs_strbuf_appendch(&msg_buf, '^'); } } char *msg = ecs_strbuf_get(&msg_buf); ecs_os_err(name, 0, msg); ecs_os_free(msg); } } void ecs_parser_error_( const char *name, const char *expr, int64_t column, const char *fmt, ...) { if (ecs_os_api.log_level_ >= -2) { va_list args; va_start(args, fmt); ecs_parser_errorv_(name, expr, column, fmt, args); va_end(args); } } void ecs_abort_( int32_t err, const char *file, int32_t line, const char *fmt, ...) { if (fmt) { va_list args; va_start(args, fmt); char *msg = flecs_vasprintf(fmt, args); va_end(args); ecs_fatal_(file, line, "%s (%s)", msg, ecs_strerror(err)); ecs_os_free(msg); } else { ecs_fatal_(file, line, "%s", ecs_strerror(err)); } ecs_os_api.log_last_error_ = err; } void ecs_assert_log_( int32_t err, const char *cond_str, const char *file, int32_t line, const char *fmt, ...) { if (fmt) { va_list args; va_start(args, fmt); char *msg = flecs_vasprintf(fmt, args); va_end(args); ecs_fatal_(file, line, "assert: %s %s (%s)", cond_str, msg, ecs_strerror(err)); ecs_os_free(msg); } else { ecs_fatal_(file, line, "assert: %s %s", cond_str, ecs_strerror(err)); } ecs_os_api.log_last_error_ = err; } void ecs_deprecated_( const char *file, int32_t line, const char *msg) { ecs_err_(file, line, "%s", msg); } bool ecs_should_log(int32_t level) { # if !defined(FLECS_LOG_3) if (level == 3) { return false; } # endif # if !defined(FLECS_LOG_2) if (level == 2) { return false; } # endif # if !defined(FLECS_LOG_1) if (level == 1) { return false; } # endif return level <= ecs_os_api.log_level_; } #define ECS_ERR_STR(code) case code: return &(#code[4]) const char* ecs_strerror( int32_t error_code) { switch (error_code) { ECS_ERR_STR(ECS_INVALID_PARAMETER); ECS_ERR_STR(ECS_NOT_A_COMPONENT); ECS_ERR_STR(ECS_INTERNAL_ERROR); ECS_ERR_STR(ECS_ALREADY_DEFINED); ECS_ERR_STR(ECS_INVALID_COMPONENT_SIZE); ECS_ERR_STR(ECS_INVALID_COMPONENT_ALIGNMENT); ECS_ERR_STR(ECS_NAME_IN_USE); ECS_ERR_STR(ECS_OUT_OF_MEMORY); ECS_ERR_STR(ECS_DOUBLE_FREE); ECS_ERR_STR(ECS_OPERATION_FAILED); ECS_ERR_STR(ECS_INVALID_CONVERSION); ECS_ERR_STR(ECS_MODULE_UNDEFINED); ECS_ERR_STR(ECS_MISSING_SYMBOL); ECS_ERR_STR(ECS_ALREADY_IN_USE); ECS_ERR_STR(ECS_CYCLE_DETECTED); ECS_ERR_STR(ECS_LEAK_DETECTED); ECS_ERR_STR(ECS_COLUMN_INDEX_OUT_OF_RANGE); ECS_ERR_STR(ECS_COLUMN_IS_NOT_SHARED); ECS_ERR_STR(ECS_COLUMN_IS_SHARED); ECS_ERR_STR(ECS_COLUMN_TYPE_MISMATCH); ECS_ERR_STR(ECS_INVALID_WHILE_READONLY); ECS_ERR_STR(ECS_INVALID_FROM_WORKER); ECS_ERR_STR(ECS_OUT_OF_RANGE); ECS_ERR_STR(ECS_MISSING_OS_API); ECS_ERR_STR(ECS_UNSUPPORTED); ECS_ERR_STR(ECS_ACCESS_VIOLATION); ECS_ERR_STR(ECS_COMPONENT_NOT_REGISTERED); ECS_ERR_STR(ECS_INCONSISTENT_COMPONENT_ID); ECS_ERR_STR(ECS_INCONSISTENT_NAME); ECS_ERR_STR(ECS_INCONSISTENT_COMPONENT_ACTION); ECS_ERR_STR(ECS_INVALID_OPERATION); ECS_ERR_STR(ECS_CONSTRAINT_VIOLATED); ECS_ERR_STR(ECS_LOCKED_STORAGE); ECS_ERR_STR(ECS_ID_IN_USE); } return "unknown error code"; } #else /* Empty bodies for when logging is disabled */ void ecs_log_( int32_t level, const char *file, int32_t line, const char *fmt, ...) { (void)level; (void)file; (void)line; (void)fmt; } void ecs_parser_error_( const char *name, const char *expr, int64_t column, const char *fmt, ...) { (void)name; (void)expr; (void)column; (void)fmt; } void ecs_parser_errorv_( const char *name, const char *expr, int64_t column, const char *fmt, va_list args) { (void)name; (void)expr; (void)column; (void)fmt; (void)args; } void ecs_abort_( int32_t error_code, const char *file, int32_t line, const char *fmt, ...) { (void)error_code; (void)file; (void)line; (void)fmt; } void ecs_assert_log_( int32_t error_code, const char *condition_str, const char *file, int32_t line, const char *fmt, ...) { (void)error_code; (void)condition_str; (void)file; (void)line; (void)fmt; } #endif int ecs_log_get_level(void) { return ecs_os_api.log_level_; } int ecs_log_set_level( int level) { int prev = 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 = ecs_childof(metric) }); ecs_os_free(to_snake_case); ecs_set(world, mbr, EcsMember, { .type = ecs_id(ecs_f64_t), .unit = EcsSeconds }); /* Truncate upper 32 bits of target so we can lookup the offset * with the id we get from the pair */ ecs_map_ensure(&ctx->target_offset, (uint32_t)tgt)[0] = offset; offset += sizeof(double); } } ctx->size = flecs_uto(ecs_size_t, offset); ecs_observer(world, { .entity = metric, .events = { EcsMonitor }, .query.terms[0] = { .id = desc->id }, .callback = flecs_metrics_on_oneof_metric, .yield_existing = true, .ctx = ctx }); ecs_set(world, metric, EcsMetricOneOf, { .ctx = ctx }); ecs_add_pair(world, metric, EcsMetric, desc->kind); ecs_add_id(world, metric, EcsMetric); return 0; error: return -1; } static int flecs_count_id_targets_metric_init( ecs_world_t *world, ecs_entity_t metric, const ecs_metric_desc_t *desc) { ecs_count_targets_metric_ctx_t *ctx = ecs_os_calloc_t(ecs_count_targets_metric_ctx_t); ctx->metric.metric = metric; ctx->metric.kind = desc->kind; ctx->idr = flecs_id_record_ensure(world, desc->id); ecs_check(ctx->idr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_map_init(&ctx->targets, NULL); ecs_set(world, metric, EcsMetricCountTargets, { .ctx = ctx }); ecs_add_pair(world, metric, EcsMetric, desc->kind); ecs_add_id(world, metric, EcsMetric); return 0; error: return -1; } static int flecs_count_ids_metric_init( ecs_world_t *world, ecs_entity_t metric, const ecs_metric_desc_t *desc) { ecs_set(world, metric, EcsMetricCountIds, { .id = desc->id }); ecs_set(world, metric, EcsMetricValue, { .value = 0 }); return 0; } ecs_entity_t ecs_metric_init( ecs_world_t *world, const ecs_metric_desc_t *desc) { ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, "ecs_metric_desc_t was not initialized to zero"); flecs_poly_assert(world, ecs_world_t); ecs_entity_t result = desc->entity; if (!result) { result = ecs_new(world); } ecs_entity_t kind = desc->kind; if (!kind) { ecs_err("missing metric kind"); goto error; } if (kind != EcsGauge && kind != EcsCounter && kind != EcsCounterId && kind != EcsCounterIncrement) { ecs_err("invalid metric kind %s", ecs_get_path(world, kind)); goto error; } if (kind == EcsCounterIncrement && !desc->member && !desc->dotmember) { ecs_err("CounterIncrement can only be used in combination with member"); goto error; } if (kind == EcsCounterId && (desc->member || desc->dotmember)) { ecs_err("CounterId cannot be used in combination with member"); goto error; } if (desc->brief) { #ifdef FLECS_DOC ecs_doc_set_brief(world, result, desc->brief); #else ecs_warn("FLECS_DOC is not enabled, ignoring metrics brief"); #endif } if (desc->member || desc->dotmember) { if (flecs_member_metric_init(world, result, desc)) { goto error; } } else if (desc->id) { if (desc->targets) { if (!ecs_id_is_pair(desc->id)) { ecs_err("cannot specify targets for id that is not a pair"); goto error; } if (ECS_PAIR_FIRST(desc->id) == EcsWildcard) { ecs_err("first element of pair cannot be wildcard with " " targets enabled"); goto error; } if (ECS_PAIR_SECOND(desc->id) != EcsWildcard) { ecs_err("second element of pair must be wildcard with " " targets enabled"); goto error; } if (kind == EcsCounterId) { if (flecs_count_id_targets_metric_init(world, result, desc)) { goto error; } } else { ecs_entity_t first = ecs_pair_first(world, desc->id); ecs_entity_t scope = flecs_get_oneof(world, first); if (!scope) { ecs_err("first element of pair must have OneOf with " " targets enabled"); goto error; } if (flecs_oneof_metric_init(world, result, scope, desc)) { goto error; } } } else { if (kind == EcsCounterId) { if (flecs_count_ids_metric_init(world, result, desc)) { goto error; } } else { if (flecs_id_metric_init(world, result, desc)) { goto error; } } } } else { ecs_err("missing source specified for metric"); goto error; } return result; error: if (result && result != desc->entity) { ecs_delete(world, result); } return 0; } void FlecsMetricsImport(ecs_world_t *world) { ECS_MODULE_DEFINE(world, FlecsMetrics); ECS_IMPORT(world, FlecsPipeline); ECS_IMPORT(world, FlecsMeta); ECS_IMPORT(world, FlecsUnits); ecs_set_name_prefix(world, "Ecs"); ECS_TAG_DEFINE(world, EcsMetric); ecs_entity_t old_scope = ecs_set_scope(world, EcsMetric); ECS_TAG_DEFINE(world, EcsCounter); ECS_TAG_DEFINE(world, EcsCounterIncrement); ECS_TAG_DEFINE(world, EcsCounterId); ECS_TAG_DEFINE(world, EcsGauge); ecs_set_scope(world, old_scope); ecs_set_name_prefix(world, "EcsMetric"); ECS_TAG_DEFINE(world, EcsMetricInstance); ECS_COMPONENT_DEFINE(world, EcsMetricValue); ECS_COMPONENT_DEFINE(world, EcsMetricSource); ECS_COMPONENT_DEFINE(world, EcsMetricMemberInstance); ECS_COMPONENT_DEFINE(world, EcsMetricIdInstance); ECS_COMPONENT_DEFINE(world, EcsMetricOneOfInstance); ECS_COMPONENT_DEFINE(world, EcsMetricMember); ECS_COMPONENT_DEFINE(world, EcsMetricId); ECS_COMPONENT_DEFINE(world, EcsMetricOneOf); ECS_COMPONENT_DEFINE(world, EcsMetricCountIds); ECS_COMPONENT_DEFINE(world, EcsMetricCountTargets); ecs_add_id(world, ecs_id(EcsMetricMemberInstance), EcsPrivate); ecs_add_id(world, ecs_id(EcsMetricIdInstance), EcsPrivate); ecs_add_id(world, ecs_id(EcsMetricOneOfInstance), EcsPrivate); ecs_struct(world, { .entity = ecs_id(EcsMetricValue), .members = { { .name = "value", .type = ecs_id(ecs_f64_t) } } }); ecs_struct(world, { .entity = ecs_id(EcsMetricSource), .members = { { .name = "entity", .type = ecs_id(ecs_entity_t) } } }); ecs_set_hooks(world, EcsMetricMember, { .ctor = flecs_default_ctor, .dtor = ecs_dtor(EcsMetricMember), .move = ecs_move(EcsMetricMember) }); ecs_set_hooks(world, EcsMetricId, { .ctor = flecs_default_ctor, .dtor = ecs_dtor(EcsMetricId), .move = ecs_move(EcsMetricId) }); ecs_set_hooks(world, EcsMetricOneOf, { .ctor = flecs_default_ctor, .dtor = ecs_dtor(EcsMetricOneOf), .move = ecs_move(EcsMetricOneOf) }); ecs_set_hooks(world, EcsMetricCountTargets, { .ctor = flecs_default_ctor, .dtor = ecs_dtor(EcsMetricCountTargets), .move = ecs_move(EcsMetricCountTargets) }); ecs_add_id(world, EcsMetric, EcsOneOf); #ifdef FLECS_DOC ECS_OBSERVER(world, SetMetricDocName, EcsOnSet, Source); #endif ECS_SYSTEM(world, ClearMetricInstance, EcsPreStore, [in] Source); ECS_SYSTEM(world, UpdateGaugeMemberInstance, EcsPreStore, [out] Value, [in] MemberInstance, [none] (Metric, Gauge)); ECS_SYSTEM(world, UpdateCounterMemberInstance, EcsPreStore, [out] Value, [in] MemberInstance, [none] (Metric, Counter)); ECS_SYSTEM(world, UpdateCounterIncrementMemberInstance, EcsPreStore, [out] Value, [in] MemberInstance, [none] (Metric, CounterIncrement)); ECS_SYSTEM(world, UpdateGaugeIdInstance, EcsPreStore, [out] Value, [in] IdInstance, [none] (Metric, Gauge)); ECS_SYSTEM(world, UpdateCounterIdInstance, EcsPreStore, [inout] Value, [in] IdInstance, [none] (Metric, Counter)); ECS_SYSTEM(world, UpdateGaugeOneOfInstance, EcsPreStore, [none] (_, Value), [in] OneOfInstance, [none] (Metric, Gauge)); ECS_SYSTEM(world, UpdateCounterOneOfInstance, EcsPreStore, [none] (_, Value), [in] OneOfInstance, [none] (Metric, Counter)); ECS_SYSTEM(world, UpdateCountIds, EcsPreStore, [inout] CountIds, Value); ECS_SYSTEM(world, UpdateCountTargets, EcsPreStore, [inout] CountTargets); } #endif /** * @file addons/module.c * @brief Module addon. */ #ifdef FLECS_MODULE #include char* flecs_module_path_from_c( const char *c_name) { ecs_strbuf_t str = ECS_STRBUF_INIT; const char *ptr; char ch; for (ptr = c_name; (ch = *ptr); ptr++) { if (isupper(ch)) { ch = flecs_ito(char, tolower(ch)); if (ptr != c_name) { ecs_strbuf_appendstrn(&str, ".", 1); } } ecs_strbuf_appendstrn(&str, &ch, 1); } return ecs_strbuf_get(&str); } ecs_entity_t ecs_import( ecs_world_t *world, ecs_module_action_t module, const char *module_name) { ecs_check(!(world->flags & EcsWorldReadonly), ECS_INVALID_WHILE_READONLY, NULL); ecs_entity_t old_scope = ecs_set_scope(world, 0); const char *old_name_prefix = world->info.name_prefix; char *path = flecs_module_path_from_c(module_name); ecs_entity_t e = ecs_lookup(world, path); ecs_os_free(path); if (!e) { ecs_trace("#[magenta]import#[reset] %s", module_name); ecs_log_push(); /* Load module */ module(world); /* Lookup module entity (must be registered by module) */ e = ecs_lookup(world, module_name); ecs_check(e != 0, ECS_MODULE_UNDEFINED, module_name); ecs_log_pop(); } /* Restore to previous state */ ecs_set_scope(world, old_scope); world->info.name_prefix = old_name_prefix; return e; error: return 0; } ecs_entity_t ecs_import_c( ecs_world_t *world, ecs_module_action_t module, const char *c_name) { char *name = flecs_module_path_from_c(c_name); ecs_entity_t e = ecs_import(world, module, name); ecs_os_free(name); return e; } ecs_entity_t ecs_import_from_library( ecs_world_t *world, const char *library_name, const char *module_name) { ecs_check(library_name != NULL, ECS_INVALID_PARAMETER, NULL); char *import_func = ECS_CONST_CAST(char*, module_name); char *module = ECS_CONST_CAST(char*, module_name); if (!ecs_os_has_modules() || !ecs_os_has_dl()) { ecs_err( "library loading not supported, set module_to_dl, dlopen, dlclose " "and dlproc os API callbacks first"); return 0; } /* If no module name is specified, try default naming convention for loading * the main module from the library */ if (!import_func) { import_func = ecs_os_malloc(ecs_os_strlen(library_name) + ECS_SIZEOF("Import")); ecs_assert(import_func != NULL, ECS_OUT_OF_MEMORY, NULL); const char *ptr; char ch, *bptr = import_func; bool capitalize = true; for (ptr = library_name; (ch = *ptr); ptr ++) { if (ch == '.') { capitalize = true; } else { if (capitalize) { *bptr = flecs_ito(char, toupper(ch)); bptr ++; capitalize = false; } else { *bptr = flecs_ito(char, tolower(ch)); bptr ++; } } } *bptr = '\0'; module = ecs_os_strdup(import_func); ecs_assert(module != NULL, ECS_OUT_OF_MEMORY, NULL); ecs_os_strcat(bptr, "Import"); } char *library_filename = ecs_os_module_to_dl(library_name); if (!library_filename) { ecs_err("failed to find library file for '%s'", library_name); if (module != module_name) { ecs_os_free(module); } return 0; } else { ecs_trace("found file '%s' for library '%s'", library_filename, library_name); } ecs_os_dl_t dl = ecs_os_dlopen(library_filename); if (!dl) { ecs_err("failed to load library '%s' ('%s')", library_name, library_filename); ecs_os_free(library_filename); if (module != module_name) { ecs_os_free(module); } return 0; } else { ecs_trace("library '%s' ('%s') loaded", library_name, library_filename); } ecs_module_action_t action = (ecs_module_action_t) ecs_os_dlproc(dl, import_func); if (!action) { ecs_err("failed to load import function %s from library %s", import_func, library_name); ecs_os_free(library_filename); ecs_os_dlclose(dl); return 0; } else { ecs_trace("found import function '%s' in library '%s' for module '%s'", import_func, library_name, module); } /* Do not free id, as it will be stored as the component identifier */ ecs_entity_t result = ecs_import(world, action, module); if (import_func != module_name) { ecs_os_free(import_func); } if (module != module_name) { ecs_os_free(module); } ecs_os_free(library_filename); return result; error: return 0; } ecs_entity_t ecs_module_init( ecs_world_t *world, const char *c_name, const ecs_component_desc_t *desc) { ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); flecs_poly_assert(world, ecs_world_t); ecs_entity_t old_scope = ecs_set_scope(world, 0); ecs_entity_t e = desc->entity; if (!e) { char *module_path = flecs_module_path_from_c(c_name); e = ecs_entity(world, { .name = module_path }); ecs_set_symbol(world, e, module_path); ecs_os_free(module_path); } else if (!ecs_exists(world, e)) { char *module_path = flecs_module_path_from_c(c_name); ecs_make_alive(world, e); ecs_add_fullpath(world, e, module_path); ecs_set_symbol(world, e, module_path); ecs_os_free(module_path); } ecs_add_id(world, e, EcsModule); ecs_component_desc_t private_desc = *desc; private_desc.entity = e; if (desc->type.size) { ecs_entity_t result = ecs_component_init(world, &private_desc); ecs_assert(result != 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(result == e, ECS_INTERNAL_ERROR, NULL); (void)result; } ecs_set_scope(world, old_scope); return e; error: return 0; } #endif /** * @file addons/rest.c * @brief Rest addon. */ /** * @file addons/pipeline/pipeline.h * @brief Internal functions/types for pipeline addon. */ #ifndef FLECS_PIPELINE_PRIVATE_H #define FLECS_PIPELINE_PRIVATE_H /** Instruction data for pipeline. * This type is the element type in the "ops" vector of a pipeline. */ typedef struct ecs_pipeline_op_t { int32_t offset; /* Offset in systems vector */ int32_t count; /* Number of systems to run before next op */ double time_spent; /* Time spent merging commands for sync point */ int64_t commands_enqueued; /* Number of commands enqueued for sync point */ bool multi_threaded; /* Whether systems can be ran multi threaded */ bool immediate; /* Whether systems are staged or not */ } ecs_pipeline_op_t; struct ecs_pipeline_state_t { ecs_query_t *query; /* Pipeline query */ ecs_vec_t ops; /* Pipeline schedule */ ecs_vec_t systems; /* Vector with system ids */ ecs_entity_t last_system; /* Last system ran by pipeline */ ecs_id_record_t *idr_inactive; /* Cached record for quick inactive test */ int32_t match_count; /* Used to track of rebuild is necessary */ int32_t rebuild_count; /* Number of pipeline rebuilds */ ecs_iter_t *iters; /* Iterator for worker(s) */ int32_t iter_count; /* Members for continuing pipeline iteration after pipeline rebuild */ ecs_pipeline_op_t *cur_op; /* Current pipeline op */ int32_t cur_i; /* Index in current result */ int32_t ran_since_merge; /* Index in current op */ bool immediate; /* Is pipeline in readonly mode */ }; typedef struct EcsPipeline { /* Stable ptr so threads can safely access while entity/components move */ ecs_pipeline_state_t *state; } EcsPipeline; //////////////////////////////////////////////////////////////////////////////// //// Pipeline API //////////////////////////////////////////////////////////////////////////////// bool flecs_pipeline_update( ecs_world_t *world, ecs_pipeline_state_t *pq, bool start_of_frame); void flecs_run_pipeline( ecs_world_t *world, ecs_pipeline_state_t *pq, ecs_ftime_t delta_time); int32_t flecs_run_pipeline_ops( ecs_world_t* world, ecs_stage_t* stage, int32_t stage_index, int32_t stage_count, ecs_ftime_t delta_time); //////////////////////////////////////////////////////////////////////////////// //// Worker API //////////////////////////////////////////////////////////////////////////////// void flecs_workers_progress( ecs_world_t *world, ecs_pipeline_state_t *pq, ecs_ftime_t delta_time); void flecs_create_worker_threads( ecs_world_t *world); void flecs_join_worker_threads( ecs_world_t *world); void flecs_signal_workers( ecs_world_t *world); void flecs_wait_for_sync( ecs_world_t *world); #endif #ifdef FLECS_REST /* Retain captured commands for one minute at 60 FPS */ #define FLECS_REST_COMMAND_RETAIN_COUNT (60 * 60) static ECS_TAG_DECLARE(EcsRestPlecs); typedef struct { ecs_world_t *world; ecs_http_server_t *srv; int32_t rc; ecs_map_t cmd_captures; double last_time; } ecs_rest_ctx_t; typedef struct { char *cmds; ecs_time_t start_time; ecs_strbuf_t buf; } ecs_rest_cmd_sync_capture_t; typedef struct { ecs_vec_t syncs; } ecs_rest_cmd_capture_t; static ECS_COPY(EcsRest, dst, src, { ecs_rest_ctx_t *impl = src->impl; if (impl) { impl->rc ++; } ecs_os_strset(&dst->ipaddr, src->ipaddr); dst->port = src->port; dst->impl = impl; }) static ECS_MOVE(EcsRest, dst, src, { *dst = *src; src->ipaddr = NULL; src->impl = NULL; }) static ECS_DTOR(EcsRest, ptr, { ecs_rest_ctx_t *impl = ptr->impl; if (impl) { impl->rc --; if (!impl->rc) { ecs_rest_server_fini(impl->srv); } } ecs_os_free(ptr->ipaddr); }) static char *rest_last_err; static ecs_os_api_log_t rest_prev_log; static void flecs_rest_capture_log( int32_t level, const char *file, int32_t line, const char *msg) { (void)file; (void)line; #ifdef FLECS_DEBUG if (level < 0) { /* Also log to previous log function in debug mode */ if (rest_prev_log) { ecs_log_enable_colors(true); rest_prev_log(level, file, line, msg); ecs_log_enable_colors(false); } } #endif if (!rest_last_err && level < 0) { rest_last_err = ecs_os_strdup(msg); } } static char* flecs_rest_get_captured_log(void) { char *result = rest_last_err; rest_last_err = NULL; return result; } static void flecs_reply_verror( ecs_http_reply_t *reply, const char *fmt, va_list args) { ecs_strbuf_appendlit(&reply->body, "{\"error\":\""); ecs_strbuf_vappend(&reply->body, fmt, args); ecs_strbuf_appendlit(&reply->body, "\"}"); } static void flecs_reply_error( ecs_http_reply_t *reply, const char *fmt, ...) { va_list args; va_start(args, fmt); flecs_reply_verror(reply, fmt, args); va_end(args); } static void flecs_rest_bool_param( const ecs_http_request_t *req, const char *name, bool *value_out) { const char *value = ecs_http_get_param(req, name); if (value) { if (!ecs_os_strcmp(value, "true")) { value_out[0] = true; } else { value_out[0] = false; } } } static void flecs_rest_int_param( const ecs_http_request_t *req, const char *name, int32_t *value_out) { const char *value = ecs_http_get_param(req, name); if (value) { *value_out = atoi(value); } } static void flecs_rest_string_param( const ecs_http_request_t *req, const char *name, char **value_out) { const char *value = ecs_http_get_param(req, name); if (value) { *value_out = ECS_CONST_CAST(char*, value); } } static void flecs_rest_parse_json_ser_entity_params( ecs_world_t *world, ecs_entity_to_json_desc_t *desc, const ecs_http_request_t *req) { flecs_rest_bool_param(req, "entity_id", &desc->serialize_entity_id); flecs_rest_bool_param(req, "doc", &desc->serialize_doc); flecs_rest_bool_param(req, "full_paths", &desc->serialize_full_paths); flecs_rest_bool_param(req, "inherited", &desc->serialize_inherited); flecs_rest_bool_param(req, "values", &desc->serialize_values); flecs_rest_bool_param(req, "builtin", &desc->serialize_builtin); flecs_rest_bool_param(req, "type_info", &desc->serialize_type_info); flecs_rest_bool_param(req, "matches", &desc->serialize_matches); flecs_rest_bool_param(req, "alerts", &desc->serialize_alerts); char *rel = NULL; flecs_rest_string_param(req, "refs", &rel); if (rel) { desc->serialize_refs = ecs_lookup(world, rel); } } static void flecs_rest_parse_json_ser_iter_params( ecs_iter_to_json_desc_t *desc, const ecs_http_request_t *req) { flecs_rest_bool_param(req, "entity_ids", &desc->serialize_entity_ids); flecs_rest_bool_param(req, "doc", &desc->serialize_doc); flecs_rest_bool_param(req, "full_paths", &desc->serialize_full_paths); flecs_rest_bool_param(req, "inherited", &desc->serialize_inherited); flecs_rest_bool_param(req, "values", &desc->serialize_values); flecs_rest_bool_param(req, "builtin", &desc->serialize_builtin); flecs_rest_bool_param(req, "type_info", &desc->serialize_type_info); flecs_rest_bool_param(req, "field_info", &desc->serialize_field_info); flecs_rest_bool_param(req, "query_info", &desc->serialize_query_info); flecs_rest_bool_param(req, "query_plan", &desc->serialize_query_plan); flecs_rest_bool_param(req, "query_profile", &desc->serialize_query_profile); flecs_rest_bool_param(req, "table", &desc->serialize_table); flecs_rest_bool_param(req, "fields", &desc->serialize_fields); bool results = true; flecs_rest_bool_param(req, "results", &results); desc->dont_serialize_results = !results; } static bool flecs_rest_get_entity( ecs_world_t *world, const ecs_http_request_t* req, ecs_http_reply_t *reply) { char *path = &req->path[7]; ecs_dbg_2("rest: request entity '%s'", path); ecs_entity_t e = ecs_lookup_path_w_sep( world, 0, path, "/", NULL, false); if (!e) { ecs_dbg_2("rest: entity '%s' not found", path); flecs_reply_error(reply, "entity '%s' not found", path); reply->code = 404; return true; } ecs_entity_to_json_desc_t desc = ECS_ENTITY_TO_JSON_INIT; flecs_rest_parse_json_ser_entity_params(world, &desc, req); if (ecs_entity_to_json_buf(world, e, &reply->body, &desc) != 0) { ecs_strbuf_reset(&reply->body); reply->code = 500; reply->status = "Internal server error"; return true; } return true; } static bool flecs_rest_put_entity( ecs_world_t *world, ecs_http_reply_t *reply, const char *path) { ecs_dbg_2("rest: create entity '%s'", path); ecs_entity_t result = ecs_entity(world, { .name = path, .sep = "/" }); if (!result) { ecs_dbg_2("rest: failed to create entity '%s'", path); flecs_reply_error(reply, "failed to create entity '%s'", path); reply->code = 500; return true; } ecs_strbuf_appendlit(&reply->body, "{\"id\":\""); ecs_strbuf_appendint(&reply->body, (uint32_t)result); ecs_strbuf_appendlit(&reply->body, "\"}"); return true; } static bool flecs_rest_get_world( ecs_world_t *world, const ecs_http_request_t* req, ecs_http_reply_t *reply) { (void)req; if (ecs_world_to_json_buf(world, &reply->body, NULL) != 0) { ecs_strbuf_reset(&reply->body); reply->code = 500; reply->status = "Internal server error"; return true; } return true; } static ecs_entity_t flecs_rest_entity_from_path( ecs_world_t *world, ecs_http_reply_t *reply, const char *path) { ecs_entity_t e = ecs_lookup_path_w_sep( world, 0, path, "/", NULL, false); if (!e) { flecs_reply_error(reply, "entity '%s' not found", path); reply->code = 404; } return e; } static bool flecs_rest_get_component( ecs_world_t *world, const ecs_http_request_t* req, ecs_http_reply_t *reply, const char *path) { ecs_entity_t e; if (!(e = flecs_rest_entity_from_path(world, reply, path))) { return true; } const char *component = ecs_http_get_param(req, "component"); if (!component) { flecs_reply_error(reply, "missing component for remove endpoint"); reply->code = 400; return true; } ecs_entity_t id; if (!flecs_id_parse(world, path, component, &id)) { flecs_reply_error(reply, "unresolved component '%s'", component); reply->code = 400; return true; } ecs_entity_t type = ecs_get_typeid(world, id); if (!type) { flecs_reply_error(reply, "component '%s' is not a type", component); reply->code = 400; return true; } const void *ptr = ecs_get_id(world, e, id); if (!ptr) { flecs_reply_error(reply, "failed to get component '%s'", component); reply->code = 500; return true; } ecs_ptr_to_json_buf(world, type, ptr, &reply->body); return true; } static bool flecs_rest_put_component( ecs_world_t *world, const ecs_http_request_t* req, ecs_http_reply_t *reply, const char *path) { ecs_entity_t e; if (!(e = flecs_rest_entity_from_path(world, reply, path))) { return true; } const char *component = ecs_http_get_param(req, "component"); if (!component) { flecs_reply_error(reply, "missing component for remove endpoint"); reply->code = 400; return true; } ecs_entity_t id; if (!flecs_id_parse(world, path, component, &id)) { flecs_reply_error(reply, "unresolved component '%s'", component); reply->code = 400; return true; } const char *data = ecs_http_get_param(req, "value"); if (!data) { ecs_add_id(world, e, id); return true; } ecs_entity_t type = ecs_get_typeid(world, id); if (!type) { flecs_reply_error(reply, "component '%s' is not a type", component); reply->code = 400; return true; } void *ptr = ecs_ensure_id(world, e, id); if (!ptr) { flecs_reply_error(reply, "failed to create component '%s'", component); reply->code = 500; return true; } if (!ecs_ptr_from_json(world, type, ptr, data, NULL)) { flecs_reply_error(reply, "invalid value for component '%s'", component); reply->code = 400; return true; } ecs_modified_id(world, e, id); return true; } static bool flecs_rest_delete_component( ecs_world_t *world, const ecs_http_request_t* req, ecs_http_reply_t *reply, const char *path) { ecs_entity_t e; if (!(e = flecs_rest_entity_from_path(world, reply, path))) { return true; } const char *component = ecs_http_get_param(req, "component"); if (!component) { flecs_reply_error(reply, "missing component for remove endpoint"); reply->code = 400; return true; } ecs_entity_t id; if (!flecs_id_parse(world, path, component, &id)) { flecs_reply_error(reply, "unresolved component '%s'", component); reply->code = 400; return true; } ecs_remove_id(world, e, id); return true; } static bool flecs_rest_delete_entity( ecs_world_t *world, ecs_http_reply_t *reply, const char *path) { ecs_entity_t e; if (!(e = flecs_rest_entity_from_path(world, reply, path))) { return true; } ecs_delete(world, e); return true; } static bool flecs_rest_toggle( ecs_world_t *world, const ecs_http_request_t* req, ecs_http_reply_t *reply, const char *path) { ecs_entity_t e; if (!(e = flecs_rest_entity_from_path(world, reply, path))) { return true; } bool enable = true; flecs_rest_bool_param(req, "enable", &enable); const char *component = ecs_http_get_param(req, "component"); if (!component) { ecs_enable(world, e, enable); return true; } ecs_entity_t id; if (!flecs_id_parse(world, path, component, &id)) { flecs_reply_error(reply, "unresolved component '%s'", component); reply->code = 400; return true; } ecs_entity_t rel = 0; if (ECS_IS_PAIR(id)) { rel = ecs_pair_first(world, id); } else { rel = id & ECS_COMPONENT_MASK; } if (!ecs_has_id(world, rel, EcsCanToggle)) { flecs_reply_error(reply, "cannot toggle component '%s'", component); reply->code = 400; return true; } ecs_enable_id(world, e, id, enable); return true; } static bool flecs_rest_script( ecs_world_t *world, const ecs_http_request_t* req, ecs_http_reply_t *reply, const char *path) { (void)world; (void)req; (void)reply; #ifdef FLECS_SCRIPT ecs_entity_t script = flecs_rest_entity_from_path(world, reply, path); if (!script) { script = ecs_entity(world, { .name = path }); } const char *code = ecs_http_get_param(req, "code"); if (!code) { flecs_reply_error(reply, "missing data parameter"); return true; } bool try = false; flecs_rest_bool_param(req, "try", &try); bool prev_color = ecs_log_enable_colors(false); ecs_os_api_log_t prev_log = ecs_os_api.log_; rest_prev_log = try ? NULL : prev_log; ecs_os_api.log_ = flecs_rest_capture_log; script = ecs_script(world, { .entity = script, .code = code }); if (!script) { char *err = flecs_rest_get_captured_log(); char *escaped_err = flecs_astresc('"', err); if (escaped_err) { flecs_reply_error(reply, escaped_err); } else { flecs_reply_error(reply, "error parsing script"); } if (!try) { reply->code = 400; /* bad request */ } ecs_os_free(escaped_err); ecs_os_free(err); } ecs_os_api.log_ = prev_log; ecs_log_enable_colors(prev_color); return true; #else return false; #endif } static void flecs_rest_reply_set_captured_log( ecs_http_reply_t *reply) { char *err = flecs_rest_get_captured_log(); if (err) { char *escaped_err = flecs_astresc('"', err); flecs_reply_error(reply, escaped_err); ecs_os_free(escaped_err); ecs_os_free(err); } reply->code = 400; } static void flecs_rest_iter_to_reply( const ecs_http_request_t* req, ecs_http_reply_t *reply, ecs_poly_t *query, ecs_iter_t *it) { ecs_iter_to_json_desc_t desc = ECS_ITER_TO_JSON_INIT; flecs_rest_parse_json_ser_iter_params(&desc, req); desc.query = query; int32_t offset = 0; int32_t limit = 1000; flecs_rest_int_param(req, "offset", &offset); flecs_rest_int_param(req, "limit", &limit); if (offset < 0 || limit < 0) { flecs_reply_error(reply, "invalid offset/limit parameter"); return; } ecs_iter_t pit = ecs_page_iter(it, offset, limit); if (ecs_iter_to_json_buf(&pit, &reply->body, &desc)) { flecs_rest_reply_set_captured_log(reply); } flecs_rest_int_param(req, "offset", &offset); } static bool flecs_rest_reply_existing_query( ecs_world_t *world, const ecs_http_request_t* req, ecs_http_reply_t *reply, const char *name) { ecs_entity_t qe = ecs_lookup(world, name); if (!qe) { flecs_reply_error(reply, "unresolved identifier '%s'", name); reply->code = 404; return true; } ecs_query_t *q = NULL; const EcsPoly *poly_comp = ecs_get_pair(world, qe, EcsPoly, EcsQuery); if (!poly_comp) { poly_comp = ecs_get_pair(world, qe, EcsPoly, EcsObserver); if (poly_comp) { q = ((ecs_observer_t*)poly_comp->poly)->query; } else { flecs_reply_error(reply, "resolved identifier '%s' is not a query", name); reply->code = 400; return true; } } else { q = poly_comp->poly; } if (!q) { flecs_reply_error(reply, "query '%s' is not initialized", name); reply->code = 400; return true; } ecs_iter_t it = ecs_query_iter(world, q); ecs_dbg_2("rest: request query '%s'", name); bool prev_color = ecs_log_enable_colors(false); rest_prev_log = ecs_os_api.log_; ecs_os_api.log_ = flecs_rest_capture_log; const char *vars = ecs_http_get_param(req, "vars"); if (vars) { #ifdef FLECS_SCRIPT if (ecs_query_args_parse(q, &it, vars) == NULL) { flecs_rest_reply_set_captured_log(reply); return true; } #else flecs_reply_error(reply, "cannot parse query arg expression: script addon required"); reply->code = 400; return true; #endif } flecs_rest_iter_to_reply(req, reply, q, &it); ecs_os_api.log_ = rest_prev_log; ecs_log_enable_colors(prev_color); return true; } static bool flecs_rest_get_query( ecs_world_t *world, const ecs_http_request_t* req, ecs_http_reply_t *reply) { const char *q_name = ecs_http_get_param(req, "name"); if (q_name) { return flecs_rest_reply_existing_query(world, req, reply, q_name); } const char *expr = ecs_http_get_param(req, "expr"); if (!expr) { ecs_strbuf_appendlit(&reply->body, "Missing parameter 'expr'"); reply->code = 400; /* bad request */ return true; } bool try = false; flecs_rest_bool_param(req, "try", &try); ecs_dbg_2("rest: request query '%s'", expr); bool prev_color = ecs_log_enable_colors(false); ecs_os_api_log_t prev_log = ecs_os_api.log_; rest_prev_log = try ? NULL : prev_log; ecs_os_api.log_ = flecs_rest_capture_log; ecs_query_t *q = ecs_query(world, { .expr = expr }); if (!q) { flecs_rest_reply_set_captured_log(reply); if (try) { /* If client is trying queries, don't spam console with errors */ reply->code = 200; } } else { ecs_iter_t it = ecs_query_iter(world, q); flecs_rest_iter_to_reply(req, reply, q, &it); ecs_query_fini(q); } ecs_os_api.log_ = prev_log; ecs_log_enable_colors(prev_color); return true; } #ifdef FLECS_STATS static void flecs_rest_array_append_( ecs_strbuf_t *reply, const char *field, int32_t field_len, const ecs_float_t *values, int32_t t) { ecs_strbuf_list_appendch(reply, '"'); ecs_strbuf_appendstrn(reply, field, field_len); ecs_strbuf_appendlit(reply, "\":"); ecs_strbuf_list_push(reply, "[", ","); int32_t i; for (i = t + 1; i <= (t + ECS_STAT_WINDOW); i ++) { int32_t index = i % ECS_STAT_WINDOW; ecs_strbuf_list_next(reply); ecs_strbuf_appendflt(reply, (double)values[index], '"'); } ecs_strbuf_list_pop(reply, "]"); } #define flecs_rest_array_append(reply, field, values, t)\ flecs_rest_array_append_(reply, field, sizeof(field) - 1, values, t) static void flecs_rest_gauge_append( ecs_strbuf_t *reply, const ecs_metric_t *m, const char *field, int32_t field_len, int32_t t, const char *brief, int32_t brief_len) { ecs_strbuf_list_appendch(reply, '"'); ecs_strbuf_appendstrn(reply, field, field_len); ecs_strbuf_appendlit(reply, "\":"); ecs_strbuf_list_push(reply, "{", ","); flecs_rest_array_append(reply, "avg", m->gauge.avg, t); flecs_rest_array_append(reply, "min", m->gauge.min, t); flecs_rest_array_append(reply, "max", m->gauge.max, t); if (brief) { ecs_strbuf_list_appendlit(reply, "\"brief\":\""); ecs_strbuf_appendstrn(reply, brief, brief_len); ecs_strbuf_appendch(reply, '"'); } ecs_strbuf_list_pop(reply, "}"); } static void flecs_rest_counter_append( ecs_strbuf_t *reply, const ecs_metric_t *m, const char *field, int32_t field_len, int32_t t, const char *brief, int32_t brief_len) { flecs_rest_gauge_append(reply, m, field, field_len, t, brief, brief_len); } #define ECS_GAUGE_APPEND_T(reply, s, field, t, brief)\ flecs_rest_gauge_append(reply, &(s)->field, #field, sizeof(#field) - 1, t, brief, sizeof(brief) - 1) #define ECS_COUNTER_APPEND_T(reply, s, field, t, brief)\ flecs_rest_counter_append(reply, &(s)->field, #field, sizeof(#field) - 1, t, brief, sizeof(brief) - 1) #define ECS_GAUGE_APPEND(reply, s, field, brief)\ ECS_GAUGE_APPEND_T(reply, s, field, (s)->t, brief) #define ECS_COUNTER_APPEND(reply, s, field, brief)\ ECS_COUNTER_APPEND_T(reply, s, field, (s)->t, brief) static void flecs_world_stats_to_json( ecs_strbuf_t *reply, const EcsWorldStats *monitor_stats) { const ecs_world_stats_t *stats = &monitor_stats->stats; ecs_strbuf_list_push(reply, "{", ","); ECS_GAUGE_APPEND(reply, stats, entities.count, "Alive entity ids in the world"); ECS_GAUGE_APPEND(reply, stats, entities.not_alive_count, "Not alive entity ids in the world"); ECS_GAUGE_APPEND(reply, stats, performance.fps, "Frames per second"); ECS_COUNTER_APPEND(reply, stats, performance.frame_time, "Time spent in frame"); ECS_COUNTER_APPEND(reply, stats, performance.system_time, "Time spent on running systems in frame"); ECS_COUNTER_APPEND(reply, stats, performance.emit_time, "Time spent on notifying observers in frame"); ECS_COUNTER_APPEND(reply, stats, performance.merge_time, "Time spent on merging commands in frame"); ECS_COUNTER_APPEND(reply, stats, performance.rematch_time, "Time spent on revalidating query caches in frame"); ECS_COUNTER_APPEND(reply, stats, commands.add_count, "Add commands executed"); ECS_COUNTER_APPEND(reply, stats, commands.remove_count, "Remove commands executed"); ECS_COUNTER_APPEND(reply, stats, commands.delete_count, "Delete commands executed"); ECS_COUNTER_APPEND(reply, stats, commands.clear_count, "Clear commands executed"); ECS_COUNTER_APPEND(reply, stats, commands.set_count, "Set commands executed"); ECS_COUNTER_APPEND(reply, stats, commands.ensure_count, "Get_mut commands executed"); ECS_COUNTER_APPEND(reply, stats, commands.modified_count, "Modified commands executed"); ECS_COUNTER_APPEND(reply, stats, commands.other_count, "Misc commands executed"); ECS_COUNTER_APPEND(reply, stats, commands.discard_count, "Commands for already deleted entities"); ECS_COUNTER_APPEND(reply, stats, commands.batched_entity_count, "Entities with batched commands"); ECS_COUNTER_APPEND(reply, stats, commands.batched_count, "Number of commands batched"); ECS_COUNTER_APPEND(reply, stats, frame.merge_count, "Number of merges (sync points)"); ECS_COUNTER_APPEND(reply, stats, frame.pipeline_build_count, "Pipeline rebuilds (happen when systems become active/enabled)"); ECS_COUNTER_APPEND(reply, stats, frame.systems_ran, "Systems ran in frame"); ECS_COUNTER_APPEND(reply, stats, frame.observers_ran, "Number of times an observer was invoked in frame"); ECS_COUNTER_APPEND(reply, stats, frame.event_emit_count, "Events emitted in frame"); ECS_COUNTER_APPEND(reply, stats, frame.rematch_count, "Number of query cache revalidations"); ECS_GAUGE_APPEND(reply, stats, tables.count, "Tables in the world (including empty)"); ECS_GAUGE_APPEND(reply, stats, tables.empty_count, "Empty tables in the world"); ECS_COUNTER_APPEND(reply, stats, tables.create_count, "Number of new tables created"); ECS_COUNTER_APPEND(reply, stats, tables.delete_count, "Number of tables deleted"); ECS_GAUGE_APPEND(reply, stats, components.tag_count, "Tag ids in use"); ECS_GAUGE_APPEND(reply, stats, components.component_count, "Component ids in use"); ECS_GAUGE_APPEND(reply, stats, components.pair_count, "Pair ids in use"); ECS_GAUGE_APPEND(reply, stats, components.type_count, "Registered component types"); ECS_COUNTER_APPEND(reply, stats, components.create_count, "Number of new component, tag and pair ids created"); ECS_COUNTER_APPEND(reply, stats, components.delete_count, "Number of component, pair and tag ids deleted"); ECS_GAUGE_APPEND(reply, stats, queries.query_count, "Queries in the world"); ECS_GAUGE_APPEND(reply, stats, queries.observer_count, "Observers in the world"); ECS_GAUGE_APPEND(reply, stats, queries.system_count, "Systems in the world"); ECS_COUNTER_APPEND(reply, stats, memory.alloc_count, "Allocations by OS API"); ECS_COUNTER_APPEND(reply, stats, memory.realloc_count, "Reallocs by OS API"); ECS_COUNTER_APPEND(reply, stats, memory.free_count, "Frees by OS API"); ECS_GAUGE_APPEND(reply, stats, memory.outstanding_alloc_count, "Outstanding allocations by OS API"); ECS_COUNTER_APPEND(reply, stats, memory.block_alloc_count, "Blocks allocated by block allocators"); ECS_COUNTER_APPEND(reply, stats, memory.block_free_count, "Blocks freed by block allocators"); ECS_GAUGE_APPEND(reply, stats, memory.block_outstanding_alloc_count, "Outstanding block allocations"); ECS_COUNTER_APPEND(reply, stats, memory.stack_alloc_count, "Pages allocated by stack allocators"); ECS_COUNTER_APPEND(reply, stats, memory.stack_free_count, "Pages freed by stack allocators"); ECS_GAUGE_APPEND(reply, stats, memory.stack_outstanding_alloc_count, "Outstanding page allocations"); ECS_COUNTER_APPEND(reply, stats, http.request_received_count, "Received requests"); ECS_COUNTER_APPEND(reply, stats, http.request_invalid_count, "Received invalid requests"); ECS_COUNTER_APPEND(reply, stats, http.request_handled_ok_count, "Requests handled successfully"); ECS_COUNTER_APPEND(reply, stats, http.request_handled_error_count, "Requests handled with error code"); ECS_COUNTER_APPEND(reply, stats, http.request_not_handled_count, "Requests not handled (unknown endpoint)"); ECS_COUNTER_APPEND(reply, stats, http.request_preflight_count, "Preflight requests received"); ECS_COUNTER_APPEND(reply, stats, http.send_ok_count, "Successful replies"); ECS_COUNTER_APPEND(reply, stats, http.send_error_count, "Unsuccessful replies"); ECS_COUNTER_APPEND(reply, stats, http.busy_count, "Dropped requests due to full send queue (503)"); ecs_strbuf_list_pop(reply, "}"); } static void flecs_system_stats_to_json( ecs_world_t *world, ecs_strbuf_t *reply, ecs_entity_t system, const ecs_system_stats_t *stats) { ecs_strbuf_list_push(reply, "{", ","); ecs_strbuf_list_appendlit(reply, "\"name\":\""); ecs_get_path_w_sep_buf(world, 0, system, ".", NULL, reply, 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; size += ECS_SIZEOF(int64_t); #endif ba->chunk_size = ECS_ALIGN(size, 16); ba->chunks_per_block = ECS_MAX(4096 / ba->chunk_size, 1); ba->block_size = ba->chunks_per_block * ba->chunk_size; ba->head = NULL; ba->block_head = NULL; ba->block_tail = NULL; } ecs_block_allocator_t* flecs_ballocator_new( ecs_size_t size) { ecs_block_allocator_t *result = ecs_os_calloc_t(ecs_block_allocator_t); flecs_ballocator_init(result, size); return result; } void flecs_ballocator_fini( ecs_block_allocator_t *ba) { ecs_assert(ba != NULL, ECS_INTERNAL_ERROR, NULL); #ifdef FLECS_SANITIZE ecs_assert(ba->alloc_count == 0, ECS_LEAK_DETECTED, "(size = %u)", (uint32_t)ba->data_size); #endif ecs_block_allocator_block_t *block; for (block = ba->block_head; block;) { ecs_block_allocator_block_t *next = block->next; ecs_os_free(block); ecs_os_linc(&ecs_block_allocator_free_count); block = next; } ba->block_head = NULL; } void flecs_ballocator_free( ecs_block_allocator_t *ba) { flecs_ballocator_fini(ba); ecs_os_free(ba); } void* flecs_balloc( ecs_block_allocator_t *ba) { void *result; #ifdef FLECS_USE_OS_ALLOC result = ecs_os_malloc(ba->data_size); #else if (!ba) return NULL; if (!ba->head) { ba->head = flecs_balloc_block(ba); ecs_assert(ba->head != NULL, ECS_INTERNAL_ERROR, NULL); } result = ba->head; ba->head = ba->head->next; #ifdef FLECS_SANITIZE ecs_assert(ba->alloc_count >= 0, ECS_INTERNAL_ERROR, "corrupted allocator"); ba->alloc_count ++; *(int64_t*)result = ba->chunk_size; result = ECS_OFFSET(result, ECS_SIZEOF(int64_t)); #endif #endif #ifdef FLECS_MEMSET_UNINITIALIZED ecs_os_memset(result, 0xAA, ba->data_size); #endif return result; } void* flecs_bcalloc( ecs_block_allocator_t *ba) { #ifdef FLECS_USE_OS_ALLOC ecs_assert(ba != NULL, ECS_INTERNAL_ERROR, NULL); return ecs_os_calloc(ba->data_size); #else if (!ba) return NULL; void *result = flecs_balloc(ba); ecs_os_memset(result, 0, ba->data_size); return result; #endif } void flecs_bfree( ecs_block_allocator_t *ba, void *memory) { flecs_bfree_w_dbg_info(ba, memory, NULL); } void flecs_bfree_w_dbg_info( ecs_block_allocator_t *ba, void *memory, const char *type_name) { (void)type_name; #ifdef FLECS_USE_OS_ALLOC (void)ba; ecs_os_free(memory); return; #else if (!ba) { ecs_assert(memory == NULL, ECS_INTERNAL_ERROR, NULL); return; } if (memory == NULL) { return; } #ifdef FLECS_SANITIZE memory = ECS_OFFSET(memory, -ECS_SIZEOF(int64_t)); if (*(int64_t*)memory != ba->chunk_size) { if (type_name) { ecs_err("chunk %p returned to wrong allocator " "(chunk = %ub, allocator = %ub, type = %s)", memory, *(int64_t*)memory, ba->chunk_size, type_name); } else { ecs_err("chunk %p returned to wrong allocator " "(chunk = %ub, allocator = %ub)", memory, *(int64_t*)memory, ba->chunk_size); } ecs_abort(ECS_INTERNAL_ERROR, NULL); } ba->alloc_count --; #endif ecs_block_allocator_chunk_header_t *chunk = memory; chunk->next = ba->head; ba->head = chunk; ecs_assert(ba->alloc_count >= 0, ECS_INTERNAL_ERROR, "corrupted allocator (size = %d)", ba->chunk_size); #endif } void* flecs_brealloc( ecs_block_allocator_t *dst, ecs_block_allocator_t *src, void *memory) { void *result; #ifdef FLECS_USE_OS_ALLOC (void)src; result = ecs_os_realloc(memory, dst->data_size); #else if (dst == src) { return memory; } result = flecs_balloc(dst); if (result && src) { ecs_size_t size = src->data_size; if (dst->data_size < size) { size = dst->data_size; } ecs_os_memcpy(result, memory, size); } flecs_bfree(src, memory); #endif #ifdef FLECS_MEMSET_UNINITIALIZED if (dst && src && (dst->data_size > src->data_size)) { ecs_os_memset(ECS_OFFSET(result, src->data_size), 0xAA, dst->data_size - src->data_size); } else if (dst && !src) { ecs_os_memset(result, 0xAA, dst->data_size); } #endif return result; } void* flecs_bdup( ecs_block_allocator_t *ba, void *memory) { #ifdef FLECS_USE_OS_ALLOC if (memory && ba->chunk_size) { return ecs_os_memdup(memory, ba->data_size); } else { return NULL; } #else void *result = flecs_balloc(ba); if (result) { ecs_os_memcpy(result, memory, ba->data_size); } return result; #endif } // This is free and unencumbered software released into the public domain under The Unlicense (http://unlicense.org/) // main repo: https://github.com/wangyi-fudan/wyhash // author: 王一 Wang Yi // contributors: Reini Urban, Dietrich Epp, Joshua Haberman, Tommy Ettinger, // Daniel Lemire, Otmar Ertl, cocowalla, leo-yuriev, // Diego Barrios Romero, paulie-g, dumblob, Yann Collet, ivte-ms, // hyb, James Z.M. Gao, easyaspi314 (Devin), TheOneric /* quick example: string s="fjsakfdsjkf"; uint64_t hash=wyhash(s.c_str(), s.size(), 0, wyp_); */ #ifndef WYHASH_CONDOM //protections that produce different results: //1: normal valid behavior //2: extra protection against entropy loss (probability=2^-63), aka. "blind multiplication" #define WYHASH_CONDOM 1 #endif #ifndef WYHASH_32BIT_MUM //0: normal version, slow on 32 bit systems //1: faster on 32 bit systems but produces different results, incompatible with wy2u0k function #define WYHASH_32BIT_MUM 0 #endif //includes #include #include #if defined(_MSC_VER) && defined(_M_X64) #include #pragma intrinsic(_umul128) #endif //likely and unlikely macros #if defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__clang__) #define likely_(x) __builtin_expect(x,1) #define unlikely_(x) __builtin_expect(x,0) #else #define likely_(x) (x) #define unlikely_(x) (x) #endif //128bit multiply function static inline void wymum_(uint64_t *A, uint64_t *B){ #if(WYHASH_32BIT_MUM) uint64_t hh=(*A>>32)*(*B>>32), hl=(*A>>32)*(uint32_t)*B, lh=(uint32_t)*A*(*B>>32), ll=(uint64_t)(uint32_t)*A*(uint32_t)*B; #if(WYHASH_CONDOM>1) *A^=_wyrot(hl)^hh; *B^=_wyrot(lh)^ll; #else *A=_wyrot(hl)^hh; *B=_wyrot(lh)^ll; #endif #elif defined(__SIZEOF_INT128__) __uint128_t r=*A; r*=*B; #if(WYHASH_CONDOM>1) *A^=(uint64_t)r; *B^=(uint64_t)(r>>64); #else *A=(uint64_t)r; *B=(uint64_t)(r>>64); #endif #elif defined(_MSC_VER) && defined(_M_X64) #if(WYHASH_CONDOM>1) uint64_t a, b; a=_umul128(*A,*B,&b); *A^=a; *B^=b; #else *A=_umul128(*A,*B,B); #endif #else uint64_t ha=*A>>32, hb=*B>>32, la=(uint32_t)*A, lb=(uint32_t)*B, hi, lo; uint64_t rh=ha*hb, rm0=ha*lb, rm1=hb*la, rl=la*lb, t=rl+(rm0<<32), c=t>32)+(rm1>>32)+c; #if(WYHASH_CONDOM>1) *A^=lo; *B^=hi; #else *A=lo; *B=hi; #endif #endif } //multiply and xor mix function, aka MUM static inline uint64_t wymix_(uint64_t A, uint64_t B){ wymum_(&A,&B); return A^B; } //endian macros #ifndef WYHASH_LITTLE_ENDIAN #if defined(_WIN32) || defined(__LITTLE_ENDIAN__) || (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) #define WYHASH_LITTLE_ENDIAN 1 #elif defined(__BIG_ENDIAN__) || (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) #define WYHASH_LITTLE_ENDIAN 0 #else #warning could not determine endianness! Falling back to little endian. #define WYHASH_LITTLE_ENDIAN 1 #endif #endif //read functions #if (WYHASH_LITTLE_ENDIAN) static inline uint64_t wyr8_(const uint8_t *p) { uint64_t v; memcpy(&v, p, 8); return v;} static inline uint64_t wyr4_(const uint8_t *p) { uint32_t v; memcpy(&v, p, 4); return v;} #elif defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__clang__) static inline uint64_t wyr8_(const uint8_t *p) { uint64_t v; memcpy(&v, p, 8); return __builtin_bswap64(v);} static inline uint64_t wyr4_(const uint8_t *p) { uint32_t v; memcpy(&v, p, 4); return __builtin_bswap32(v);} #elif defined(_MSC_VER) static inline uint64_t wyr8_(const uint8_t *p) { uint64_t v; memcpy(&v, p, 8); return _byteswap_uint64(v);} static inline uint64_t wyr4_(const uint8_t *p) { uint32_t v; memcpy(&v, p, 4); return _byteswap_ulong(v);} #else static inline uint64_t wyr8_(const uint8_t *p) { uint64_t v; memcpy(&v, p, 8); return (((v >> 56) & 0xff)| ((v >> 40) & 0xff00)| ((v >> 24) & 0xff0000)| ((v >> 8) & 0xff000000)| ((v << 8) & 0xff00000000)| ((v << 24) & 0xff0000000000)| ((v << 40) & 0xff000000000000)| ((v << 56) & 0xff00000000000000)); } static inline uint64_t wyr4_(const uint8_t *p) { uint32_t v; memcpy(&v, p, 4); return (((v >> 24) & 0xff)| ((v >> 8) & 0xff00)| ((v << 8) & 0xff0000)| ((v << 24) & 0xff000000)); } #endif static inline uint64_t wyr3_(const uint8_t *p, size_t k) { return (((uint64_t)p[0])<<16)|(((uint64_t)p[k>>1])<<8)|p[k-1];} //wyhash main function static inline uint64_t wyhash(const void *key, size_t len, uint64_t seed, const uint64_t *secret){ const uint8_t *p=(const uint8_t *)key; seed^=wymix_(seed^secret[0],secret[1]); uint64_t a, b; if(likely_(len<=16)){ if(likely_(len>=4)){ a=(wyr4_(p)<<32)|wyr4_(p+((len>>3)<<2)); b=(wyr4_(p+len-4)<<32)|wyr4_(p+len-4-((len>>3)<<2)); } else if(likely_(len>0)){ a=wyr3_(p,len); b=0;} else a=b=0; } else{ size_t i=len; if(unlikely_(i>48)){ uint64_t see1=seed, see2=seed; do{ seed=wymix_(wyr8_(p)^secret[1],wyr8_(p+8)^seed); see1=wymix_(wyr8_(p+16)^secret[2],wyr8_(p+24)^see1); see2=wymix_(wyr8_(p+32)^secret[3],wyr8_(p+40)^see2); p+=48; i-=48; }while(likely_(i>48)); seed^=see1^see2; } while(unlikely_(i>16)){ seed=wymix_(wyr8_(p)^secret[1],wyr8_(p+8)^seed); i-=16; p+=16; } a=wyr8_(p+i-16); b=wyr8_(p+i-8); } a^=secret[1]; b^=seed; wymum_(&a,&b); return wymix_(a^secret[0]^len,b^secret[1]); } //the default secret parameters static const uint64_t wyp_[4] = {0xa0761d6478bd642full, 0xe7037ed1a0b428dbull, 0x8ebc6af09c88c6e3ull, 0x589965cc75374cc3ull}; uint64_t flecs_hash( const void *data, ecs_size_t length) { return wyhash(data, flecs_ito(size_t, length), 0, wyp_); } /** * @file datastructures/hashmap.c * @brief Hashmap data structure. * * The hashmap data structure is built on top of the map data structure. Where * the map data structure can only work with 64bit key values, the hashmap can * hash keys of any size, and handles collisions between hashes. */ static int32_t flecs_hashmap_find_key( const ecs_hashmap_t *map, ecs_vec_t *keys, ecs_size_t key_size, const void *key) { int32_t i, count = ecs_vec_count(keys); void *key_array = ecs_vec_first(keys); for (i = 0; i < count; i ++) { void *key_ptr = ECS_OFFSET(key_array, key_size * i); if (map->compare(key_ptr, key) == 0) { return i; } } return -1; } void flecs_hashmap_init_( ecs_hashmap_t *map, ecs_size_t key_size, ecs_size_t value_size, ecs_hash_value_action_t hash, ecs_compare_action_t compare, ecs_allocator_t *allocator) { map->key_size = key_size; map->value_size = value_size; map->hash = hash; map->compare = compare; flecs_ballocator_init_t(&map->bucket_allocator, ecs_hm_bucket_t); ecs_map_init(&map->impl, allocator); } void flecs_hashmap_fini( ecs_hashmap_t *map) { ecs_allocator_t *a = map->impl.allocator; ecs_map_iter_t it = ecs_map_iter(&map->impl); while (ecs_map_next(&it)) { ecs_hm_bucket_t *bucket = ecs_map_ptr(&it); ecs_vec_fini(a, &bucket->keys, map->key_size); ecs_vec_fini(a, &bucket->values, map->value_size); #ifdef FLECS_SANITIZE flecs_bfree(&map->bucket_allocator, bucket); #endif } flecs_ballocator_fini(&map->bucket_allocator); ecs_map_fini(&map->impl); } void flecs_hashmap_copy( ecs_hashmap_t *dst, const ecs_hashmap_t *src) { ecs_assert(dst != src, ECS_INVALID_PARAMETER, NULL); flecs_hashmap_init_(dst, src->key_size, src->value_size, src->hash, src->compare, src->impl.allocator); ecs_map_copy(&dst->impl, &src->impl); ecs_allocator_t *a = dst->impl.allocator; ecs_map_iter_t it = ecs_map_iter(&dst->impl); while (ecs_map_next(&it)) { ecs_hm_bucket_t **bucket_ptr = ecs_map_ref(&it, ecs_hm_bucket_t); ecs_hm_bucket_t *src_bucket = bucket_ptr[0]; ecs_hm_bucket_t *dst_bucket = flecs_balloc(&dst->bucket_allocator); bucket_ptr[0] = dst_bucket; dst_bucket->keys = ecs_vec_copy(a, &src_bucket->keys, dst->key_size); dst_bucket->values = ecs_vec_copy(a, &src_bucket->values, dst->value_size); } } void* flecs_hashmap_get_( const ecs_hashmap_t *map, ecs_size_t key_size, const void *key, ecs_size_t value_size) { ecs_assert(map->key_size == key_size, ECS_INVALID_PARAMETER, NULL); ecs_assert(map->value_size == value_size, ECS_INVALID_PARAMETER, NULL); uint64_t hash = map->hash(key); ecs_hm_bucket_t *bucket = ecs_map_get_deref(&map->impl, ecs_hm_bucket_t, hash); if (!bucket) { return NULL; } int32_t index = flecs_hashmap_find_key(map, &bucket->keys, key_size, key); if (index == -1) { return NULL; } return ecs_vec_get(&bucket->values, value_size, index); } flecs_hashmap_result_t flecs_hashmap_ensure_( ecs_hashmap_t *map, ecs_size_t key_size, const void *key, ecs_size_t value_size) { ecs_assert(map->key_size == key_size, ECS_INVALID_PARAMETER, NULL); ecs_assert(map->value_size == value_size, ECS_INVALID_PARAMETER, NULL); uint64_t hash = map->hash(key); ecs_hm_bucket_t **r = ecs_map_ensure_ref(&map->impl, ecs_hm_bucket_t, hash); ecs_hm_bucket_t *bucket = r[0]; if (!bucket) { bucket = r[0] = flecs_bcalloc(&map->bucket_allocator); } ecs_allocator_t *a = map->impl.allocator; void *value_ptr, *key_ptr; ecs_vec_t *keys = &bucket->keys; ecs_vec_t *values = &bucket->values; if (!keys->array) { ecs_vec_init(a, &bucket->keys, key_size, 1); ecs_vec_init(a, &bucket->values, value_size, 1); keys = &bucket->keys; values = &bucket->values; key_ptr = ecs_vec_append(a, keys, key_size); value_ptr = ecs_vec_append(a, values, value_size); ecs_os_memcpy(key_ptr, key, key_size); ecs_os_memset(value_ptr, 0, value_size); } else { int32_t index = flecs_hashmap_find_key(map, keys, key_size, key); if (index == -1) { key_ptr = ecs_vec_append(a, keys, key_size); value_ptr = ecs_vec_append(a, values, value_size); ecs_os_memcpy(key_ptr, key, key_size); ecs_os_memset(value_ptr, 0, value_size); } else { key_ptr = ecs_vec_get(keys, key_size, index); value_ptr = ecs_vec_get(values, value_size, index); } } return (flecs_hashmap_result_t){ .key = key_ptr, .value = value_ptr, .hash = hash }; } void flecs_hashmap_set_( ecs_hashmap_t *map, ecs_size_t key_size, void *key, ecs_size_t value_size, const void *value) { void *value_ptr = flecs_hashmap_ensure_(map, key_size, key, value_size).value; ecs_assert(value_ptr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_os_memcpy(value_ptr, value, value_size); } ecs_hm_bucket_t* flecs_hashmap_get_bucket( const ecs_hashmap_t *map, uint64_t hash) { ecs_assert(map != NULL, ECS_INTERNAL_ERROR, NULL); return ecs_map_get_deref(&map->impl, ecs_hm_bucket_t, hash); } void flecs_hm_bucket_remove( ecs_hashmap_t *map, ecs_hm_bucket_t *bucket, uint64_t hash, int32_t index) { ecs_vec_remove(&bucket->keys, map->key_size, index); ecs_vec_remove(&bucket->values, map->value_size, index); if (!ecs_vec_count(&bucket->keys)) { ecs_allocator_t *a = map->impl.allocator; ecs_vec_fini(a, &bucket->keys, map->key_size); ecs_vec_fini(a, &bucket->values, map->value_size); ecs_hm_bucket_t *b = ecs_map_remove_ptr(&map->impl, hash); ecs_assert(bucket == b, ECS_INTERNAL_ERROR, NULL); (void)b; flecs_bfree(&map->bucket_allocator, bucket); } } void flecs_hashmap_remove_w_hash_( ecs_hashmap_t *map, ecs_size_t key_size, const void *key, ecs_size_t value_size, uint64_t hash) { ecs_assert(map->key_size == key_size, ECS_INVALID_PARAMETER, NULL); ecs_assert(map->value_size == value_size, ECS_INVALID_PARAMETER, NULL); (void)value_size; ecs_hm_bucket_t *bucket = ecs_map_get_deref(&map->impl, ecs_hm_bucket_t, hash); if (!bucket) { return; } int32_t index = flecs_hashmap_find_key(map, &bucket->keys, key_size, key); if (index == -1) { return; } flecs_hm_bucket_remove(map, bucket, hash, index); } void flecs_hashmap_remove_( ecs_hashmap_t *map, ecs_size_t key_size, const void *key, ecs_size_t value_size) { ecs_assert(map->key_size == key_size, ECS_INVALID_PARAMETER, NULL); ecs_assert(map->value_size == value_size, ECS_INVALID_PARAMETER, NULL); uint64_t hash = map->hash(key); flecs_hashmap_remove_w_hash_(map, key_size, key, value_size, hash); } flecs_hashmap_iter_t flecs_hashmap_iter( ecs_hashmap_t *map) { return (flecs_hashmap_iter_t){ .it = ecs_map_iter(&map->impl) }; } void* flecs_hashmap_next_( flecs_hashmap_iter_t *it, ecs_size_t key_size, void *key_out, ecs_size_t value_size) { int32_t index = ++ it->index; ecs_hm_bucket_t *bucket = it->bucket; while (!bucket || it->index >= ecs_vec_count(&bucket->keys)) { ecs_map_next(&it->it); bucket = it->bucket = ecs_map_ptr(&it->it); if (!bucket) { return NULL; } index = it->index = 0; } if (key_out) { *(void**)key_out = ecs_vec_get(&bucket->keys, key_size, index); } return ecs_vec_get(&bucket->values, value_size, index); } /** * @file datastructures/map.c * @brief Map data structure. * * Map data structure for 64bit keys and dynamic payload size. */ /* The ratio used to determine whether the map should flecs_map_rehash. If * (element_count * ECS_LOAD_FACTOR) > bucket_count, bucket count is increased. */ #define ECS_LOAD_FACTOR (12) #define ECS_BUCKET_END(b, c) ECS_ELEM_T(b, ecs_bucket_t, c) static uint8_t flecs_log2(uint32_t v) { static const uint8_t log2table[32] = {0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30, 8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31}; v |= v >> 1; v |= v >> 2; v |= v >> 4; v |= v >> 8; v |= v >> 16; return log2table[(uint32_t)(v * 0x07C4ACDDU) >> 27]; } /* Get bucket count for number of elements */ static int32_t flecs_map_get_bucket_count( int32_t count) { return flecs_next_pow_of_2((int32_t)(count * ECS_LOAD_FACTOR * 0.1)); } /* Get bucket shift amount for a given bucket count */ static uint8_t flecs_map_get_bucket_shift ( int32_t bucket_count) { return (uint8_t)(64u - flecs_log2((uint32_t)bucket_count)); } /* Get bucket index for provided map key */ static int32_t flecs_map_get_bucket_index( uint16_t bucket_shift, ecs_map_key_t key) { ecs_assert(bucket_shift != 0, ECS_INTERNAL_ERROR, NULL); return (int32_t)((11400714819323198485ull * key) >> bucket_shift); } /* Get bucket for key */ static ecs_bucket_t* flecs_map_get_bucket( const ecs_map_t *map, ecs_map_key_t key) { ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL); int32_t bucket_id = flecs_map_get_bucket_index(map->bucket_shift, key); ecs_assert(bucket_id < map->bucket_count, ECS_INTERNAL_ERROR, NULL); return &map->buckets[bucket_id]; } /* Add element to bucket */ static ecs_map_val_t* flecs_map_bucket_add( ecs_block_allocator_t *allocator, ecs_bucket_t *bucket, ecs_map_key_t key) { ecs_bucket_entry_t *new_entry = flecs_balloc(allocator); new_entry->key = key; new_entry->next = bucket->first; bucket->first = new_entry; return &new_entry->value; } /* Remove element from bucket */ static ecs_map_val_t flecs_map_bucket_remove( ecs_map_t *map, ecs_bucket_t *bucket, ecs_map_key_t key) { ecs_bucket_entry_t *entry; for (entry = bucket->first; entry; entry = entry->next) { if (entry->key == key) { ecs_map_val_t value = entry->value; ecs_bucket_entry_t **next_holder = &bucket->first; while(*next_holder != entry) { next_holder = &(*next_holder)->next; } *next_holder = entry->next; flecs_bfree(map->entry_allocator, entry); map->count --; return value; } } return 0; } /* Free contents of bucket */ static void flecs_map_bucket_clear( ecs_block_allocator_t *allocator, ecs_bucket_t *bucket) { ecs_bucket_entry_t *entry = bucket->first; while(entry) { ecs_bucket_entry_t *next = entry->next; flecs_bfree(allocator, entry); entry = next; } } /* Get payload pointer for key from bucket */ static ecs_map_val_t* flecs_map_bucket_get( ecs_bucket_t *bucket, ecs_map_key_t key) { ecs_bucket_entry_t *entry; for (entry = bucket->first; entry; entry = entry->next) { if (entry->key == key) { return &entry->value; } } return NULL; } /* Grow number of buckets */ static void flecs_map_rehash( ecs_map_t *map, int32_t count) { count = flecs_next_pow_of_2(count); if (count < 2) { count = 2; } ecs_assert(count > map->bucket_count, ECS_INTERNAL_ERROR, NULL); int32_t old_count = map->bucket_count; ecs_bucket_t *buckets = map->buckets, *b, *end = ECS_BUCKET_END(buckets, old_count); if (map->allocator) { map->buckets = flecs_calloc_n(map->allocator, ecs_bucket_t, count); } else { map->buckets = ecs_os_calloc_n(ecs_bucket_t, count); } map->bucket_count = count; map->bucket_shift = flecs_map_get_bucket_shift(count); /* Remap old bucket entries to new buckets */ for (b = buckets; b < end; b++) { ecs_bucket_entry_t* entry; for (entry = b->first; entry;) { ecs_bucket_entry_t* next = entry->next; int32_t bucket_index = flecs_map_get_bucket_index( map->bucket_shift, entry->key); ecs_bucket_t *bucket = &map->buckets[bucket_index]; entry->next = bucket->first; bucket->first = entry; entry = next; } } if (map->allocator) { flecs_free_n(map->allocator, ecs_bucket_t, old_count, buckets); } else { ecs_os_free(buckets); } } void ecs_map_params_init( ecs_map_params_t *params, ecs_allocator_t *allocator) { params->allocator = allocator; flecs_ballocator_init_t(¶ms->entry_allocator, ecs_bucket_entry_t); } void ecs_map_params_fini( ecs_map_params_t *params) { flecs_ballocator_fini(¶ms->entry_allocator); } void ecs_map_init_w_params( ecs_map_t *result, ecs_map_params_t *params) { ecs_os_zeromem(result); result->allocator = params->allocator; if (params->entry_allocator.chunk_size) { result->entry_allocator = ¶ms->entry_allocator; result->shared_allocator = true; } else { result->entry_allocator = flecs_ballocator_new_t(ecs_bucket_entry_t); } flecs_map_rehash(result, 0); } void ecs_map_init_w_params_if( ecs_map_t *result, ecs_map_params_t *params) { if (!ecs_map_is_init(result)) { ecs_map_init_w_params(result, params); } } void ecs_map_init( ecs_map_t *result, ecs_allocator_t *allocator) { ecs_map_init_w_params(result, &(ecs_map_params_t) { .allocator = allocator }); } void ecs_map_init_if( ecs_map_t *result, ecs_allocator_t *allocator) { if (!ecs_map_is_init(result)) { ecs_map_init(result, allocator); } } void ecs_map_fini( ecs_map_t *map) { if (!ecs_map_is_init(map)) { return; } bool sanitize = false; #ifdef FLECS_SANITIZE sanitize = true; #endif /* Free buckets in sanitized mode, so we can replace the allocator with * regular malloc/free and use asan/valgrind to find memory errors. */ ecs_allocator_t *a = map->allocator; ecs_block_allocator_t *ea = map->entry_allocator; if (map->shared_allocator || sanitize) { ecs_bucket_t *bucket = map->buckets, *end = &bucket[map->bucket_count]; while (bucket != end) { flecs_map_bucket_clear(ea, bucket); bucket ++; } } if (ea && !map->shared_allocator) { flecs_ballocator_free(ea); map->entry_allocator = NULL; } if (a) { flecs_free_n(a, ecs_bucket_t, map->bucket_count, map->buckets); } else { ecs_os_free(map->buckets); } map->bucket_shift = 0; } ecs_map_val_t* ecs_map_get( const ecs_map_t *map, ecs_map_key_t key) { return flecs_map_bucket_get(flecs_map_get_bucket(map, key), key); } void* ecs_map_get_deref_( const ecs_map_t *map, ecs_map_key_t key) { ecs_map_val_t* ptr = flecs_map_bucket_get( flecs_map_get_bucket(map, key), key); if (ptr) { return (void*)(uintptr_t)ptr[0]; } return NULL; } void ecs_map_insert( ecs_map_t *map, ecs_map_key_t key, ecs_map_val_t value) { ecs_assert(ecs_map_get(map, key) == NULL, ECS_INVALID_PARAMETER, NULL); int32_t map_count = ++map->count; int32_t tgt_bucket_count = flecs_map_get_bucket_count(map_count); int32_t bucket_count = map->bucket_count; if (tgt_bucket_count > bucket_count) { flecs_map_rehash(map, tgt_bucket_count); } ecs_bucket_t *bucket = flecs_map_get_bucket(map, key); flecs_map_bucket_add(map->entry_allocator, bucket, key)[0] = value; } void* ecs_map_insert_alloc( ecs_map_t *map, ecs_size_t elem_size, ecs_map_key_t key) { void *elem = ecs_os_calloc(elem_size); ecs_map_insert_ptr(map, key, (uintptr_t)elem); return elem; } ecs_map_val_t* ecs_map_ensure( ecs_map_t *map, ecs_map_key_t key) { ecs_bucket_t *bucket = flecs_map_get_bucket(map, key); ecs_map_val_t *result = flecs_map_bucket_get(bucket, key); if (result) { return result; } int32_t map_count = ++map->count; int32_t tgt_bucket_count = flecs_map_get_bucket_count(map_count); int32_t bucket_count = map->bucket_count; if (tgt_bucket_count > bucket_count) { flecs_map_rehash(map, tgt_bucket_count); bucket = flecs_map_get_bucket(map, key); } ecs_map_val_t* v = flecs_map_bucket_add(map->entry_allocator, bucket, key); *v = 0; return v; } void* ecs_map_ensure_alloc( ecs_map_t *map, ecs_size_t elem_size, ecs_map_key_t key) { ecs_map_val_t *val = ecs_map_ensure(map, key); if (!*val) { void *elem = ecs_os_calloc(elem_size); *val = (ecs_map_val_t)(uintptr_t)elem; return elem; } else { return (void*)(uintptr_t)*val; } } ecs_map_val_t ecs_map_remove( ecs_map_t *map, ecs_map_key_t key) { return flecs_map_bucket_remove(map, flecs_map_get_bucket(map, key), key); } void ecs_map_remove_free( ecs_map_t *map, ecs_map_key_t key) { ecs_map_val_t val = ecs_map_remove(map, key); if (val) { ecs_os_free((void*)(uintptr_t)val); } } void ecs_map_clear( ecs_map_t *map) { ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL); int32_t i, count = map->bucket_count; for (i = 0; i < count; i ++) { flecs_map_bucket_clear(map->entry_allocator, &map->buckets[i]); } if (map->allocator) { flecs_free_n(map->allocator, ecs_bucket_t, count, map->buckets); } else { ecs_os_free(map->buckets); } map->buckets = NULL; map->bucket_count = 0; map->count = 0; flecs_map_rehash(map, 2); } ecs_map_iter_t ecs_map_iter( const ecs_map_t *map) { if (ecs_map_is_init(map)) { return (ecs_map_iter_t){ .map = map, .bucket = NULL, .entry = NULL }; } else { return (ecs_map_iter_t){ 0 }; } } bool ecs_map_next( ecs_map_iter_t *iter) { const ecs_map_t *map = iter->map; ecs_bucket_t *end; if (!map || (iter->bucket == (end = &map->buckets[map->bucket_count]))) { return false; } ecs_bucket_entry_t *entry = NULL; if (!iter->bucket) { for (iter->bucket = map->buckets; iter->bucket != end; ++iter->bucket) { if (iter->bucket->first) { entry = iter->bucket->first; break; } } if (iter->bucket == end) { return false; } } else if ((entry = iter->entry) == NULL) { do { ++iter->bucket; if (iter->bucket == end) { return false; } } while(!iter->bucket->first); entry = iter->bucket->first; } ecs_assert(entry != NULL, ECS_INTERNAL_ERROR, NULL); iter->entry = entry->next; iter->res = &entry->key; return true; } void ecs_map_copy( ecs_map_t *dst, const ecs_map_t *src) { if (ecs_map_is_init(dst)) { ecs_assert(ecs_map_count(dst) == 0, ECS_INVALID_PARAMETER, NULL); ecs_map_fini(dst); } if (!ecs_map_is_init(src)) { return; } ecs_map_init(dst, src->allocator); ecs_map_iter_t it = ecs_map_iter(src); while (ecs_map_next(&it)) { ecs_map_insert(dst, ecs_map_key(&it), ecs_map_value(&it)); } } /** * @file datastructures/name_index.c * @brief Data structure for resolving 64bit keys by string (name). */ static uint64_t flecs_name_index_hash( const void *ptr) { const ecs_hashed_string_t *str = ptr; ecs_assert(str->hash != 0, ECS_INTERNAL_ERROR, NULL); return str->hash; } static int flecs_name_index_compare( const void *ptr1, const void *ptr2) { const ecs_hashed_string_t *str1 = ptr1; const ecs_hashed_string_t *str2 = ptr2; ecs_size_t len1 = str1->length; ecs_size_t len2 = str2->length; if (len1 != len2) { return (len1 > len2) - (len1 < len2); } return ecs_os_memcmp(str1->value, str2->value, len1); } void flecs_name_index_init( ecs_hashmap_t *hm, ecs_allocator_t *allocator) { flecs_hashmap_init_(hm, ECS_SIZEOF(ecs_hashed_string_t), ECS_SIZEOF(uint64_t), flecs_name_index_hash, flecs_name_index_compare, allocator); } void flecs_name_index_init_if( ecs_hashmap_t *hm, ecs_allocator_t *allocator) { if (!hm->compare) { flecs_name_index_init(hm, allocator); } } bool flecs_name_index_is_init( const ecs_hashmap_t *hm) { return hm->compare != NULL; } ecs_hashmap_t* flecs_name_index_new( ecs_world_t *world, ecs_allocator_t *allocator) { ecs_hashmap_t *result = flecs_bcalloc(&world->allocators.hashmap); flecs_name_index_init(result, allocator); result->hashmap_allocator = &world->allocators.hashmap; return result; } void flecs_name_index_fini( ecs_hashmap_t *map) { flecs_hashmap_fini(map); } void flecs_name_index_free( ecs_hashmap_t *map) { if (map) { flecs_name_index_fini(map); flecs_bfree(map->hashmap_allocator, map); } } ecs_hashmap_t* flecs_name_index_copy( ecs_hashmap_t *map) { ecs_hashmap_t *result = flecs_bcalloc(map->hashmap_allocator); result->hashmap_allocator = map->hashmap_allocator; flecs_hashmap_copy(result, map); return result; } ecs_hashed_string_t flecs_get_hashed_string( const char *name, ecs_size_t length, uint64_t hash) { if (!length) { length = ecs_os_strlen(name); } else { ecs_assert(length == ecs_os_strlen(name), ECS_INTERNAL_ERROR, NULL); } if (!hash) { hash = flecs_hash(name, length); } else { ecs_assert(hash == flecs_hash(name, length), ECS_INTERNAL_ERROR, NULL); } return (ecs_hashed_string_t) { .value = ECS_CONST_CAST(char*, name), .length = length, .hash = hash }; } const uint64_t* flecs_name_index_find_ptr( const ecs_hashmap_t *map, const char *name, ecs_size_t length, uint64_t hash) { ecs_hashed_string_t hs = flecs_get_hashed_string(name, length, hash); ecs_hm_bucket_t *b = flecs_hashmap_get_bucket(map, hs.hash); if (!b) { return NULL; } ecs_hashed_string_t *keys = ecs_vec_first(&b->keys); int32_t i, count = ecs_vec_count(&b->keys); for (i = 0; i < count; i ++) { ecs_hashed_string_t *key = &keys[i]; ecs_assert(key->hash == hs.hash, ECS_INTERNAL_ERROR, NULL); if (hs.length != key->length) { continue; } if (!ecs_os_strcmp(name, key->value)) { uint64_t *e = ecs_vec_get_t(&b->values, uint64_t, i); ecs_assert(e != NULL, ECS_INTERNAL_ERROR, NULL); return e; } } return NULL; } uint64_t flecs_name_index_find( const ecs_hashmap_t *map, const char *name, ecs_size_t length, uint64_t hash) { const uint64_t *id = flecs_name_index_find_ptr(map, name, length, hash); if (id) { return id[0]; } return 0; } void flecs_name_index_remove( ecs_hashmap_t *map, uint64_t e, uint64_t hash) { ecs_hm_bucket_t *b = flecs_hashmap_get_bucket(map, hash); if (!b) { return; } uint64_t *ids = ecs_vec_first(&b->values); int32_t i, count = ecs_vec_count(&b->values); for (i = 0; i < count; i ++) { if (ids[i] == e) { flecs_hm_bucket_remove(map, b, hash, i); break; } } } void flecs_name_index_update_name( ecs_hashmap_t *map, uint64_t e, uint64_t hash, const char *name) { ecs_hm_bucket_t *b = flecs_hashmap_get_bucket(map, hash); if (!b) { return; } uint64_t *ids = ecs_vec_first(&b->values); int32_t i, count = ecs_vec_count(&b->values); for (i = 0; i < count; i ++) { if (ids[i] == e) { ecs_hashed_string_t *key = ecs_vec_get_t( &b->keys, ecs_hashed_string_t, i); key->value = ECS_CONST_CAST(char*, name); ecs_assert(ecs_os_strlen(name) == key->length, ECS_INTERNAL_ERROR, NULL); ecs_assert(flecs_hash(name, key->length) == key->hash, ECS_INTERNAL_ERROR, NULL); return; } } /* Record must already have been in the index */ ecs_abort(ECS_INTERNAL_ERROR, NULL); } void flecs_name_index_ensure( ecs_hashmap_t *map, uint64_t id, const char *name, ecs_size_t length, uint64_t hash) { ecs_check(name != NULL, ECS_INVALID_PARAMETER, NULL); ecs_hashed_string_t key = flecs_get_hashed_string(name, length, hash); uint64_t existing = flecs_name_index_find( map, name, key.length, key.hash); if (existing) { if (existing != id) { ecs_abort(ECS_ALREADY_DEFINED, "conflicting id registered with name '%s'", name); } } flecs_hashmap_result_t hmr = flecs_hashmap_ensure( map, &key, uint64_t); *((uint64_t*)hmr.value) = id; error: return; } /** * @file datastructures/sparse.c * @brief Sparse set data structure. */ /* Utility to get a pointer to the payload */ #define DATA(array, size, offset) (ECS_OFFSET(array, size * offset)) typedef struct ecs_page_t { int32_t *sparse; /* Sparse array with indices to dense array */ void *data; /* Store data in sparse array to reduce * indirection and provide stable pointers. */ } ecs_page_t; static ecs_page_t* flecs_sparse_page_new( ecs_sparse_t *sparse, int32_t page_index) { ecs_allocator_t *a = sparse->allocator; ecs_block_allocator_t *ca = sparse->page_allocator; int32_t count = ecs_vec_count(&sparse->pages); ecs_page_t *pages; if (count <= page_index) { ecs_vec_set_count_t(a, &sparse->pages, ecs_page_t, page_index + 1); pages = ecs_vec_first_t(&sparse->pages, ecs_page_t); ecs_os_memset_n(&pages[count], 0, ecs_page_t, (1 + page_index - count)); } else { pages = ecs_vec_first_t(&sparse->pages, ecs_page_t); } ecs_assert(pages != NULL, ECS_INTERNAL_ERROR, NULL); ecs_page_t *result = &pages[page_index]; ecs_assert(result->sparse == NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(result->data == NULL, ECS_INTERNAL_ERROR, NULL); /* Initialize sparse array with zero's, as zero is used to indicate that the * sparse element has not been paired with a dense element. Use zero * as this means we can take advantage of calloc having a possibly better * performance than malloc + memset. */ result->sparse = ca ? flecs_bcalloc(ca) : ecs_os_calloc_n(int32_t, FLECS_SPARSE_PAGE_SIZE); /* Initialize the data array with zero's to guarantee that data is * always initialized. When an entry is removed, data is reset back to * zero. Initialize now, as this can take advantage of calloc. */ result->data = a ? flecs_calloc(a, sparse->size * FLECS_SPARSE_PAGE_SIZE) : ecs_os_calloc(sparse->size * FLECS_SPARSE_PAGE_SIZE); ecs_assert(result->sparse != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(result->data != NULL, ECS_INTERNAL_ERROR, NULL); return result; } static void flecs_sparse_page_free( ecs_sparse_t *sparse, ecs_page_t *page) { ecs_allocator_t *a = sparse->allocator; ecs_block_allocator_t *ca = sparse->page_allocator; if (ca) { flecs_bfree(ca, page->sparse); } else { ecs_os_free(page->sparse); } if (a) { flecs_free(a, sparse->size * FLECS_SPARSE_PAGE_SIZE, page->data); } else { ecs_os_free(page->data); } } static ecs_page_t* flecs_sparse_get_page( const ecs_sparse_t *sparse, int32_t page_index) { ecs_assert(page_index >= 0, ECS_INVALID_PARAMETER, NULL); if (page_index >= ecs_vec_count(&sparse->pages)) { return NULL; } return ecs_vec_get_t(&sparse->pages, ecs_page_t, page_index); } static ecs_page_t* flecs_sparse_get_or_create_page( ecs_sparse_t *sparse, int32_t page_index) { ecs_page_t *page = flecs_sparse_get_page(sparse, page_index); if (page && page->sparse) { return page; } return flecs_sparse_page_new(sparse, page_index); } static void flecs_sparse_grow_dense( ecs_sparse_t *sparse) { ecs_vec_append_t(sparse->allocator, &sparse->dense, uint64_t); } static uint64_t flecs_sparse_strip_generation( uint64_t *index_out) { uint64_t index = *index_out; uint64_t gen = index & ECS_GENERATION_MASK; /* Make sure there's no junk in the id */ ecs_assert(gen == (index & (0xFFFFFFFFull << 32)), ECS_INVALID_PARAMETER, NULL); *index_out -= gen; return gen; } static void flecs_sparse_assign_index( ecs_page_t * page, uint64_t * dense_array, uint64_t index, int32_t dense) { /* Initialize sparse-dense pair. This assigns the dense index to the sparse * array, and the sparse index to the dense array .*/ page->sparse[FLECS_SPARSE_OFFSET(index)] = dense; dense_array[dense] = index; } static uint64_t flecs_sparse_inc_gen( uint64_t index) { /* When an index is deleted, its generation is increased so that we can do * liveliness checking while recycling ids */ return ECS_GENERATION_INC(index); } static uint64_t flecs_sparse_inc_id( ecs_sparse_t *sparse) { /* Generate a new id. The last issued id could be stored in an external * variable, such as is the case with the last issued entity id, which is * stored on the world. */ return ++ sparse->max_id; } static uint64_t flecs_sparse_get_id( const ecs_sparse_t *sparse) { ecs_assert(sparse != NULL, ECS_INTERNAL_ERROR, NULL); return sparse->max_id; } static void flecs_sparse_set_id( ecs_sparse_t *sparse, uint64_t value) { /* Sometimes the max id needs to be assigned directly, which typically * happens when the API calls get_or_create for an id that hasn't been * issued before. */ sparse->max_id = value; } /* Pair dense id with new sparse id */ static uint64_t flecs_sparse_create_id( ecs_sparse_t *sparse, int32_t dense) { uint64_t index = flecs_sparse_inc_id(sparse); flecs_sparse_grow_dense(sparse); ecs_page_t *page = flecs_sparse_get_or_create_page(sparse, FLECS_SPARSE_PAGE(index)); ecs_assert(page->sparse[FLECS_SPARSE_OFFSET(index)] == 0, ECS_INTERNAL_ERROR, NULL); uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); flecs_sparse_assign_index(page, dense_array, index, dense); return index; } /* Create new id */ static uint64_t flecs_sparse_new_index( ecs_sparse_t *sparse) { int32_t dense_count = ecs_vec_count(&sparse->dense); int32_t count = sparse->count ++; ecs_assert(count <= dense_count, ECS_INTERNAL_ERROR, NULL); if (count < dense_count) { /* If there are unused elements in the dense array, return first */ uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); return dense_array[count]; } else { return flecs_sparse_create_id(sparse, count); } } /* Get value from sparse set when it is guaranteed that the value exists. This * function is used when values are obtained using a dense index */ static void* flecs_sparse_get_sparse( const ecs_sparse_t *sparse, int32_t dense, uint64_t index) { flecs_sparse_strip_generation(&index); ecs_page_t *page = flecs_sparse_get_page(sparse, FLECS_SPARSE_PAGE(index)); if (!page || !page->sparse) { return NULL; } int32_t offset = FLECS_SPARSE_OFFSET(index); ecs_assert(page != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(dense == page->sparse[offset], ECS_INTERNAL_ERROR, NULL); (void)dense; return DATA(page->data, sparse->size, offset); } /* Swap dense elements. A swap occurs when an element is removed, or when a * removed element is recycled. */ static void flecs_sparse_swap_dense( ecs_sparse_t * sparse, ecs_page_t * page_a, int32_t a, int32_t b) { uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); uint64_t index_a = dense_array[a]; uint64_t index_b = dense_array[b]; ecs_page_t *page_b = flecs_sparse_get_or_create_page(sparse, FLECS_SPARSE_PAGE(index_b)); flecs_sparse_assign_index(page_a, dense_array, index_a, b); flecs_sparse_assign_index(page_b, dense_array, index_b, a); } void flecs_sparse_init( ecs_sparse_t *result, struct ecs_allocator_t *allocator, ecs_block_allocator_t *page_allocator, ecs_size_t size) { ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL); result->size = size; result->max_id = UINT64_MAX; result->allocator = allocator; result->page_allocator = page_allocator; ecs_vec_init_t(allocator, &result->pages, ecs_page_t, 0); ecs_vec_init_t(allocator, &result->dense, uint64_t, 1); result->dense.count = 1; /* Consume first value in dense array as 0 is used in the sparse array to * indicate that a sparse element hasn't been paired yet. */ ecs_vec_first_t(&result->dense, uint64_t)[0] = 0; result->count = 1; } void flecs_sparse_clear( ecs_sparse_t *sparse) { ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); int32_t i, count = ecs_vec_count(&sparse->pages); ecs_page_t *pages = ecs_vec_first_t(&sparse->pages, ecs_page_t); for (i = 0; i < count; i ++) { int32_t *indices = pages[i].sparse; if (indices) { ecs_os_memset_n(indices, 0, int32_t, FLECS_SPARSE_PAGE_SIZE); } } ecs_vec_set_count_t(sparse->allocator, &sparse->dense, uint64_t, 1); sparse->count = 1; sparse->max_id = 0; } void flecs_sparse_fini( ecs_sparse_t *sparse) { ecs_assert(sparse != NULL, ECS_INTERNAL_ERROR, NULL); int32_t i, count = ecs_vec_count(&sparse->pages); ecs_page_t *pages = ecs_vec_first_t(&sparse->pages, ecs_page_t); for (i = 0; i < count; i ++) { flecs_sparse_page_free(sparse, &pages[i]); } ecs_vec_fini_t(sparse->allocator, &sparse->pages, ecs_page_t); ecs_vec_fini_t(sparse->allocator, &sparse->dense, uint64_t); } uint64_t flecs_sparse_new_id( ecs_sparse_t *sparse) { ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); return flecs_sparse_new_index(sparse); } void* flecs_sparse_add( ecs_sparse_t *sparse, ecs_size_t size) { ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); uint64_t index = flecs_sparse_new_index(sparse); ecs_page_t *page = flecs_sparse_get_page(sparse, FLECS_SPARSE_PAGE(index)); ecs_assert(page != NULL, ECS_INTERNAL_ERROR, NULL); return DATA(page->data, size, FLECS_SPARSE_OFFSET(index)); } uint64_t flecs_sparse_last_id( const ecs_sparse_t *sparse) { ecs_assert(sparse != NULL, ECS_INTERNAL_ERROR, NULL); uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); return dense_array[sparse->count - 1]; } void* flecs_sparse_ensure( ecs_sparse_t *sparse, ecs_size_t size, uint64_t index) { ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); ecs_assert(ecs_vec_count(&sparse->dense) > 0, ECS_INTERNAL_ERROR, NULL); (void)size; uint64_t gen = flecs_sparse_strip_generation(&index); ecs_page_t *page = flecs_sparse_get_or_create_page(sparse, FLECS_SPARSE_PAGE(index)); int32_t offset = FLECS_SPARSE_OFFSET(index); int32_t dense = page->sparse[offset]; if (dense) { /* Check if element is alive. If element is not alive, update indices so * that the first unused dense element points to the sparse element. */ int32_t count = sparse->count; if (dense >= count) { /* If dense is not alive, swap it with the first unused element. */ flecs_sparse_swap_dense(sparse, page, dense, count); dense = count; /* First unused element is now last used element */ sparse->count ++; /* Set dense element to new generation */ ecs_vec_first_t(&sparse->dense, uint64_t)[dense] = index | gen; } else { /* Dense is already alive, nothing to be done */ } /* Ensure provided generation matches current. Only allow mismatching * generations if the provided generation count is 0. This allows for * using the ensure function in combination with ids that have their * generation stripped. */ #ifdef FLECS_DEBUG uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); ecs_assert(!gen || dense_array[dense] == (index | gen), ECS_INTERNAL_ERROR, NULL); #endif } else { /* Element is not paired yet. Must add a new element to dense array */ flecs_sparse_grow_dense(sparse); uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); int32_t dense_count = ecs_vec_count(&sparse->dense) - 1; int32_t count = sparse->count ++; /* If index is larger than max id, update max id */ if (index >= flecs_sparse_get_id(sparse)) { flecs_sparse_set_id(sparse, index); } if (count < dense_count) { /* If there are unused elements in the list, move the first unused * element to the end of the list */ uint64_t unused = dense_array[count]; ecs_page_t *unused_page = flecs_sparse_get_or_create_page(sparse, FLECS_SPARSE_PAGE(unused)); flecs_sparse_assign_index(unused_page, dense_array, unused, dense_count); } flecs_sparse_assign_index(page, dense_array, index, count); dense_array[count] |= gen; } return DATA(page->data, sparse->size, offset); } void* flecs_sparse_ensure_fast( ecs_sparse_t *sparse, ecs_size_t size, uint64_t index_long) { ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); ecs_assert(ecs_vec_count(&sparse->dense) > 0, ECS_INTERNAL_ERROR, NULL); (void)size; uint32_t index = (uint32_t)index_long; ecs_page_t *page = flecs_sparse_get_or_create_page(sparse, FLECS_SPARSE_PAGE(index)); int32_t offset = FLECS_SPARSE_OFFSET(index); int32_t dense = page->sparse[offset]; int32_t count = sparse->count; if (!dense) { /* Element is not paired yet. Must add a new element to dense array */ sparse->count = count + 1; if (count == ecs_vec_count(&sparse->dense)) { flecs_sparse_grow_dense(sparse); } uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); flecs_sparse_assign_index(page, dense_array, index, count); } return DATA(page->data, sparse->size, offset); } void* flecs_sparse_remove_fast( ecs_sparse_t *sparse, ecs_size_t size, uint64_t index) { ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); (void)size; ecs_page_t *page = flecs_sparse_get_page(sparse, FLECS_SPARSE_PAGE(index)); if (!page || !page->sparse) { return NULL; } flecs_sparse_strip_generation(&index); int32_t offset = FLECS_SPARSE_OFFSET(index); int32_t dense = page->sparse[offset]; if (dense) { int32_t count = sparse->count; if (dense == (count - 1)) { /* If dense is the last used element, simply decrease count */ sparse->count --; } else if (dense < count) { /* If element is alive, move it to unused elements */ flecs_sparse_swap_dense(sparse, page, dense, count - 1); sparse->count --; } /* Reset memory to zero on remove */ return DATA(page->data, sparse->size, offset); } else { /* Element is not paired and thus not alive, nothing to be done */ return NULL; } } void flecs_sparse_remove( ecs_sparse_t *sparse, ecs_size_t size, uint64_t index) { ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); (void)size; ecs_page_t *page = flecs_sparse_get_page(sparse, FLECS_SPARSE_PAGE(index)); if (!page || !page->sparse) { return; } uint64_t gen = flecs_sparse_strip_generation(&index); int32_t offset = FLECS_SPARSE_OFFSET(index); int32_t dense = page->sparse[offset]; if (dense) { uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); uint64_t cur_gen = dense_array[dense] & ECS_GENERATION_MASK; if (gen != cur_gen) { /* Generation doesn't match which means that the provided entity is * already not alive. */ return; } /* Increase generation */ dense_array[dense] = index | flecs_sparse_inc_gen(cur_gen); int32_t count = sparse->count; if (dense == (count - 1)) { /* If dense is the last used element, simply decrease count */ sparse->count --; } else if (dense < count) { /* If element is alive, move it to unused elements */ flecs_sparse_swap_dense(sparse, page, dense, count - 1); sparse->count --; } else { /* Element is not alive, nothing to be done */ return; } /* Reset memory to zero on remove */ void *ptr = DATA(page->data, sparse->size, offset); ecs_os_memset(ptr, 0, size); } else { /* Element is not paired and thus not alive, nothing to be done */ return; } } void* flecs_sparse_get_dense( const ecs_sparse_t *sparse, ecs_size_t size, int32_t dense_index) { ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); ecs_assert(dense_index < sparse->count, ECS_INVALID_PARAMETER, NULL); (void)size; dense_index ++; uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); return flecs_sparse_get_sparse(sparse, dense_index, dense_array[dense_index]); } bool flecs_sparse_is_alive( const ecs_sparse_t *sparse, uint64_t index) { ecs_page_t *page = flecs_sparse_get_page(sparse, FLECS_SPARSE_PAGE(index)); if (!page || !page->sparse) { return false; } int32_t offset = FLECS_SPARSE_OFFSET(index); int32_t dense = page->sparse[offset]; if (!dense || (dense >= sparse->count)) { return false; } uint64_t gen = flecs_sparse_strip_generation(&index); uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); uint64_t cur_gen = dense_array[dense] & ECS_GENERATION_MASK; if (cur_gen != gen) { return false; } ecs_assert(dense == page->sparse[offset], ECS_INTERNAL_ERROR, NULL); return true; } void* flecs_sparse_try( const ecs_sparse_t *sparse, ecs_size_t size, uint64_t index) { ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); (void)size; ecs_page_t *page = flecs_sparse_get_page(sparse, FLECS_SPARSE_PAGE(index)); if (!page || !page->sparse) { return NULL; } int32_t offset = FLECS_SPARSE_OFFSET(index); int32_t dense = page->sparse[offset]; if (!dense || (dense >= sparse->count)) { return NULL; } uint64_t gen = flecs_sparse_strip_generation(&index); uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); uint64_t cur_gen = dense_array[dense] & ECS_GENERATION_MASK; if (cur_gen != gen) { return NULL; } ecs_assert(dense == page->sparse[offset], ECS_INTERNAL_ERROR, NULL); return DATA(page->data, sparse->size, offset); } void* flecs_sparse_get( const ecs_sparse_t *sparse, ecs_size_t size, uint64_t index) { ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); (void)size; ecs_page_t *page = ecs_vec_get_t(&sparse->pages, ecs_page_t, FLECS_SPARSE_PAGE(index)); int32_t offset = FLECS_SPARSE_OFFSET(index); int32_t dense = page->sparse[offset]; ecs_assert(dense != 0, ECS_INTERNAL_ERROR, NULL); uint64_t gen = flecs_sparse_strip_generation(&index); uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); uint64_t cur_gen = dense_array[dense] & ECS_GENERATION_MASK; (void)cur_gen; (void)gen; ecs_assert(cur_gen == gen, ECS_INVALID_PARAMETER, NULL); ecs_assert(dense == page->sparse[offset], ECS_INTERNAL_ERROR, NULL); ecs_assert(dense < sparse->count, ECS_INTERNAL_ERROR, NULL); return DATA(page->data, sparse->size, offset); } void* flecs_sparse_get_any( const ecs_sparse_t *sparse, ecs_size_t size, uint64_t index) { ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); (void)size; flecs_sparse_strip_generation(&index); ecs_page_t *page = flecs_sparse_get_page(sparse, FLECS_SPARSE_PAGE(index)); if (!page || !page->sparse) { return NULL; } int32_t offset = FLECS_SPARSE_OFFSET(index); int32_t dense = page->sparse[offset]; bool in_use = dense && (dense < sparse->count); if (!in_use) { return NULL; } ecs_assert(dense == page->sparse[offset], ECS_INTERNAL_ERROR, NULL); return DATA(page->data, sparse->size, offset); } int32_t flecs_sparse_count( const ecs_sparse_t *sparse) { if (!sparse || !sparse->count) { return 0; } return sparse->count - 1; } const uint64_t* flecs_sparse_ids( const ecs_sparse_t *sparse) { ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); if (sparse->dense.array) { return &(ecs_vec_first_t(&sparse->dense, uint64_t)[1]); } else { return NULL; } } void ecs_sparse_init( ecs_sparse_t *sparse, ecs_size_t elem_size) { flecs_sparse_init(sparse, NULL, NULL, elem_size); } void* ecs_sparse_add( ecs_sparse_t *sparse, ecs_size_t elem_size) { return flecs_sparse_add(sparse, elem_size); } uint64_t ecs_sparse_last_id( const ecs_sparse_t *sparse) { return flecs_sparse_last_id(sparse); } int32_t ecs_sparse_count( const ecs_sparse_t *sparse) { return flecs_sparse_count(sparse); } void* ecs_sparse_get_dense( const ecs_sparse_t *sparse, ecs_size_t elem_size, int32_t index) { return flecs_sparse_get_dense(sparse, elem_size, index); } void* ecs_sparse_get( const ecs_sparse_t *sparse, ecs_size_t elem_size, uint64_t id) { return flecs_sparse_get(sparse, elem_size, id); } /** * @file datastructures/stack_allocator.c * @brief Stack allocator. * * The stack allocator enables pushing and popping values to a stack, and has * a lower overhead when compared to block allocators. A stack allocator is a * good fit for small temporary allocations. * * The stack allocator allocates memory in pages. If the requested size of an * allocation exceeds the page size, a regular allocator is used instead. */ #define FLECS_STACK_PAGE_OFFSET ECS_ALIGN(ECS_SIZEOF(ecs_stack_page_t), 16) int64_t ecs_stack_allocator_alloc_count = 0; int64_t ecs_stack_allocator_free_count = 0; static ecs_stack_page_t* flecs_stack_page_new(uint32_t page_id) { ecs_stack_page_t *result = ecs_os_malloc( FLECS_STACK_PAGE_OFFSET + ECS_STACK_PAGE_SIZE); result->data = ECS_OFFSET(result, FLECS_STACK_PAGE_OFFSET); result->next = NULL; result->id = page_id + 1; ecs_os_linc(&ecs_stack_allocator_alloc_count); return result; } void* flecs_stack_alloc( ecs_stack_t *stack, ecs_size_t size, ecs_size_t align) { ecs_assert(size > 0, ECS_INTERNAL_ERROR, NULL); ecs_stack_page_t *page = stack->tail_page; if (page == &stack->first && !page->data) { page->data = ecs_os_malloc(ECS_STACK_PAGE_SIZE); ecs_os_linc(&ecs_stack_allocator_alloc_count); } int16_t sp = flecs_ito(int16_t, ECS_ALIGN(page->sp, align)); int16_t next_sp = flecs_ito(int16_t, sp + size); void *result = NULL; if (next_sp > ECS_STACK_PAGE_SIZE) { if (size > ECS_STACK_PAGE_SIZE) { result = ecs_os_malloc(size); /* Too large for page */ goto done; } if (page->next) { page = page->next; } else { page = page->next = flecs_stack_page_new(page->id); } sp = 0; next_sp = flecs_ito(int16_t, size); stack->tail_page = page; } page->sp = next_sp; result = ECS_OFFSET(page->data, sp); done: #ifdef FLECS_SANITIZE ecs_os_memset(result, 0xAA, size); #endif return result; } void* flecs_stack_calloc( ecs_stack_t *stack, ecs_size_t size, ecs_size_t align) { void *ptr = flecs_stack_alloc(stack, size, align); ecs_os_memset(ptr, 0, size); return ptr; } void flecs_stack_free( void *ptr, ecs_size_t size) { if (size > ECS_STACK_PAGE_SIZE) { ecs_os_free(ptr); } } ecs_stack_cursor_t* flecs_stack_get_cursor( ecs_stack_t *stack) { ecs_assert(stack != NULL, ECS_INTERNAL_ERROR, NULL); ecs_stack_page_t *page = stack->tail_page; int16_t sp = stack->tail_page->sp; ecs_stack_cursor_t *result = flecs_stack_alloc_t(stack, ecs_stack_cursor_t); result->page = page; result->sp = sp; result->is_free = false; #ifdef FLECS_DEBUG ++ stack->cursor_count; result->owner = stack; #endif result->prev = stack->tail_cursor; stack->tail_cursor = result; return result; } #define FLECS_STACK_LEAK_MSG \ "a stack allocator leak is most likely due to an unterminated " \ "iteration: call ecs_iter_fini to fix" void flecs_stack_restore_cursor( ecs_stack_t *stack, ecs_stack_cursor_t *cursor) { if (!cursor) { return; } ecs_dbg_assert(stack == cursor->owner, ECS_INVALID_OPERATION, "attempting to restore a cursor for the wrong stack"); ecs_dbg_assert(stack->cursor_count > 0, ECS_DOUBLE_FREE, "double free detected in stack allocator"); ecs_assert(cursor->is_free == false, ECS_DOUBLE_FREE, "double free detected in stack allocator"); cursor->is_free = true; #ifdef FLECS_DEBUG -- stack->cursor_count; #endif /* If cursor is not the last on the stack no memory should be freed */ if (cursor != stack->tail_cursor) { return; } /* Iterate freed cursors to know how much memory we can free */ do { ecs_stack_cursor_t* prev = cursor->prev; if (!prev || !prev->is_free) { break; /* Found active cursor, free up until this point */ } cursor = prev; } while (cursor); stack->tail_cursor = cursor->prev; stack->tail_page = cursor->page; stack->tail_page->sp = cursor->sp; /* If the cursor count is zero, stack should be empty * if the cursor count is non-zero, stack should not be empty */ ecs_dbg_assert((stack->cursor_count == 0) == (stack->tail_page == &stack->first && stack->tail_page->sp == 0), ECS_LEAK_DETECTED, FLECS_STACK_LEAK_MSG); } void flecs_stack_reset( ecs_stack_t *stack) { ecs_dbg_assert(stack->cursor_count == 0, ECS_LEAK_DETECTED, FLECS_STACK_LEAK_MSG); stack->tail_page = &stack->first; stack->first.sp = 0; stack->tail_cursor = NULL; } void flecs_stack_init( ecs_stack_t *stack) { ecs_os_zeromem(stack); stack->tail_page = &stack->first; stack->first.data = NULL; } void flecs_stack_fini( ecs_stack_t *stack) { ecs_stack_page_t *next, *cur = &stack->first; ecs_dbg_assert(stack->cursor_count == 0, ECS_LEAK_DETECTED, FLECS_STACK_LEAK_MSG); ecs_assert(stack->tail_page == &stack->first, ECS_LEAK_DETECTED, FLECS_STACK_LEAK_MSG); ecs_assert(stack->tail_page->sp == 0, ECS_LEAK_DETECTED, FLECS_STACK_LEAK_MSG); do { next = cur->next; if (cur == &stack->first) { if (cur->data) { ecs_os_linc(&ecs_stack_allocator_free_count); } ecs_os_free(cur->data); } else { ecs_os_linc(&ecs_stack_allocator_free_count); ecs_os_free(cur); } } while ((cur = next)); } /** * @file datastructures/strbuf.c * @brief Utility for constructing strings. * * A buffer builds up a list of elements which individually can be up to N bytes * large. While appending, data is added to these elements. More elements are * added on the fly when needed. When an application calls ecs_strbuf_get, all * elements are combined in one string and the element administration is freed. * * This approach prevents reallocs of large blocks of memory, and therefore * copying large blocks of memory when appending to a large buffer. A buffer * preallocates some memory for the element overhead so that for small strings * there is hardly any overhead, while for large strings the overhead is offset * by the reduced time spent on copying memory. * * The functionality provided by strbuf is similar to std::stringstream. */ #include /** * stm32tpl -- STM32 C++ Template Peripheral Library * Visit https://github.com/antongus/stm32tpl for new versions * * Copyright (c) 2011-2020 Anton B. Gusev aka AHTOXA */ #define MAX_PRECISION (10) #define EXP_THRESHOLD (3) #define INT64_MAX_F ((double)INT64_MAX) static const double rounders[MAX_PRECISION + 1] = { 0.5, // 0 0.05, // 1 0.005, // 2 0.0005, // 3 0.00005, // 4 0.000005, // 5 0.0000005, // 6 0.00000005, // 7 0.000000005, // 8 0.0000000005, // 9 0.00000000005 // 10 }; static char* flecs_strbuf_itoa( char *buf, int64_t v) { char *ptr = buf; char * p1; char c; if (!v) { *ptr++ = '0'; } else { if (v < 0) { ptr[0] = '-'; ptr ++; v *= -1; } char *p = ptr; while (v) { int64_t vdiv = v / 10; int64_t vmod = v - (vdiv * 10); p[0] = (char)('0' + vmod); p ++; v = vdiv; } p1 = p; while (p > ptr) { c = *--p; *p = *ptr; *ptr++ = c; } ptr = p1; } return ptr; } static void flecs_strbuf_ftoa( ecs_strbuf_t *out, double f, int precision, char nan_delim) { char buf[64]; char * ptr = buf; char c; int64_t intPart; int64_t exp = 0; if (ecs_os_isnan(f)) { if (nan_delim) { ecs_strbuf_appendch(out, nan_delim); ecs_strbuf_appendlit(out, "NaN"); ecs_strbuf_appendch(out, nan_delim); return; } else { ecs_strbuf_appendlit(out, "NaN"); return; } } if (ecs_os_isinf(f)) { if (nan_delim) { ecs_strbuf_appendch(out, nan_delim); ecs_strbuf_appendlit(out, "Inf"); ecs_strbuf_appendch(out, nan_delim); return; } else { ecs_strbuf_appendlit(out, "Inf"); return; } } if (precision > MAX_PRECISION) { precision = MAX_PRECISION; } if (f < 0) { f = -f; *ptr++ = '-'; } if (precision < 0) { if (f < 1.0) precision = 6; else if (f < 10.0) precision = 5; else if (f < 100.0) precision = 4; else if (f < 1000.0) precision = 3; else if (f < 10000.0) precision = 2; else if (f < 100000.0) precision = 1; else precision = 0; } if (precision) { f += rounders[precision]; } /* Make sure that number can be represented as 64bit int, increase exp */ while (f > INT64_MAX_F) { f /= 1000 * 1000 * 1000; exp += 9; } intPart = (int64_t)f; f -= (double)intPart; ptr = flecs_strbuf_itoa(ptr, intPart); if (precision) { *ptr++ = '.'; while (precision--) { f *= 10.0; c = (char)f; *ptr++ = (char)('0' + c); f -= c; } } *ptr = 0; /* Remove trailing 0s */ while ((&ptr[-1] != buf) && (ptr[-1] == '0')) { ptr[-1] = '\0'; ptr --; } if (ptr != buf && ptr[-1] == '.') { ptr[-1] = '\0'; ptr --; } /* If 0s before . exceed threshold, convert to exponent to save space * without losing precision. */ char *cur = ptr; while ((&cur[-1] != buf) && (cur[-1] == '0')) { cur --; } if (exp || ((ptr - cur) > EXP_THRESHOLD)) { cur[0] = '\0'; exp += (ptr - cur); ptr = cur; } if (exp) { char *p1 = &buf[1]; if (nan_delim) { ecs_os_memmove(buf + 1, buf, 1 + (ptr - buf)); buf[0] = nan_delim; p1 ++; } /* Make sure that exp starts after first character */ c = p1[0]; if (c) { p1[0] = '.'; do { char t = (++p1)[0]; if (t == '.') { exp ++; p1 --; break; } p1[0] = c; c = t; exp ++; } while (c); ptr = p1 + 1; } else { ptr = p1; } ptr[0] = 'e'; ptr = flecs_strbuf_itoa(ptr + 1, exp); if (nan_delim) { ptr[0] = nan_delim; ptr ++; } ptr[0] = '\0'; } ecs_strbuf_appendstrn(out, buf, (int32_t)(ptr - buf)); } /* Add an extra element to the buffer */ static void flecs_strbuf_grow( ecs_strbuf_t *b) { if (!b->content) { b->content = b->small_string; b->size = ECS_STRBUF_SMALL_STRING_SIZE; } else if (b->content == b->small_string) { b->size *= 2; b->content = ecs_os_malloc_n(char, b->size); ecs_os_memcpy(b->content, b->small_string, b->length); } else { b->size *= 2; if (b->size < 16) b->size = 16; b->content = ecs_os_realloc_n(b->content, char, b->size); } } static char* flecs_strbuf_ptr( ecs_strbuf_t *b) { ecs_assert(b->content != NULL, ECS_INTERNAL_ERROR, NULL); return &b->content[b->length]; } /* Append a format string to a buffer */ static void flecs_strbuf_vappend( ecs_strbuf_t *b, const char* str, va_list args) { va_list arg_cpy; if (!str) { return; } /* Compute the memory required to add the string to the buffer. If user * provided buffer, use space left in buffer, otherwise use space left in * current element. */ int32_t mem_left = b->size - b->length; int32_t mem_required; va_copy(arg_cpy, args); if (b->content) { mem_required = ecs_os_vsnprintf( flecs_strbuf_ptr(b), flecs_itosize(mem_left), str, args); } else { mem_required = ecs_os_vsnprintf(NULL, 0, str, args); mem_left = 0; } ecs_assert(mem_required != -1, ECS_INTERNAL_ERROR, NULL); if ((mem_required + 1) >= mem_left) { while ((mem_required + 1) >= mem_left) { flecs_strbuf_grow(b); mem_left = b->size - b->length; } ecs_os_vsnprintf(flecs_strbuf_ptr(b), flecs_itosize(mem_required + 1), str, arg_cpy); } b->length += mem_required; va_end(arg_cpy); } static void flecs_strbuf_appendstr( ecs_strbuf_t *b, const char* str, int n) { int32_t mem_left = b->size - b->length; while (n >= mem_left) { flecs_strbuf_grow(b); mem_left = b->size - b->length; } ecs_os_memcpy(flecs_strbuf_ptr(b), str, n); b->length += n; } static void flecs_strbuf_appendch( ecs_strbuf_t *b, char ch) { if (b->size == b->length) { flecs_strbuf_grow(b); } flecs_strbuf_ptr(b)[0] = ch; b->length ++; } void ecs_strbuf_vappend( ecs_strbuf_t *b, const char* fmt, va_list args) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(fmt != NULL, ECS_INVALID_PARAMETER, NULL); flecs_strbuf_vappend(b, fmt, args); } void ecs_strbuf_append( ecs_strbuf_t *b, const char* fmt, ...) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(fmt != NULL, ECS_INVALID_PARAMETER, NULL); va_list args; va_start(args, fmt); flecs_strbuf_vappend(b, fmt, args); va_end(args); } void ecs_strbuf_appendstrn( ecs_strbuf_t *b, const char* str, int32_t len) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); flecs_strbuf_appendstr(b, str, len); } void ecs_strbuf_appendch( ecs_strbuf_t *b, char ch) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); flecs_strbuf_appendch(b, ch); } void ecs_strbuf_appendint( ecs_strbuf_t *b, int64_t v) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); char numbuf[32]; char *ptr = flecs_strbuf_itoa(numbuf, v); ecs_strbuf_appendstrn(b, numbuf, flecs_ito(int32_t, ptr - numbuf)); } void ecs_strbuf_appendflt( ecs_strbuf_t *b, double flt, char nan_delim) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); flecs_strbuf_ftoa(b, flt, 10, nan_delim); } void ecs_strbuf_appendbool( ecs_strbuf_t *buffer, bool v) { ecs_assert(buffer != NULL, ECS_INVALID_PARAMETER, NULL); if (v) { ecs_strbuf_appendlit(buffer, "true"); } else { ecs_strbuf_appendlit(buffer, "false"); } } void ecs_strbuf_appendstr( ecs_strbuf_t *b, const char* str) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); flecs_strbuf_appendstr(b, str, ecs_os_strlen(str)); } void ecs_strbuf_mergebuff( ecs_strbuf_t *b, ecs_strbuf_t *src) { if (src->content) { ecs_strbuf_appendstr(b, src->content); } ecs_strbuf_reset(src); } char* ecs_strbuf_get( ecs_strbuf_t *b) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); char *result = b->content; if (!result) { return NULL; } ecs_strbuf_appendch(b, '\0'); result = b->content; if (result == b->small_string) { result = ecs_os_memdup_n(result, char, b->length + 1); } b->length = 0; b->content = NULL; b->size = 0; b->list_sp = 0; return result; } char* ecs_strbuf_get_small( ecs_strbuf_t *b) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); char *result = b->content; result[b->length] = '\0'; b->length = 0; b->content = NULL; b->size = 0; return result; } void ecs_strbuf_reset( ecs_strbuf_t *b) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); if (b->content && b->content != b->small_string) { ecs_os_free(b->content); } *b = ECS_STRBUF_INIT; } void ecs_strbuf_list_push( ecs_strbuf_t *b, const char *list_open, const char *separator) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(list_open != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(separator != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(b->list_sp >= 0, ECS_INVALID_OPERATION, "strbuf list is corrupt"); b->list_sp ++; ecs_assert(b->list_sp < ECS_STRBUF_MAX_LIST_DEPTH, ECS_INVALID_OPERATION, "max depth for strbuf list stack exceeded"); b->list_stack[b->list_sp].count = 0; b->list_stack[b->list_sp].separator = separator; if (list_open) { char ch = list_open[0]; if (ch && !list_open[1]) { ecs_strbuf_appendch(b, ch); } else { ecs_strbuf_appendstr(b, list_open); } } } void ecs_strbuf_list_pop( ecs_strbuf_t *b, const char *list_close) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(list_close != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(b->list_sp > 0, ECS_INVALID_OPERATION, "pop called more often than push for strbuf list"); b->list_sp --; if (list_close) { char ch = list_close[0]; if (ch && !list_close[1]) { ecs_strbuf_appendch(b, list_close[0]); } else { ecs_strbuf_appendstr(b, list_close); } } } void ecs_strbuf_list_next( ecs_strbuf_t *b) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); int32_t list_sp = b->list_sp; if (b->list_stack[list_sp].count != 0) { const char *sep = b->list_stack[list_sp].separator; if (sep && !sep[1]) { ecs_strbuf_appendch(b, sep[0]); } else { ecs_strbuf_appendstr(b, sep); } } b->list_stack[list_sp].count ++; } void ecs_strbuf_list_appendch( ecs_strbuf_t *b, char ch) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); ecs_strbuf_list_next(b); flecs_strbuf_appendch(b, ch); } void ecs_strbuf_list_append( ecs_strbuf_t *b, const char *fmt, ...) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(fmt != NULL, ECS_INVALID_PARAMETER, NULL); ecs_strbuf_list_next(b); va_list args; va_start(args, fmt); flecs_strbuf_vappend(b, fmt, args); va_end(args); } void ecs_strbuf_list_appendstr( ecs_strbuf_t *b, const char *str) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); ecs_strbuf_list_next(b); ecs_strbuf_appendstr(b, str); } void ecs_strbuf_list_appendstrn( ecs_strbuf_t *b, const char *str, int32_t n) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); ecs_strbuf_list_next(b); ecs_strbuf_appendstrn(b, str, n); } int32_t ecs_strbuf_written( const ecs_strbuf_t *b) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); return b->length; } static ecs_switch_page_t* flecs_switch_page_ensure( ecs_switch_t* sw, uint32_t elem) { int32_t page_index = FLECS_SPARSE_PAGE(elem); ecs_vec_set_min_count_zeromem_t( sw->hdrs.allocator, &sw->pages, ecs_switch_page_t, page_index + 1); ecs_switch_page_t *page = ecs_vec_get_t( &sw->pages, ecs_switch_page_t, page_index); if (!ecs_vec_count(&page->nodes)) { ecs_vec_init_t(sw->hdrs.allocator, &page->nodes, ecs_switch_node_t, FLECS_SPARSE_PAGE_SIZE); ecs_vec_init_t(sw->hdrs.allocator, &page->values, uint64_t, FLECS_SPARSE_PAGE_SIZE); ecs_vec_set_min_count_zeromem_t(sw->hdrs.allocator, &page->nodes, ecs_switch_node_t, FLECS_SPARSE_PAGE_SIZE); ecs_vec_set_min_count_zeromem_t(sw->hdrs.allocator, &page->values, uint64_t, FLECS_SPARSE_PAGE_SIZE); } return page; } static ecs_switch_page_t* flecs_switch_page_get( const ecs_switch_t* sw, uint32_t elem) { int32_t page_index = FLECS_SPARSE_PAGE(elem); if (page_index >= ecs_vec_count(&sw->pages)) { return NULL; } ecs_switch_page_t *page = ecs_vec_get_t( &sw->pages, ecs_switch_page_t, page_index); if (!ecs_vec_count(&page->nodes)) { return NULL; } return page; } static void flecs_switch_page_fini( ecs_switch_t* sw, ecs_switch_page_t *page) { if (ecs_vec_count(&page->nodes)) { ecs_vec_fini_t(sw->hdrs.allocator, &page->nodes, ecs_switch_node_t); ecs_vec_fini_t(sw->hdrs.allocator, &page->values, uint64_t); } } static ecs_switch_node_t* flecs_switch_get_node( ecs_switch_t* sw, uint32_t element) { if (!element) { return NULL; } ecs_switch_page_t *page = flecs_switch_page_ensure(sw, element); int32_t page_offset = FLECS_SPARSE_OFFSET(element); return ecs_vec_get_t(&page->nodes, ecs_switch_node_t, page_offset); } void flecs_switch_init( ecs_switch_t* sw, ecs_allocator_t *allocator) { ecs_map_init(&sw->hdrs, allocator); ecs_vec_init_t(allocator, &sw->pages, ecs_switch_page_t, 0); } void flecs_switch_fini( ecs_switch_t* sw) { int32_t i, count = ecs_vec_count(&sw->pages); ecs_switch_page_t *pages = ecs_vec_first(&sw->pages); for (i = 0; i < count; i ++) { flecs_switch_page_fini(sw, &pages[i]); } ecs_vec_fini_t(sw->hdrs.allocator, &sw->pages, ecs_switch_page_t); ecs_map_fini(&sw->hdrs); } bool flecs_switch_set( ecs_switch_t *sw, uint32_t element, uint64_t value) { ecs_switch_page_t *page = flecs_switch_page_ensure(sw, element); int32_t page_offset = FLECS_SPARSE_OFFSET(element); uint64_t *elem = ecs_vec_get_t(&page->values, uint64_t, page_offset); if (elem[0] == value) { return false; } ecs_switch_node_t *node = ecs_vec_get_t( &page->nodes, ecs_switch_node_t, page_offset); uint64_t prev_value = elem[0]; if (prev_value) { ecs_switch_node_t *prev = flecs_switch_get_node(sw, node->prev); if (prev) { prev->next = node->next; } ecs_switch_node_t *next = flecs_switch_get_node(sw, node->next); if (next) { next->prev = node->prev; } if (!prev) { uint64_t *hdr = ecs_map_get(&sw->hdrs, prev_value); ecs_assert(hdr[0] == (uint64_t)element, ECS_INTERNAL_ERROR, NULL); hdr[0] = (uint64_t)node->next; } } elem[0] = value; if (value) { uint64_t *hdr = ecs_map_ensure(&sw->hdrs, value); if (!hdr[0]) { hdr[0] = (uint64_t)element; 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_assert(size != 0, ECS_INVALID_PARAMETER, NULL); v->array = NULL; v->count = 0; if (elem_count) { if (allocator) { v->array = flecs_alloc(allocator, size * elem_count); } else { v->array = ecs_os_malloc(size * elem_count); } } v->size = elem_count; #ifdef FLECS_SANITIZE v->elem_size = size; #endif } void ecs_vec_init_if( ecs_vec_t *vec, ecs_size_t size) { ecs_san_assert(!vec->elem_size || vec->elem_size == size, ECS_INVALID_PARAMETER, NULL); (void)vec; (void)size; #ifdef FLECS_SANITIZE if (!vec->elem_size) { ecs_assert(vec->count == 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(vec->size == 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(vec->array == NULL, ECS_INTERNAL_ERROR, NULL); vec->elem_size = size; } #endif } void ecs_vec_fini( ecs_allocator_t *allocator, ecs_vec_t *v, ecs_size_t size) { if (v->array) { ecs_san_assert(!size || size == v->elem_size, ECS_INVALID_PARAMETER, NULL); if (allocator) { flecs_free(allocator, size * v->size, v->array); } else { ecs_os_free(v->array); } v->array = NULL; v->count = 0; v->size = 0; } } ecs_vec_t* ecs_vec_reset( ecs_allocator_t *allocator, ecs_vec_t *v, ecs_size_t size) { if (!v->size) { ecs_vec_init(allocator, v, size, 0); } else { ecs_san_assert(size == v->elem_size, ECS_INTERNAL_ERROR, NULL); ecs_vec_clear(v); } return v; } void ecs_vec_clear( ecs_vec_t *vec) { vec->count = 0; } ecs_vec_t ecs_vec_copy( ecs_allocator_t *allocator, const ecs_vec_t *v, ecs_size_t size) { ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); void *array; if (allocator) { array = flecs_dup(allocator, size * v->size, v->array); } else { array = ecs_os_memdup(v->array, size * v->size); } return (ecs_vec_t) { .count = v->count, .size = v->size, .array = array #ifdef FLECS_SANITIZE , .elem_size = size #endif }; } ecs_vec_t ecs_vec_copy_shrink( ecs_allocator_t *allocator, const ecs_vec_t *v, ecs_size_t size) { ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); int32_t count = v->count; void *array = NULL; if (count) { if (allocator) { array = flecs_dup(allocator, size * count, v->array); } else { array = ecs_os_memdup(v->array, size * count); } } return (ecs_vec_t) { .count = count, .size = count, .array = array #ifdef FLECS_SANITIZE , .elem_size = size #endif }; } void ecs_vec_reclaim( ecs_allocator_t *allocator, ecs_vec_t *v, ecs_size_t size) { ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); int32_t count = v->count; if (count < v->size) { if (count) { if (allocator) { v->array = flecs_realloc( allocator, size * count, size * v->size, v->array); } else { v->array = ecs_os_realloc(v->array, size * count); } v->size = count; } else { ecs_vec_fini(allocator, v, size); } } } void ecs_vec_set_size( ecs_allocator_t *allocator, ecs_vec_t *v, ecs_size_t size, int32_t elem_count) { ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); if (v->size != elem_count) { if (elem_count < v->count) { elem_count = v->count; } elem_count = flecs_next_pow_of_2(elem_count); if (elem_count < 2) { elem_count = 2; } if (elem_count != v->size) { if (allocator) { v->array = flecs_realloc( allocator, size * elem_count, size * v->size, v->array); } else { v->array = ecs_os_realloc(v->array, size * elem_count); } v->size = elem_count; } } } void ecs_vec_set_min_size( struct ecs_allocator_t *allocator, ecs_vec_t *vec, ecs_size_t size, int32_t elem_count) { if (elem_count > vec->size) { ecs_vec_set_size(allocator, vec, size, elem_count); } } void ecs_vec_set_min_count( struct ecs_allocator_t *allocator, ecs_vec_t *vec, ecs_size_t size, int32_t elem_count) { ecs_vec_set_min_size(allocator, vec, size, elem_count); if (vec->count < elem_count) { vec->count = elem_count; } } void ecs_vec_set_min_count_zeromem( struct ecs_allocator_t *allocator, ecs_vec_t *vec, ecs_size_t size, int32_t elem_count) { int32_t count = vec->count; if (count < elem_count) { ecs_vec_set_min_count(allocator, vec, size, elem_count); ecs_os_memset(ECS_ELEM(vec->array, size, count), 0, size * (elem_count - count)); } } void ecs_vec_set_count( ecs_allocator_t *allocator, ecs_vec_t *v, ecs_size_t size, int32_t elem_count) { ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); if (v->count != elem_count) { if (v->size < elem_count) { ecs_vec_set_size(allocator, v, size, elem_count); } v->count = elem_count; } } void* ecs_vec_grow( ecs_allocator_t *allocator, ecs_vec_t *v, ecs_size_t size, int32_t elem_count) { ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); ecs_assert(elem_count > 0, ECS_INTERNAL_ERROR, NULL); int32_t count = v->count; ecs_vec_set_count(allocator, v, size, count + elem_count); return ECS_ELEM(v->array, size, count); } void* ecs_vec_append( ecs_allocator_t *allocator, ecs_vec_t *v, ecs_size_t size) { ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); int32_t count = v->count; if (v->size == count) { ecs_vec_set_size(allocator, v, size, count + 1); } v->count = count + 1; return ECS_ELEM(v->array, size, count); } void ecs_vec_remove( ecs_vec_t *v, ecs_size_t size, int32_t index) { ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); ecs_assert(index < v->count, ECS_OUT_OF_RANGE, NULL); if (index == --v->count) { return; } ecs_os_memcpy( ECS_ELEM(v->array, size, index), ECS_ELEM(v->array, size, v->count), size); } void ecs_vec_remove_last( ecs_vec_t *v) { v->count --; } int32_t ecs_vec_count( const ecs_vec_t *v) { return v->count; } int32_t ecs_vec_size( const ecs_vec_t *v) { return v->size; } void* ecs_vec_get( const ecs_vec_t *v, ecs_size_t size, int32_t index) { ecs_san_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); ecs_assert(index >= 0, ECS_OUT_OF_RANGE, NULL); ecs_assert(index < v->count, ECS_OUT_OF_RANGE, NULL); return ECS_ELEM(v->array, size, index); } void* ecs_vec_last( const ecs_vec_t *v, ecs_size_t size) { ecs_san_assert(!v->elem_size || size == v->elem_size, ECS_INVALID_PARAMETER, NULL); return ECS_ELEM(v->array, size, v->count - 1); } void* ecs_vec_first( const ecs_vec_t *v) { return v->array; } /** * @file queries/api.c * @brief User facing API for rules. */ #include /* 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 (!(q->world->flags & EcsWorldQuit)) { if (ecs_os_has_threading()) { int32_t idr_keep_alive = ecs_os_adec(&idr->keep_alive); ecs_assert(idr_keep_alive >= 0, ECS_INTERNAL_ERROR, NULL); (void)idr_keep_alive; } else { idr->keep_alive --; ecs_assert(idr->keep_alive >= 0, ECS_INTERNAL_ERROR, NULL); } } } } if (impl->tokens) { flecs_free(&impl->stage->allocator, impl->tokens_len, impl->tokens); } if (impl->cache) { flecs_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_run_aperiodic(q->world, EcsAperiodicEmptyTables); ecs_query_impl_t *impl = flecs_query_impl(q); if (impl->cache && q->flags & EcsQueryIsCacheable) { result.results = flecs_query_cache_table_count(impl->cache); result.entities = flecs_query_cache_entity_count(impl->cache); result.tables = flecs_query_cache_table_count(impl->cache); result.empty_tables = flecs_query_cache_empty_table_count(impl->cache); } else { ecs_iter_t it = flecs_query_iter(q->world, q); it.flags |= 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_run_aperiodic(q->world, EcsAperiodicEmptyTables); ecs_query_impl_t *impl = flecs_query_impl(q); if (impl->cache && q->flags & EcsQueryIsCacheable) { return flecs_query_cache_table_count(impl->cache) != 0; } else { ecs_iter_t it = flecs_query_iter(q->world, q); return ecs_iter_is_true(&it); } } int32_t ecs_query_match_count( const ecs_query_t *q) { flecs_poly_assert(q, ecs_query_t); ecs_query_impl_t *impl = flecs_query_impl(q); if (!impl->cache) { return 0; } else { return impl->cache->match_count; } } 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; } } /** * @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) { ecs_strbuf_appendstr(buf, term->src.name); } else { /* Shouldn't happen */ } if (ECS_TERM_REF_ID(&term->first) == EcsPredEq) { if (term->oper == EcsNot) { ecs_strbuf_appendlit(buf, " != "); } else { ecs_strbuf_appendlit(buf, " == "); } } else if (ECS_TERM_REF_ID(&term->first) == EcsPredMatch) { ecs_strbuf_appendlit(buf, " ~= "); } if (term->second.id & EcsIsEntity) { if (term->second.id != 0) { ecs_get_path_w_sep_buf(world, 0, ECS_TERM_REF_ID(&term->second), ".", NULL, buf, 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); } 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) { 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 '{'"); } } 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