/** * @file table.c * @brief Table storage implementation. * * Tables are the data structure that store the component data. Tables have * columns for each component in the table, and rows for each entity stored in * the table. Once created, the component list for a table doesn't change, but * entities can move from one table to another. * * Each table has a type, which is a vector with the (component) ids in the * table. The vector is sorted by id, which ensures that there can be only one * table for each unique combination of components. * * Not all ids in a table have to be components. Tags are ids that have no * data type associated with them, and as a result don't need to be explicitly * stored beyond an element in the table type. To save space and speed up table * creation, each table has a reference to a "storage table", which is a table * that only includes component ids (so excluding tags). * * Note that the actual data is not stored on the storage table. The storage * table is only used for sharing administration. A storage_map member maps * between column indices of the table and its storage table. Tables are * refcounted, which ensures that storage tables won't be deleted if other * tables have references to it. */ #include "flecs.h" /** * @file private_api.h * @brief Private functions. */ #ifndef FLECS_PRIVATE_H #define FLECS_PRIVATE_H /** * @file private_types.h * @brief Private types. */ #ifndef FLECS_PRIVATE_TYPES_H #define FLECS_PRIVATE_TYPES_H #ifndef __MACH__ #ifndef _POSIX_C_SOURCE #define _POSIX_C_SOURCE 200809L #endif #endif #include #include #include /** * @file datastructures/entity_index.h * @brief Entity index data structure. * * The entity index stores the table, row for an entity id. It is implemented as * a sparse set. This file contains convenience macros for working with the * entity index. */ #ifndef FLECS_ENTITY_INDEX_H #define FLECS_ENTITY_INDEX_H #define ecs_eis(world) (&((world)->store.entity_index)) #define flecs_entities_get(world, entity) flecs_sparse_get_t(ecs_eis(world), ecs_record_t, entity) #define flecs_entities_try(world, entity) flecs_sparse_try_t(ecs_eis(world), ecs_record_t, entity) #define flecs_entities_get_any(world, entity) flecs_sparse_get_any_t(ecs_eis(world), ecs_record_t, entity) #define flecs_entities_ensure(world, entity) flecs_sparse_ensure_t(ecs_eis(world), ecs_record_t, entity) #define flecs_entities_remove(world, entity) flecs_sparse_remove_t(ecs_eis(world), ecs_record_t, entity) #define flecs_entities_set_generation(world, entity) flecs_sparse_set_generation(ecs_eis(world), entity) #define flecs_entities_is_alive(world, entity) flecs_sparse_is_alive(ecs_eis(world), entity) #define flecs_entities_is_valid(world, entity) flecs_sparse_is_valid(ecs_eis(world), entity) #define flecs_entities_get_current(world, entity) flecs_sparse_get_current(ecs_eis(world), entity) #define flecs_entities_exists(world, entity) flecs_sparse_exists(ecs_eis(world), entity) #define flecs_entities_new_id(world) flecs_sparse_new_id(ecs_eis(world)) #define flecs_entities_new_ids(world, count) flecs_sparse_new_ids(ecs_eis(world), count) #define flecs_entities_set_size(world, size) flecs_sparse_set_size(ecs_eis(world), size) #define flecs_entities_count(world) flecs_sparse_count(ecs_eis(world)) #define flecs_entities_fini(world) flecs_sparse_fini(ecs_eis(world)) #endif /** * @file datastructures/stack_allocator.h * @brief Stack allocator. */ #ifndef FLECS_STACK_ALLOCATOR_H #define FLECS_STACK_ALLOCATOR_H /** Stack allocator for quick allocation of small temporary values */ #define ECS_STACK_PAGE_SIZE (4096) typedef struct ecs_stack_page_t { void *data; struct ecs_stack_page_t *next; int16_t sp; uint32_t id; } ecs_stack_page_t; typedef struct ecs_stack_t { ecs_stack_page_t first; ecs_stack_page_t *cur; } ecs_stack_t; void flecs_stack_init( ecs_stack_t *stack); void flecs_stack_fini( ecs_stack_t *stack); void* flecs_stack_alloc( ecs_stack_t *stack, ecs_size_t size, ecs_size_t align); #define flecs_stack_alloc_t(stack, T)\ flecs_stack_alloc(stack, ECS_SIZEOF(T), ECS_ALIGNOF(T)) #define flecs_stack_alloc_n(stack, T, count)\ flecs_stack_alloc(stack, ECS_SIZEOF(T) * count, ECS_ALIGNOF(T)) void* flecs_stack_calloc( ecs_stack_t *stack, ecs_size_t size, ecs_size_t align); #define flecs_stack_calloc_t(stack, T)\ flecs_stack_calloc(stack, ECS_SIZEOF(T), ECS_ALIGNOF(T)) #define flecs_stack_calloc_n(stack, T, count)\ flecs_stack_calloc(stack, ECS_SIZEOF(T) * count, ECS_ALIGNOF(T)) void flecs_stack_free( void *ptr, ecs_size_t size); #define flecs_stack_free_t(ptr, T)\ flecs_stack_free(ptr, ECS_SIZEOF(T)) #define flecs_stack_free_n(ptr, T, count)\ flecs_stack_free(ptr, ECS_SIZEOF(T) * count) void flecs_stack_reset( ecs_stack_t *stack); ecs_stack_cursor_t flecs_stack_get_cursor( ecs_stack_t *stack); void flecs_stack_restore_cursor( ecs_stack_t *stack, const ecs_stack_cursor_t *cursor); #endif /** * @file bitset.h * @brief Bitset data structure. */ #ifndef FLECS_BITSET_H #define FLECS_BITSET_H #ifdef __cplusplus extern "C" { #endif typedef struct ecs_bitset_t { uint64_t *data; int32_t count; ecs_size_t size; } ecs_bitset_t; /** Initialize bitset. */ FLECS_DBG_API void flecs_bitset_init( ecs_bitset_t *bs); /** Deinialize bitset. */ FLECS_DBG_API void flecs_bitset_fini( ecs_bitset_t *bs); /** Add n elements to bitset. */ FLECS_DBG_API void flecs_bitset_addn( ecs_bitset_t *bs, int32_t count); /** Ensure element exists. */ FLECS_DBG_API void flecs_bitset_ensure( ecs_bitset_t *bs, int32_t count); /** Set element. */ FLECS_DBG_API void flecs_bitset_set( ecs_bitset_t *bs, int32_t elem, bool value); /** Get element. */ FLECS_DBG_API bool flecs_bitset_get( const ecs_bitset_t *bs, int32_t elem); /** Return number of elements. */ FLECS_DBG_API int32_t flecs_bitset_count( const ecs_bitset_t *bs); /** Remove from bitset. */ FLECS_DBG_API void flecs_bitset_remove( ecs_bitset_t *bs, int32_t elem); /** Swap values in bitset. */ FLECS_DBG_API void flecs_bitset_swap( ecs_bitset_t *bs, int32_t elem_a, int32_t elem_b); #ifdef __cplusplus } #endif #endif /** * @file switch_list.h * @brief Interleaved linked list for storing mutually exclusive values. */ #ifndef FLECS_SWITCH_LIST_H #define FLECS_SWITCH_LIST_H typedef struct ecs_switch_header_t { int32_t element; /* First element for value */ int32_t count; /* Number of elements for value */ } ecs_switch_header_t; typedef struct ecs_switch_node_t { int32_t next; /* Next node in list */ int32_t prev; /* Prev node in list */ } ecs_switch_node_t; struct ecs_switch_t { ecs_map_t hdrs; /* map */ ecs_vec_t nodes; /* vec */ ecs_vec_t values; /* vec */ }; /** Init new switch. */ FLECS_DBG_API void flecs_switch_init( ecs_switch_t* sw, ecs_allocator_t *allocator, int32_t elements); /** Fini switch. */ FLECS_DBG_API void flecs_switch_fini( ecs_switch_t *sw); /** Remove all values. */ FLECS_DBG_API void flecs_switch_clear( ecs_switch_t *sw); /** Add element to switch, initialize value to 0 */ FLECS_DBG_API void flecs_switch_add( ecs_switch_t *sw); /** Set number of elements in switch list */ FLECS_DBG_API void flecs_switch_set_count( ecs_switch_t *sw, int32_t count); /** Get number of elements */ FLECS_DBG_API int32_t flecs_switch_count( ecs_switch_t *sw); /** Ensure that element exists. */ FLECS_DBG_API void flecs_switch_ensure( ecs_switch_t *sw, int32_t count); /** Add n elements. */ FLECS_DBG_API void flecs_switch_addn( ecs_switch_t *sw, int32_t count); /** Set value of element. */ FLECS_DBG_API void flecs_switch_set( ecs_switch_t *sw, int32_t element, uint64_t value); /** Remove element. */ FLECS_DBG_API void flecs_switch_remove( ecs_switch_t *sw, int32_t element); /** Get value for element. */ FLECS_DBG_API uint64_t flecs_switch_get( const ecs_switch_t *sw, int32_t element); /** Swap element. */ FLECS_DBG_API void flecs_switch_swap( ecs_switch_t *sw, int32_t elem_1, int32_t elem_2); /** Get vector with all values. Use together with count(). */ FLECS_DBG_API ecs_vec_t* flecs_switch_values( const ecs_switch_t *sw); /** Return number of different values. */ FLECS_DBG_API int32_t flecs_switch_case_count( const ecs_switch_t *sw, uint64_t value); /** Return first element for value. */ FLECS_DBG_API int32_t flecs_switch_first( const ecs_switch_t *sw, uint64_t value); /** Return next element for value. Use with first(). */ FLECS_DBG_API int32_t flecs_switch_next( const ecs_switch_t *sw, int32_t elem); #ifdef __cplusplus extern "C" { #endif #ifdef __cplusplus } #endif #endif /* Used in id records to keep track of entities used with id flags */ extern const ecs_entity_t EcsFlag; #define ECS_MAX_JOBS_PER_WORKER (16) /* Magic number for a flecs object */ #define ECS_OBJECT_MAGIC (0x6563736f) /* Tags associated with poly for (Poly, tag) components */ #define ecs_world_t_tag invalid #define ecs_stage_t_tag invalid #define ecs_query_t_tag EcsQuery #define ecs_rule_t_tag EcsQuery #define ecs_table_t_tag invalid #define ecs_filter_t_tag EcsQuery #define ecs_observer_t_tag EcsObserver /* Mixin kinds */ typedef enum ecs_mixin_kind_t { EcsMixinWorld, EcsMixinEntity, EcsMixinObservable, EcsMixinIterable, EcsMixinDtor, EcsMixinMax } ecs_mixin_kind_t; /* The mixin array contains pointers to mixin members for different kinds of * flecs objects. This allows the API to retrieve data from an object regardless * of its type. Each mixin array is only stored once per type */ struct ecs_mixins_t { const char *type_name; /* Include name of mixin type so debug code doesn't * need to know about every object */ ecs_size_t elems[EcsMixinMax]; }; /* Mixin tables */ extern ecs_mixins_t ecs_world_t_mixins; extern ecs_mixins_t ecs_stage_t_mixins; extern ecs_mixins_t ecs_filter_t_mixins; extern ecs_mixins_t ecs_query_t_mixins; extern ecs_mixins_t ecs_trigger_t_mixins; extern ecs_mixins_t ecs_observer_t_mixins; /* Types that have no mixins */ #define ecs_table_t_mixins (&(ecs_mixins_t){ NULL }) /* Scope for flecs internals, like observers used for builtin features */ extern const ecs_entity_t EcsFlecsInternals; /** Type used for internal string hashmap */ typedef struct ecs_hashed_string_t { char *value; ecs_size_t length; uint64_t hash; } ecs_hashed_string_t; /* Table event type for notifying tables of world events */ typedef enum ecs_table_eventkind_t { EcsTableTriggersForId, EcsTableNoTriggersForId, } ecs_table_eventkind_t; typedef struct ecs_table_event_t { ecs_table_eventkind_t kind; /* Query event */ ecs_query_t *query; /* Component info event */ ecs_entity_t component; /* Event match */ ecs_entity_t event; /* If the nubmer of fields gets out of hand, this can be turned into a union * but since events are very temporary objects, this works for now and makes * initializing an event a bit simpler. */ } ecs_table_event_t; /** Stage-specific component data */ struct ecs_data_t { ecs_vec_t entities; /* Entity identifiers */ ecs_vec_t records; /* Ptrs to records in main entity index */ ecs_vec_t *columns; /* Component columns */ ecs_switch_t *sw_columns; /* Switch columns */ ecs_bitset_t *bs_columns; /* Bitset columns */ }; /** Cache of added/removed components for non-trivial edges between tables */ #define ECS_TABLE_DIFF_INIT { .added = {0}} typedef struct ecs_table_diff_t { ecs_type_t added; /* Components added between tables */ ecs_type_t removed; /* Components removed between tables */ } ecs_table_diff_t; /** Builder for table diff. The table diff type itself doesn't use ecs_vec_t to * conserve memory on table edges (a type doesn't have the size field), whereas * a vec for the builder is more convenient to use & has allocator support. */ typedef struct ecs_table_diff_builder_t { ecs_vec_t added; ecs_vec_t removed; } ecs_table_diff_builder_t; /** Edge linked list (used to keep track of incoming edges) */ typedef struct ecs_graph_edge_hdr_t { struct ecs_graph_edge_hdr_t *prev; struct ecs_graph_edge_hdr_t *next; } ecs_graph_edge_hdr_t; /** Single edge. */ typedef struct ecs_graph_edge_t { ecs_graph_edge_hdr_t hdr; ecs_table_t *from; /* Edge source table */ ecs_table_t *to; /* Edge destination table */ ecs_table_diff_t *diff; /* Index into diff vector, if non trivial edge */ ecs_id_t id; /* Id associated with edge */ } ecs_graph_edge_t; /* Edges to other tables. */ typedef struct ecs_graph_edges_t { ecs_graph_edge_t *lo; /* Small array optimized for low edges */ ecs_map_t hi; /* Map for hi edges (map) */ } ecs_graph_edges_t; /* Table graph node */ typedef struct ecs_graph_node_t { /* Outgoing edges */ ecs_graph_edges_t add; ecs_graph_edges_t remove; /* Incoming edges (next = add edges, prev = remove edges) */ ecs_graph_edge_hdr_t refs; } ecs_graph_node_t; /** A table is the Flecs equivalent of an archetype. Tables store all entities * with a specific set of components. Tables are automatically created when an * entity has a set of components not previously observed before. When a new * table is created, it is automatically matched with existing queries */ struct ecs_table_t { uint64_t id; /* Table id in sparse set */ ecs_type_t type; /* Identifies table type in type_index */ ecs_flags32_t flags; /* Flags for testing table properties */ uint16_t storage_count; /* Number of components (excluding tags) */ uint16_t generation; /* Used for table cleanup */ struct ecs_table_record_t *records; /* Array with table records */ ecs_table_t *storage_table; /* Table without tags */ ecs_id_t *storage_ids; /* Component ids (prevent indirection) */ int32_t *storage_map; /* Map type <-> data type * - 0..count(T): type -> data_type * - count(T)..count(S): data_type -> type */ ecs_graph_node_t node; /* Graph node */ ecs_data_t data; /* Component storage */ ecs_type_info_t **type_info; /* Cached type info */ int32_t *dirty_state; /* Keep track of changes in columns */ int16_t sw_count; int16_t sw_offset; int16_t bs_count; int16_t bs_offset; int32_t refcount; /* Increased when used as storage table */ int32_t lock; /* Prevents modifications */ int32_t observed_count; /* Number of observed entities in table */ uint16_t record_count; /* Table record count including wildcards */ }; /** Must appear as first member in payload of table cache */ typedef struct ecs_table_cache_hdr_t { struct ecs_table_cache_t *cache; ecs_table_t *table; struct ecs_table_cache_hdr_t *prev, *next; bool empty; } ecs_table_cache_hdr_t; /** Linked list of tables in table cache */ typedef struct ecs_table_cache_list_t { ecs_table_cache_hdr_t *first; ecs_table_cache_hdr_t *last; int32_t count; } ecs_table_cache_list_t; /** Table cache */ typedef struct ecs_table_cache_t { ecs_map_t index; /* */ ecs_table_cache_list_t tables; ecs_table_cache_list_t empty_tables; } ecs_table_cache_t; /* Sparse query column */ typedef struct flecs_switch_term_t { ecs_switch_t *sw_column; ecs_entity_t sw_case; int32_t signature_column_index; } flecs_switch_term_t; /* Bitset query column */ typedef struct flecs_bitset_term_t { ecs_bitset_t *bs_column; int32_t column_index; } flecs_bitset_term_t; /* Entity filter. This filters the entities of a matched table, for example when * it has disabled components or union relationships (switch). */ typedef struct ecs_entity_filter_t { ecs_vec_t sw_terms; /* Terms with switch (union) entity filter */ ecs_vec_t bs_terms; /* Terms with bitset (toggle) entity filter */ bool has_filter; } ecs_entity_filter_t; typedef struct ecs_entity_filter_iter_t { ecs_entity_filter_t *entity_filter; int32_t *columns; ecs_table_range_t range; int32_t bs_offset; int32_t sw_offset; int32_t sw_smallest; } ecs_entity_filter_iter_t; typedef struct ecs_query_table_match_t ecs_query_table_match_t; /** List node used to iterate tables in a query. * The list of nodes dictates the order in which tables should be iterated by a * query. A single node may refer to the table multiple times with different * offset/count parameters, which enables features such as sorting. */ struct ecs_query_table_node_t { ecs_query_table_node_t *next, *prev; ecs_table_t *table; /* The current table. */ uint64_t group_id; /* Value used to organize tables in groups */ int32_t offset; /* Starting point in table */ int32_t count; /* Number of entities to iterate in table */ ecs_query_table_match_t *match; /* Reference to the match */ }; /** Type containing data for a table matched with a query. * A single table can have multiple matches, if the query contains wildcards. */ struct ecs_query_table_match_t { ecs_query_table_node_t node; /* Embedded list node */ int32_t *columns; /* Mapping from query fields to table columns */ int32_t *storage_columns; /* Mapping from query fields to storage columns */ ecs_id_t *ids; /* Resolved (component) ids for current table */ ecs_entity_t *sources; /* Subjects (sources) of ids */ ecs_size_t *sizes; /* Sizes for ids for current table */ ecs_vec_t refs; /* Cached components for non-this terms */ ecs_entity_filter_t entity_filter; /* Entity specific filters */ /* Next match in cache for same table (includes empty tables) */ ecs_query_table_match_t *next_match; int32_t *monitor; /* Used to monitor table for changes */ }; /** A single table can occur multiple times in the cache when a term matches * multiple table columns. */ typedef struct ecs_query_table_t { ecs_table_cache_hdr_t hdr; /* Header for ecs_table_cache_t */ ecs_query_table_match_t *first; /* List with matches for table */ ecs_query_table_match_t *last; /* Last discovered match for table */ uint64_t table_id; int32_t rematch_count; /* Track whether table was rematched */ } ecs_query_table_t; /** Points to the beginning & ending of a query group */ typedef struct ecs_query_table_list_t { ecs_query_table_node_t *first; ecs_query_table_node_t *last; ecs_query_group_info_t info; } ecs_query_table_list_t; /* Query event type for notifying queries of world events */ typedef enum ecs_query_eventkind_t { EcsQueryTableMatch, EcsQueryTableRematch, EcsQueryTableUnmatch, EcsQueryOrphan } ecs_query_eventkind_t; typedef struct ecs_query_event_t { ecs_query_eventkind_t kind; ecs_table_t *table; ecs_query_t *parent_query; } ecs_query_event_t; /* Query level block allocators have sizes that depend on query field count */ typedef struct ecs_query_allocators_t { ecs_block_allocator_t columns; ecs_block_allocator_t ids; ecs_block_allocator_t sources; ecs_block_allocator_t sizes; ecs_block_allocator_t monitors; } ecs_query_allocators_t; /** Query that is automatically matched against tables */ struct ecs_query_t { ecs_header_t hdr; /* Query filter */ ecs_filter_t filter; /* Tables matched with query */ ecs_table_cache_t cache; /* Linked list with all matched non-empty tables, in iteration order */ ecs_query_table_list_t list; /* Contains head/tail to nodes of query groups (if group_by is used) */ ecs_map_t groups; /* Table sorting */ ecs_entity_t order_by_component; ecs_order_by_action_t order_by; ecs_sort_table_action_t sort_table; ecs_vec_t table_slices; int32_t order_by_term; /* Table grouping */ ecs_entity_t group_by_id; ecs_group_by_action_t group_by; ecs_group_create_action_t on_group_create; ecs_group_delete_action_t on_group_delete; void *group_by_ctx; ecs_ctx_free_t group_by_ctx_free; /* Subqueries */ ecs_query_t *parent; ecs_vec_t subqueries; /* Flags for query properties */ ecs_flags32_t flags; /* Monitor generation */ int32_t monitor_generation; int32_t cascade_by; /* Identify cascade column */ int32_t match_count; /* How often have tables been (un)matched */ int32_t prev_match_count; /* Track if sorting is needed */ int32_t rematch_count; /* Track which tables were added during rematch */ /* Mixins */ ecs_iterable_t iterable; ecs_poly_dtor_t dtor; /* Query-level allocators */ ecs_query_allocators_t allocators; }; /** All observers for a specific (component) id */ typedef struct ecs_event_id_record_t { /* Triggers for Self */ ecs_map_t self; /* map */ ecs_map_t self_up; /* map */ ecs_map_t up; /* map */ ecs_map_t observers; /* map */ /* Triggers for SuperSet, SubSet */ ecs_map_t set_observers; /* map */ /* Triggers for Self with non-This subject */ ecs_map_t entity_observers; /* map */ /* Number of active observers for (component) id */ int32_t observer_count; } ecs_event_id_record_t; /* World level allocators are for operations that are not multithreaded */ typedef struct ecs_world_allocators_t { ecs_map_params_t ptr; ecs_map_params_t query_table_list; ecs_block_allocator_t query_table; ecs_block_allocator_t query_table_match; ecs_block_allocator_t graph_edge_lo; ecs_block_allocator_t graph_edge; ecs_block_allocator_t id_record; ecs_block_allocator_t id_record_chunk; ecs_block_allocator_t table_diff; ecs_block_allocator_t sparse_chunk; ecs_block_allocator_t hashmap; /* Temporary vectors used for creating table diff id sequences */ ecs_table_diff_builder_t diff_builder; } ecs_world_allocators_t; /* Stage level allocators are for operations that can be multithreaded */ typedef struct ecs_stage_allocators_t { ecs_stack_t iter_stack; ecs_stack_t deser_stack; ecs_block_allocator_t cmd_entry_chunk; } ecs_stage_allocators_t; /** Types for deferred operations */ typedef enum ecs_cmd_kind_t { EcsOpClone, EcsOpBulkNew, EcsOpAdd, EcsOpRemove, EcsOpSet, EcsOpEmplace, EcsOpMut, EcsOpModified, EcsOpAddModified, EcsOpPath, EcsOpDelete, EcsOpClear, EcsOpOnDeleteAction, EcsOpEnable, EcsOpDisable, EcsOpSkip } ecs_cmd_kind_t; typedef struct ecs_cmd_1_t { void *value; /* Component value (used by set / get_mut) */ ecs_size_t size; /* Size of value */ bool clone_value; /* Clone entity with value (used for clone) */ } ecs_cmd_1_t; typedef struct ecs_cmd_n_t { ecs_entity_t *entities; int32_t count; } ecs_cmd_n_t; typedef struct ecs_cmd_t { ecs_cmd_kind_t kind; /* Command kind */ int32_t next_for_entity; /* Next operation for entity */ ecs_id_t id; /* (Component) id */ ecs_id_record_t *idr; /* Id record (only for set/mut/emplace) */ ecs_entity_t entity; /* Entity id */ union { ecs_cmd_1_t _1; /* Data for single entity operation */ ecs_cmd_n_t _n; /* Data for multi entity operation */ } is; } ecs_cmd_t; /* Entity specific metadata for command in defer queue */ typedef struct ecs_cmd_entry_t { int32_t first; int32_t last; /* If -1, a delete command was inserted */ } ecs_cmd_entry_t; /** A stage is a context that allows for safely using the API from multiple * threads. Stage pointers can be passed to the world argument of API * operations, which causes the operation to be ran on the stage instead of the * world. */ struct ecs_stage_t { ecs_header_t hdr; /* Unique id that identifies the stage */ int32_t id; /* Deferred command queue */ int32_t defer; ecs_vec_t commands; ecs_stack_t defer_stack; /* Temp memory used by deferred commands */ ecs_sparse_t cmd_entries; /* - command combining */ /* Thread context */ ecs_world_t *thread_ctx; /* Points to stage when a thread stage */ ecs_world_t *world; /* Reference to world */ ecs_os_thread_t thread; /* Thread handle (0 if no threading is used) */ /* One-shot actions to be executed after the merge */ ecs_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 */ ecs_entity_t *lookup_path; /* Search path used by lookup operations */ /* Properties */ bool auto_merge; /* Should this stage automatically merge? */ bool async; /* Is stage asynchronous? (write only) */ /* Thread specific allocators */ ecs_stage_allocators_t allocators; ecs_allocator_t allocator; /* Caches for rule creation */ ecs_vec_t variables; ecs_vec_t operations; }; /* Component monitor */ typedef struct ecs_monitor_t { ecs_vec_t queries; /* vector */ bool is_dirty; /* Should queries be rematched? */ } ecs_monitor_t; /* Component monitors */ typedef struct ecs_monitor_set_t { ecs_map_t monitors; /* map */ bool is_dirty; /* Should monitors be evaluated? */ } ecs_monitor_set_t; /* Data stored for id marked for deletion */ typedef struct ecs_marked_id_t { ecs_id_record_t *idr; ecs_id_t id; ecs_entity_t action; /* Set explicitly for delete_with, remove_all */ bool delete_id; } ecs_marked_id_t; typedef struct ecs_store_t { /* Entity lookup */ ecs_sparse_t entity_index; /* sparse */ /* Table lookup by id */ ecs_sparse_t tables; /* sparse */ /* Table lookup by hash */ ecs_hashmap_t table_map; /* hashmap */ /* Root table */ ecs_table_t root; /* Records cache */ ecs_vec_t records; /* Stack of ids being deleted. */ ecs_vec_t marked_ids; /* vector */ } ecs_store_t; /* fini actions */ typedef struct ecs_action_elem_t { ecs_fini_action_t action; void *ctx; } ecs_action_elem_t; /** The world stores and manages all ECS data. An application can have more than * one world, but data is not shared between worlds. */ struct ecs_world_t { ecs_header_t hdr; /* -- Type metadata -- */ ecs_id_record_t *id_index_lo; ecs_map_t id_index_hi; /* map */ ecs_sparse_t type_info; /* sparse */ /* -- Cached handle to id records -- */ ecs_id_record_t *idr_wildcard; ecs_id_record_t *idr_wildcard_wildcard; ecs_id_record_t *idr_any; ecs_id_record_t *idr_isa_wildcard; ecs_id_record_t *idr_childof_0; ecs_id_record_t *idr_childof_wildcard; ecs_id_record_t *idr_identifier_name; /* -- Mixins -- */ ecs_world_t *self; ecs_observable_t observable; ecs_iterable_t iterable; /* Unique id per generated event used to prevent duplicate notifications */ int32_t event_id; /* Is entity range checking enabled? */ bool range_check_enabled; /* -- Data storage -- */ ecs_store_t store; /* -- Pending table event buffers -- */ ecs_sparse_t *pending_buffer; /* sparse */ ecs_sparse_t *pending_tables; /* sparse */ /* Used to track when cache needs to be updated */ ecs_monitor_set_t monitors; /* map */ /* -- Systems -- */ ecs_entity_t pipeline; /* Current pipeline */ /* -- Identifiers -- */ ecs_hashmap_t aliases; ecs_hashmap_t symbols; /* -- Staging -- */ ecs_stage_t *stages; /* Stages */ int32_t stage_count; /* Number of stages */ /* -- Multithreading -- */ ecs_os_cond_t worker_cond; /* Signal that worker threads can start */ ecs_os_cond_t sync_cond; /* Signal that worker thread job is done */ ecs_os_mutex_t sync_mutex; /* Mutex for job_cond */ int32_t workers_running; /* Number of threads running */ int32_t workers_waiting; /* Number of workers waiting on sync */ /* -- Time management -- */ ecs_time_t world_start_time; /* Timestamp of simulation start */ ecs_time_t frame_start_time; /* Timestamp of frame start */ ecs_ftime_t fps_sleep; /* Sleep time to prevent fps overshoot */ /* -- Metrics -- */ ecs_world_info_t info; /* -- World flags -- */ ecs_flags32_t flags; /* Count that increases when component monitors change */ int32_t monitor_generation; /* -- Allocators -- */ ecs_world_allocators_t allocators; /* Static allocation sizes */ ecs_allocator_t allocator; /* Dynamic allocation sizes */ void *context; /* Application context */ ecs_vec_t fini_actions; /* Callbacks to execute when world exits */ }; #endif /** * @file table_cache.h * @brief Data structure for fast table iteration/lookups. */ #ifndef FLECS_TABLE_CACHE_H_ #define FLECS_TABLE_CACHE_H_ void ecs_table_cache_init( ecs_world_t *world, ecs_table_cache_t *cache); void ecs_table_cache_fini( ecs_table_cache_t *cache); void ecs_table_cache_insert( ecs_table_cache_t *cache, const ecs_table_t *table, ecs_table_cache_hdr_t *result); void ecs_table_cache_replace( ecs_table_cache_t *cache, const ecs_table_t *table, ecs_table_cache_hdr_t *elem); void* ecs_table_cache_remove( ecs_table_cache_t *cache, uint64_t table_id, ecs_table_cache_hdr_t *elem); void* ecs_table_cache_get( const ecs_table_cache_t *cache, const ecs_table_t *table); bool ecs_table_cache_set_empty( ecs_table_cache_t *cache, const ecs_table_t *table, bool empty); bool ecs_table_cache_is_empty( const ecs_table_cache_t *cache); #define flecs_table_cache_count(cache) (cache)->tables.count #define flecs_table_cache_empty_count(cache) (cache)->empty_tables.count bool flecs_table_cache_iter( ecs_table_cache_t *cache, ecs_table_cache_iter_t *out); bool flecs_table_cache_empty_iter( ecs_table_cache_t *cache, ecs_table_cache_iter_t *out); bool flecs_table_cache_all_iter( ecs_table_cache_t *cache, ecs_table_cache_iter_t *out); ecs_table_cache_hdr_t* _flecs_table_cache_next( ecs_table_cache_iter_t *it); #define flecs_table_cache_next(it, T)\ (ECS_CAST(T*, _flecs_table_cache_next(it))) #endif /** * @file id_record.h * @brief Index for looking up tables by (component) id. */ #ifndef FLECS_ID_RECORD_H #define FLECS_ID_RECORD_H /* Payload for id cache */ struct ecs_table_record_t { ecs_table_cache_hdr_t hdr; /* Table cache header */ int32_t column; /* First column where id occurs in table */ int32_t count; /* Number of times id occurs in table */ }; /* Linked list of id records */ typedef struct ecs_id_record_elem_t { struct ecs_id_record_t *prev, *next; } ecs_id_record_elem_t; typedef struct ecs_reachable_elem_t { const ecs_table_record_t *tr; ecs_record_t *record; ecs_entity_t src; ecs_id_t id; #ifndef NDEBUG ecs_table_t *table; #endif } ecs_reachable_elem_t; typedef struct ecs_reachable_cache_t { int32_t generation; int32_t current; ecs_vec_t ids; /* vec */ } ecs_reachable_cache_t; /* Payload for id index which contains all datastructures for an id. */ struct ecs_id_record_t { /* Cache with all tables that contain the id. Must be first member. */ ecs_table_cache_t cache; /* table_cache */ /* Id of record */ ecs_id_t id; /* Flags for id */ ecs_flags32_t flags; /* 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 */ /* Cached pointer to type info for id, if id contains data. */ const ecs_type_info_t *type_info; /* Parent id record. For pair records the parent is the (R, *) record. */ ecs_id_record_t *parent; /* Refcount */ int32_t refcount; /* Keep alive count. This count must be 0 when the id record is deleted. If * it is not 0, an application attempted to delete an id that was still * queried for. */ int32_t keep_alive; /* Cache invalidation counter */ ecs_reachable_cache_t reachable; /* Name lookup index (currently only used for ChildOf pairs) */ ecs_hashmap_t *name_index; }; /* Get id record for id */ ecs_id_record_t* flecs_id_record_get( const ecs_world_t *world, ecs_id_t id); /* Get id record for id for searching. * Same as flecs_id_record_get, but replaces (R, *) with (Union, R) if R is a * union relationship. */ ecs_id_record_t* flecs_query_id_record_get( const ecs_world_t *world, ecs_id_t id); /* Ensure id record for id */ ecs_id_record_t* flecs_id_record_ensure( ecs_world_t *world, ecs_id_t id); /* Increase refcount of id record */ void flecs_id_record_claim( ecs_world_t *world, ecs_id_record_t *idr); /* Decrease refcount of id record, delete if 0 */ int32_t flecs_id_record_release( ecs_world_t *world, ecs_id_record_t *idr); /* Release all empty tables in id record */ void flecs_id_record_release_tables( ecs_world_t *world, ecs_id_record_t *idr); /* Set (component) type info for id record */ bool flecs_id_record_set_type_info( ecs_world_t *world, ecs_id_record_t *idr, const ecs_type_info_t *ti); /* Ensure id record has name index */ ecs_hashmap_t* flecs_id_name_index_ensure( ecs_world_t *world, ecs_id_t id); /* Get name index for id record */ ecs_hashmap_t* flecs_id_name_index_get( const ecs_world_t *world, ecs_id_t id); /* Find table record for id */ ecs_table_record_t* flecs_table_record_get( const ecs_world_t *world, const ecs_table_t *table, ecs_id_t id); /* Find table record for id record */ const ecs_table_record_t* flecs_id_record_get_table( const ecs_id_record_t *idr, const ecs_table_t *table); /* Bootstrap cached id records */ void flecs_init_id_records( ecs_world_t *world); /* Cleanup all id records in world */ void flecs_fini_id_records( ecs_world_t *world); #endif /** * @file observable.h * @brief Functions for sending events. */ #ifndef FLECS_OBSERVABLE_H #define FLECS_OBSERVABLE_H ecs_event_record_t* flecs_event_record_get( const ecs_observable_t *o, ecs_entity_t event); ecs_event_record_t* flecs_event_record_ensure( ecs_observable_t *o, ecs_entity_t event); ecs_event_id_record_t* flecs_event_id_record_get( const ecs_event_record_t *er, ecs_id_t id); ecs_event_id_record_t* flecs_event_id_record_ensure( ecs_world_t *world, ecs_event_record_t *er, ecs_id_t id); void flecs_event_id_record_remove( ecs_event_record_t *er, ecs_id_t id); void flecs_observable_init( ecs_observable_t *observable); void flecs_observable_fini( ecs_observable_t *observable); bool flecs_check_observers_for_event( const ecs_poly_t *world, ecs_id_t id, ecs_entity_t event); void flecs_observer_fini( ecs_observer_t *observer); void flecs_emit( ecs_world_t *world, ecs_world_t *stage, ecs_event_desc_t *desc); bool flecs_default_observer_next_callback( ecs_iter_t *it); void flecs_observers_invoke( ecs_world_t *world, ecs_map_t *observers, ecs_iter_t *it, ecs_table_t *table, ecs_entity_t trav, int32_t evtx); void flecs_emit_propagate_invalidate( ecs_world_t *world, ecs_table_t *table, int32_t offset, int32_t count); #endif /** * @file iter.h * @brief Iterator utilities. */ #ifndef FLECS_ITER_H #define FLECS_ITER_H void flecs_iter_init( const ecs_world_t *world, ecs_iter_t *it, ecs_flags8_t fields); void flecs_iter_validate( ecs_iter_t *it); void flecs_iter_populate_data( ecs_world_t *world, ecs_iter_t *it, ecs_table_t *table, int32_t offset, int32_t count, void **ptrs, ecs_size_t *sizes); bool flecs_iter_next_row( ecs_iter_t *it); bool flecs_iter_next_instanced( ecs_iter_t *it, bool result); 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 table.c * @brief Table storage implementation. */ #ifndef FLECS_TABLE_H #define FLECS_TABLE_H /* Init table */ void flecs_table_init( ecs_world_t *world, ecs_table_t *table, ecs_table_t *from); /** Copy type. */ ecs_type_t flecs_type_copy( ecs_world_t *world, const ecs_type_t *src); /** Free type. */ void flecs_type_free( ecs_world_t *world, ecs_type_t *type); /** Find or create table for a set of components */ ecs_table_t* flecs_table_find_or_create( ecs_world_t *world, ecs_type_t *type); /* Initialize columns for data */ void flecs_table_init_data( ecs_world_t *world, ecs_table_t *table); /* Clear all entities from a table. */ void flecs_table_clear_entities( ecs_world_t *world, ecs_table_t *table); /* Reset a table to its initial state */ void flecs_table_reset( ecs_world_t *world, ecs_table_t *table); /* Clear all entities from the table. Do not invoke OnRemove systems */ void flecs_table_clear_entities_silent( ecs_world_t *world, ecs_table_t *table); /* Clear table data. Don't call OnRemove handlers. */ void flecs_table_clear_data( ecs_world_t *world, ecs_table_t *table, ecs_data_t *data); /* Return number of entities in data */ int32_t flecs_table_data_count( const ecs_data_t *data); /* Add a new entry to the table for the specified entity */ int32_t flecs_table_append( ecs_world_t *world, ecs_table_t *table, ecs_entity_t entity, ecs_record_t *record, bool construct, bool on_add); /* Delete an entity from the table. */ void flecs_table_delete( ecs_world_t *world, ecs_table_t *table, int32_t index, bool destruct); /* Make sure table records are in correct table cache list */ bool flecs_table_records_update_empty( ecs_table_t *table); /* Move a row from one table to another */ void flecs_table_move( ecs_world_t *world, ecs_entity_t dst_entity, ecs_entity_t src_entity, ecs_table_t *new_table, int32_t new_index, ecs_table_t *old_table, int32_t old_index, bool construct); /* Grow table with specified number of records. Populate table with entities, * starting from specified entity id. */ int32_t flecs_table_appendn( ecs_world_t *world, ecs_table_t *table, ecs_data_t *data, int32_t count, const ecs_entity_t *ids); /* Set table to a fixed size. Useful for preallocating memory in advance. */ void flecs_table_set_size( ecs_world_t *world, ecs_table_t *table, ecs_data_t *data, int32_t count); /* Shrink table to contents */ bool flecs_table_shrink( ecs_world_t *world, ecs_table_t *table); /* Get dirty state for table columns */ int32_t* flecs_table_get_dirty_state( ecs_world_t *world, ecs_table_t *table); /* Initialize root table */ void flecs_init_root_table( ecs_world_t *world); /* Unset components in table */ void flecs_table_remove_actions( ecs_world_t *world, ecs_table_t *table); /* Free table */ void flecs_table_free( ecs_world_t *world, ecs_table_t *table); /* Free table */ void flecs_table_free_type( ecs_world_t *world, ecs_table_t *table); /* Replace data */ void flecs_table_replace_data( ecs_world_t *world, ecs_table_t *table, ecs_data_t *data); /* Merge data of one table into another table */ void flecs_table_merge( ecs_world_t *world, ecs_table_t *new_table, ecs_table_t *old_table, ecs_data_t *new_data, ecs_data_t *old_data); void flecs_table_swap( ecs_world_t *world, ecs_table_t *table, int32_t row_1, int32_t row_2); ecs_table_t *flecs_table_traverse_add( ecs_world_t *world, ecs_table_t *table, ecs_id_t *id_ptr, ecs_table_diff_t *diff); ecs_table_t *flecs_table_traverse_remove( ecs_world_t *world, ecs_table_t *table, ecs_id_t *id_ptr, ecs_table_diff_t *diff); void flecs_table_mark_dirty( ecs_world_t *world, ecs_table_t *table, ecs_entity_t component); void flecs_table_notify( ecs_world_t *world, ecs_table_t *table, ecs_table_event_t *event); void flecs_table_clear_edges( ecs_world_t *world, ecs_table_t *table); void flecs_table_delete_entities( ecs_world_t *world, ecs_table_t *table); ecs_vec_t *ecs_table_column_for_id( const ecs_world_t *world, const ecs_table_t *table, ecs_id_t id); int32_t flecs_table_column_to_union_index( const ecs_table_t *table, int32_t column); /* Increase refcount of table (prevents deletion) */ void flecs_table_claim( ecs_world_t *world, ecs_table_t *table); /* Decreases refcount of table (may delete) */ bool flecs_table_release( ecs_world_t *world, ecs_table_t *table); /* Increase observer count of table */ void flecs_table_observer_add( ecs_table_t *table, int32_t value); /* Table diff builder, used to build id lists that indicate the difference in * ids between two tables. */ void flecs_table_diff_builder_init( ecs_world_t *world, ecs_table_diff_builder_t *builder); void flecs_table_diff_builder_fini( ecs_world_t *world, ecs_table_diff_builder_t *builder); void flecs_table_diff_builder_clear( ecs_table_diff_builder_t *builder); void flecs_table_diff_build_append_table( ecs_world_t *world, ecs_table_diff_builder_t *dst, ecs_table_diff_t *src); void flecs_table_diff_build( ecs_world_t *world, ecs_table_diff_builder_t *builder, ecs_table_diff_t *diff, int32_t added_offset, int32_t removed_offset); void flecs_table_diff_build_noalloc( ecs_table_diff_builder_t *builder, ecs_table_diff_t *diff); #endif /** * @file poly.h * @brief Functions for managing poly objects. */ #ifndef FLECS_POLY_H #define FLECS_POLY_H #include /* Initialize poly */ void* _ecs_poly_init( ecs_poly_t *object, int32_t kind, ecs_size_t size, ecs_mixins_t *mixins); #define ecs_poly_init(object, type)\ _ecs_poly_init(object, type##_magic, sizeof(type), &type##_mixins) /* Deinitialize object for specified type */ void _ecs_poly_fini( ecs_poly_t *object, int32_t kind); #define ecs_poly_fini(object, type)\ _ecs_poly_fini(object, type##_magic) /* Utility functions for creating an object on the heap */ #define ecs_poly_new(type)\ (type*)ecs_poly_init(ecs_os_calloc_t(type), type) #define ecs_poly_free(obj, type)\ ecs_poly_fini(obj, type);\ ecs_os_free(obj) /* Get or create poly component for an entity */ EcsPoly* _ecs_poly_bind( ecs_world_t *world, ecs_entity_t entity, ecs_entity_t tag); #define ecs_poly_bind(world, entity, T) \ _ecs_poly_bind(world, entity, T##_tag) void _ecs_poly_modified( ecs_world_t *world, ecs_entity_t entity, ecs_entity_t tag); #define ecs_poly_modified(world, entity, T) \ _ecs_poly_modified(world, entity, T##_tag) /* Get poly component for an entity */ const EcsPoly* _ecs_poly_bind_get( const ecs_world_t *world, ecs_entity_t entity, ecs_entity_t tag); #define ecs_poly_bind_get(world, entity, T) \ _ecs_poly_bind_get(world, entity, T##_tag) ecs_poly_t* _ecs_poly_get( const ecs_world_t *world, ecs_entity_t entity, ecs_entity_t tag); #define ecs_poly_get(world, entity, T) \ ((T*)_ecs_poly_get(world, entity, T##_tag)) /* Utilities for testing/asserting an object type */ #ifndef FLECS_NDEBUG void* _ecs_poly_assert( const ecs_poly_t *object, int32_t type, const char *file, int32_t line); #define ecs_poly_assert(object, type)\ _ecs_poly_assert(object, type##_magic, __FILE__, __LINE__) #define ecs_poly(object, T) ((T*)ecs_poly_assert(object, T)) #else #define ecs_poly_assert(object, type) #define ecs_poly(object, T) ((T*)object) #endif /* Utility functions for getting a mixin from an object */ ecs_iterable_t* ecs_get_iterable( const ecs_poly_t *poly); ecs_observable_t* ecs_get_observable( const ecs_poly_t *object); ecs_poly_dtor_t* ecs_get_dtor( const ecs_poly_t *poly); #endif /** * @file stage.h * @brief Stage functions. */ #ifndef FLECS_STAGE_H #define FLECS_STAGE_H /* Initialize stage data structures */ void flecs_stage_init( ecs_world_t *world, ecs_stage_t *stage); /* Deinitialize stage */ void flecs_stage_fini( ecs_world_t *world, ecs_stage_t *stage); /* Post-frame merge actions */ void flecs_stage_merge_post_frame( ecs_world_t *world, ecs_stage_t *stage); bool flecs_defer_cmd( ecs_stage_t *stage); bool flecs_defer_begin( ecs_world_t *world, ecs_stage_t *stage); bool flecs_defer_modified( ecs_stage_t *stage, ecs_entity_t entity, ecs_entity_t component); bool flecs_defer_clone( ecs_stage_t *stage, ecs_entity_t entity, ecs_entity_t src, bool clone_value); bool flecs_defer_bulk_new( ecs_world_t *world, ecs_stage_t *stage, int32_t count, ecs_id_t id, const ecs_entity_t **ids_out); bool flecs_defer_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 need_value); bool flecs_defer_end( ecs_world_t *world, ecs_stage_t *stage); bool flecs_defer_purge( ecs_world_t *world, ecs_stage_t *stage); #endif /** * @file world.c * @brief World-level API. */ #ifndef FLECS_WORLD_H #define FLECS_WORLD_H /* Get current stage */ ecs_stage_t* flecs_stage_from_world( ecs_world_t **world_ptr); /* Get current thread-specific stage from readonly world */ const ecs_stage_t* flecs_stage_from_readonly_world( const ecs_world_t *world); /* Get component callbacks */ const ecs_type_info_t *flecs_type_info_get( const ecs_world_t *world, ecs_entity_t component); /* Get or create component callbacks */ ecs_type_info_t* flecs_type_info_ensure( ecs_world_t *world, ecs_entity_t component); bool flecs_type_info_init_id( ecs_world_t *world, ecs_entity_t component, ecs_size_t size, ecs_size_t alignment, const ecs_type_hooks_t *li); #define flecs_type_info_init(world, T, ...)\ flecs_type_info_init_id(world, ecs_id(T), ECS_SIZEOF(T), ECS_ALIGNOF(T),\ &(ecs_type_hooks_t)__VA_ARGS__) void flecs_type_info_fini( ecs_type_info_t *ti); void flecs_type_info_free( ecs_world_t *world, ecs_entity_t component); void flecs_eval_component_monitors( ecs_world_t *world); void flecs_monitor_mark_dirty( ecs_world_t *world, ecs_entity_t id); void flecs_monitor_register( ecs_world_t *world, ecs_entity_t id, ecs_query_t *query); void flecs_monitor_unregister( ecs_world_t *world, ecs_entity_t id, ecs_query_t *query); void flecs_notify_tables( ecs_world_t *world, ecs_id_t id, ecs_table_event_t *event); void flecs_register_table( ecs_world_t *world, ecs_table_t *table); void flecs_unregister_table( ecs_world_t *world, ecs_table_t *table); void flecs_table_set_empty( ecs_world_t *world, ecs_table_t *table); void flecs_delete_table( ecs_world_t *world, ecs_table_t *table); void flecs_process_pending_tables( const ecs_world_t *world); /* Suspend/resume readonly state. To fully support implicit registration of * components, it should be possible to register components while the world is * in readonly mode. It is not uncommon that a component is used first from * within a system, which are often ran while in readonly mode. * * Suspending readonly mode is only allowed when the world is not multithreaded. * When a world is multithreaded, it is not safe to (even temporarily) leave * readonly mode, so a multithreaded application should always explicitly * register components in advance. * * These operations also suspend deferred mode. */ typedef struct ecs_suspend_readonly_state_t { bool is_readonly; bool is_deferred; int32_t defer_count; ecs_entity_t scope; ecs_entity_t with; ecs_vec_t commands; ecs_stack_t defer_stack; ecs_stage_t *stage; } ecs_suspend_readonly_state_t; ecs_world_t* flecs_suspend_readonly( const ecs_world_t *world, ecs_suspend_readonly_state_t *state); void flecs_resume_readonly( ecs_world_t *world, ecs_suspend_readonly_state_t *state); /* Convenience macro's for world allocator */ #define flecs_walloc(world, size)\ flecs_alloc(&world->allocator, size) #define flecs_walloc_n(world, T, count)\ flecs_alloc_n(&world->allocator, T, count) #define flecs_wcalloc(world, size)\ flecs_calloc(&world->allocator, size) #define flecs_wcalloc_n(world, T, count)\ flecs_calloc_n(&world->allocator, T, count) #define flecs_wfree(world, size, ptr)\ flecs_free(&world->allocator, size, ptr) #define flecs_wfree_n(world, T, count, ptr)\ flecs_free_n(&world->allocator, T, count, ptr) #define flecs_wrealloc(world, size_dst, size_src, ptr)\ flecs_realloc(&world->allocator, size_dst, size_src, ptr) #define flecs_wrealloc_n(world, T, count_dst, count_src, ptr)\ flecs_realloc_n(&world->allocator, T, count_dst, count_src, ptr) #define flecs_wdup(world, size, ptr)\ flecs_dup(&world->allocator, size, ptr) #define flecs_wdup_n(world, T, count, ptr)\ flecs_dup_n(&world->allocator, T, count, ptr) #endif /** * @file datastructures/qsort.h * @brief Quicksort implementation. */ /* From: https://github.com/svpv/qsort/blob/master/qsort.h * Use custom qsort implementation rather than relying on the version in libc to * ensure that results are consistent across platforms. */ /* * Copyright (c) 2013, 2017 Alexey Tourbin * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ /* * This is a traditional Quicksort implementation which mostly follows * [Sedgewick 1978]. Sorting is performed entirely on array indices, * while actual access to the array elements is abstracted out with the * user-defined `LESS` and `SWAP` primitives. * * Synopsis: * QSORT(N, LESS, SWAP); * where * N - the number of elements in A[]; * LESS(i, j) - compares A[i] to A[j]; * SWAP(i, j) - exchanges A[i] with A[j]. */ #ifndef QSORT_H #define QSORT_H /* Sort 3 elements. */ #define Q_SORT3(q_a1, q_a2, q_a3, Q_LESS, Q_SWAP) \ do { \ if (Q_LESS(q_a2, q_a1)) { \ if (Q_LESS(q_a3, q_a2)) \ Q_SWAP(q_a1, q_a3); \ else { \ Q_SWAP(q_a1, q_a2); \ if (Q_LESS(q_a3, q_a2)) \ Q_SWAP(q_a2, q_a3); \ } \ } \ else if (Q_LESS(q_a3, q_a2)) { \ Q_SWAP(q_a2, q_a3); \ if (Q_LESS(q_a2, q_a1)) \ Q_SWAP(q_a1, q_a2); \ } \ } while (0) /* Partition [q_l,q_r] around a pivot. After partitioning, * [q_l,q_j] are the elements that are less than or equal to the pivot, * while [q_i,q_r] are the elements greater than or equal to the pivot. */ #define Q_PARTITION(q_l, q_r, q_i, q_j, Q_UINT, Q_LESS, Q_SWAP) \ do { \ /* The middle element, not to be confused with the median. */ \ Q_UINT q_m = q_l + ((q_r - q_l) >> 1); \ /* Reorder the second, the middle, and the last items. \ * As [Edelkamp Weiss 2016] explain, using the second element \ * instead of the first one helps avoid bad behaviour for \ * decreasingly sorted arrays. This method is used in recent \ * versions of gcc's std::sort, see gcc bug 58437#c13, although \ * the details are somewhat different (cf. #c14). */ \ Q_SORT3(q_l + 1, q_m, q_r, Q_LESS, Q_SWAP); \ /* Place the median at the beginning. */ \ Q_SWAP(q_l, q_m); \ /* Partition [q_l+2, q_r-1] around the median which is in q_l. \ * q_i and q_j are initially off by one, they get decremented \ * in the do-while loops. */ \ q_i = q_l + 1; q_j = q_r; \ while (1) { \ do q_i++; while (Q_LESS(q_i, q_l)); \ do q_j--; while (Q_LESS(q_l, q_j)); \ if (q_i >= q_j) break; /* Sedgewick says "until j < i" */ \ Q_SWAP(q_i, q_j); \ } \ /* Compensate for the i==j case. */ \ q_i = q_j + 1; \ /* Put the median to its final place. */ \ Q_SWAP(q_l, q_j); \ /* The median is not part of the left subfile. */ \ q_j--; \ } while (0) /* Insertion sort is applied to small subfiles - this is contrary to * Sedgewick's suggestion to run a separate insertion sort pass after * the partitioning is done. The reason I don't like a separate pass * is that it triggers extra comparisons, because it can't see that the * medians are already in their final positions and need not be rechecked. * Since I do not assume that comparisons are cheap, I also do not try * to eliminate the (q_j > q_l) boundary check. */ #define Q_INSERTION_SORT(q_l, q_r, Q_UINT, Q_LESS, Q_SWAP) \ do { \ Q_UINT q_i, q_j; \ /* For each item starting with the second... */ \ for (q_i = q_l + 1; q_i <= q_r; q_i++) \ /* move it down the array so that the first part is sorted. */ \ for (q_j = q_i; q_j > q_l && (Q_LESS(q_j, q_j - 1)); q_j--) \ Q_SWAP(q_j, q_j - 1); \ } while (0) /* When the size of [q_l,q_r], i.e. q_r-q_l+1, is greater than or equal to * Q_THRESH, the algorithm performs recursive partitioning. When the size * drops below Q_THRESH, the algorithm switches to insertion sort. * The minimum valid value is probably 5 (with 5 items, the second and * the middle items, the middle itself being rounded down, are distinct). */ #define Q_THRESH 16 /* The main loop. */ #define Q_LOOP(Q_UINT, Q_N, Q_LESS, Q_SWAP) \ do { \ Q_UINT q_l = 0; \ Q_UINT q_r = (Q_N) - 1; \ Q_UINT q_sp = 0; /* the number of frames pushed to the stack */ \ struct { Q_UINT q_l, q_r; } \ /* On 32-bit platforms, to sort a "char[3GB+]" array, \ * it may take full 32 stack frames. On 64-bit CPUs, \ * though, the address space is limited to 48 bits. \ * The usage is further reduced if Q_N has a 32-bit type. */ \ q_st[sizeof(Q_UINT) > 4 && sizeof(Q_N) > 4 ? 48 : 32]; \ while (1) { \ if (q_r - q_l + 1 >= Q_THRESH) { \ Q_UINT q_i, q_j; \ Q_PARTITION(q_l, q_r, q_i, q_j, Q_UINT, Q_LESS, Q_SWAP); \ /* Now have two subfiles: [q_l,q_j] and [q_i,q_r]. \ * Dealing with them depends on which one is bigger. */ \ if (q_j - q_l >= q_r - q_i) \ Q_SUBFILES(q_l, q_j, q_i, q_r); \ else \ Q_SUBFILES(q_i, q_r, q_l, q_j); \ } \ else { \ Q_INSERTION_SORT(q_l, q_r, Q_UINT, Q_LESS, Q_SWAP); \ /* Pop subfiles from the stack, until it gets empty. */ \ if (q_sp == 0) break; \ q_sp--; \ q_l = q_st[q_sp].q_l; \ q_r = q_st[q_sp].q_r; \ } \ } \ } while (0) /* The missing part: dealing with subfiles. * Assumes that the first subfile is not smaller than the second. */ #define Q_SUBFILES(q_l1, q_r1, q_l2, q_r2) \ do { \ /* If the second subfile is only a single element, it needs \ * no further processing. The first subfile will be processed \ * on the next iteration (both subfiles cannot be only a single \ * element, due to Q_THRESH). */ \ if (q_l2 == q_r2) { \ q_l = q_l1; \ q_r = q_r1; \ } \ else { \ /* Otherwise, both subfiles need processing. \ * Push the larger subfile onto the stack. */ \ q_st[q_sp].q_l = q_l1; \ q_st[q_sp].q_r = q_r1; \ q_sp++; \ /* Process the smaller subfile on the next iteration. */ \ q_l = q_l2; \ q_r = q_r2; \ } \ } while (0) /* And now, ladies and gentlemen, may I proudly present to you... */ #define QSORT(Q_N, Q_LESS, Q_SWAP) \ do { \ if ((Q_N) > 1) \ /* We could check sizeof(Q_N) and use "unsigned", but at least \ * on x86_64, this has the performance penalty of up to 5%. */ \ Q_LOOP(ecs_size_t, Q_N, Q_LESS, Q_SWAP); \ } while (0) void ecs_qsort( void *base, ecs_size_t nitems, ecs_size_t size, int (*compar)(const void *, const void*)); #define ecs_qsort_t(base, nitems, T, compar) \ ecs_qsort(base, nitems, ECS_SIZEOF(T), compar) #endif /** * @file datastructures/name_index.h * @brief Data structure for resolving 64bit keys by string (name). */ #ifndef FLECS_NAME_INDEX_H #define FLECS_NAME_INDEX_H void flecs_name_index_init( ecs_hashmap_t *hm, ecs_allocator_t *allocator); 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_ensure(world, name);\ ecs_add_id(world, name, EcsFinal);\ ecs_add_pair(world, name, EcsChildOf, ecs_get_scope(world));\ ecs_set(world, name, EcsComponent, {.size = 0});\ ecs_set_name(world, name, (char*)&#name[ecs_os_strlen(world->info.name_prefix)]);\ ecs_set_symbol(world, name, #name) /* Bootstrap functions for other parts in the code */ void flecs_bootstrap_hierarchy(ecs_world_t *world); //////////////////////////////////////////////////////////////////////////////// //// Entity API //////////////////////////////////////////////////////////////////////////////// /* Mark an entity as being watched. This is used to trigger automatic rematching * when entities used in system expressions change their components. */ ecs_record_t* flecs_add_flag( ecs_world_t *world, ecs_entity_t entity, uint32_t flag); ecs_entity_t flecs_get_oneof( const ecs_world_t *world, ecs_entity_t e); void flecs_notify_on_remove( ecs_world_t *world, ecs_table_t *table, ecs_table_t *other_table, int32_t row, int32_t count, const ecs_type_t *diff); void flecs_notify_on_set( ecs_world_t *world, ecs_table_t *table, int32_t row, int32_t count, ecs_type_t *type, bool owned); int32_t flecs_relation_depth( const ecs_world_t *world, ecs_entity_t r, const ecs_table_t *table); void flecs_instantiate( ecs_world_t *world, ecs_entity_t base, ecs_table_t *table, int32_t row, int32_t count); void* flecs_get_base_component( const ecs_world_t *world, ecs_table_t *table, ecs_id_t id, ecs_id_record_t *table_index, int32_t recur_depth); void flecs_invoke_hook( ecs_world_t *world, ecs_table_t *table, int32_t count, int32_t row, ecs_entity_t *entities, void *ptr, ecs_id_t id, const ecs_type_info_t *ti, ecs_entity_t event, ecs_iter_action_t hook); //////////////////////////////////////////////////////////////////////////////// //// Query API //////////////////////////////////////////////////////////////////////////////// /* Match table with term */ bool flecs_term_match_table( ecs_world_t *world, const ecs_term_t *term, const ecs_table_t *table, ecs_id_t *id_out, int32_t *column_out, ecs_entity_t *subject_out, int32_t *match_indices, bool first, ecs_flags32_t iter_flags); /* Match table with filter */ bool flecs_filter_match_table( ecs_world_t *world, const ecs_filter_t *filter, const ecs_table_t *table, ecs_id_t *ids, int32_t *columns, ecs_entity_t *sources, int32_t *match_indices, int32_t *matches_left, bool first, int32_t skip_term, ecs_flags32_t iter_flags); ecs_iter_t flecs_filter_iter_w_flags( const ecs_world_t *stage, const ecs_filter_t *filter, ecs_flags32_t flags); void flecs_query_notify( ecs_world_t *world, ecs_query_t *query, ecs_query_event_t *event); ecs_id_t flecs_to_public_id( ecs_id_t id); ecs_id_t flecs_from_public_id( ecs_world_t *world, ecs_id_t id); void flecs_filter_apply_iter_flags( ecs_iter_t *it, const ecs_filter_t *filter); //////////////////////////////////////////////////////////////////////////////// //// Safe(r) integer casting //////////////////////////////////////////////////////////////////////////////// #define FLECS_CONVERSION_ERR(T, value)\ "illegal conversion from value " #value " to type " #T #define flecs_signed_char__ (CHAR_MIN < 0) #define flecs_signed_short__ true #define flecs_signed_int__ true #define flecs_signed_long__ true #define flecs_signed_size_t__ false #define flecs_signed_int8_t__ true #define flecs_signed_int16_t__ true #define flecs_signed_int32_t__ true #define flecs_signed_int64_t__ true #define flecs_signed_intptr_t__ true #define flecs_signed_uint8_t__ false #define flecs_signed_uint16_t__ false #define flecs_signed_uint32_t__ false #define flecs_signed_uint64_t__ false #define flecs_signed_uintptr_t__ false #define flecs_signed_ecs_size_t__ true #define flecs_signed_ecs_entity_t__ false uint64_t _flecs_ito( size_t dst_size, bool dst_signed, bool lt_zero, uint64_t value, const char *err); #ifndef FLECS_NDEBUG #define flecs_ito(T, value)\ (T)_flecs_ito(\ sizeof(T),\ flecs_signed_##T##__,\ (value) < 0,\ (uint64_t)(value),\ FLECS_CONVERSION_ERR(T, (value))) #define flecs_uto(T, value)\ (T)_flecs_ito(\ sizeof(T),\ flecs_signed_##T##__,\ false,\ (uint64_t)(value),\ FLECS_CONVERSION_ERR(T, (value))) #else #define flecs_ito(T, value) (T)(value) #define flecs_uto(T, value) (T)(value) #endif #define flecs_itosize(value) flecs_ito(size_t, (value)) #define flecs_utosize(value) flecs_uto(ecs_size_t, (value)) #define flecs_itoi16(value) flecs_ito(int16_t, (value)) #define flecs_itoi32(value) flecs_ito(int32_t, (value)) //////////////////////////////////////////////////////////////////////////////// //// Entity filter //////////////////////////////////////////////////////////////////////////////// void flecs_entity_filter_init( ecs_world_t *world, ecs_entity_filter_t *entity_filter, const ecs_filter_t *filter, const ecs_table_t *table, ecs_id_t *ids, int32_t *columns); void flecs_entity_filter_fini( ecs_world_t *world, ecs_entity_filter_t *entity_filter); int flecs_entity_filter_next( ecs_entity_filter_iter_t *it); //////////////////////////////////////////////////////////////////////////////// //// Utilities //////////////////////////////////////////////////////////////////////////////// uint64_t flecs_hash( const void *data, ecs_size_t length); /* Get next power of 2 */ int32_t flecs_next_pow_of_2( int32_t n); /* Convert 64bit value to ecs_record_t type. ecs_record_t is stored as 64bit int in the * entity index */ ecs_record_t flecs_to_row( uint64_t value); /* Get 64bit integer from ecs_record_t */ uint64_t flecs_from_row( ecs_record_t record); /* Convert a symbol name to an entity name by removing the prefix */ const char* flecs_name_from_symbol( ecs_world_t *world, const char *type_name); /* Compare function for entity ids */ int flecs_entity_compare( ecs_entity_t e1, const void *ptr1, ecs_entity_t e2, const void *ptr2); /* Compare function for entity ids which can be used with qsort */ int flecs_entity_compare_qsort( const void *e1, const void *e2); bool flecs_name_is_id( const char *name); ecs_entity_t flecs_name_to_id( const ecs_world_t *world, const char *name); /* Convert floating point to string */ char * ecs_ftoa( double f, char * buf, int precision); uint64_t flecs_string_hash( const void *ptr); void flecs_table_hashmap_init( ecs_world_t *world, ecs_hashmap_t *hm); #define assert_func(cond) _assert_func(cond, #cond, __FILE__, __LINE__, __func__) void _assert_func( bool cond, const char *cond_str, const char *file, int32_t line, const char *func); void flecs_dump_backtrace( FILE *stream); void flecs_colorize_buf( char *msg, bool enable_colors, ecs_strbuf_t *buf); bool flecs_isident( char ch); int32_t flecs_search_relation_w_idr( const ecs_world_t *world, const ecs_table_t *table, int32_t offset, ecs_id_t id, ecs_entity_t rel, ecs_flags32_t flags, ecs_entity_t *subject_out, ecs_id_t *id_out, struct ecs_table_record_t **tr_out, ecs_id_record_t *idr); #endif /* Table sanity check to detect storage issues. Only enabled in SANITIZE mode as * this can severly slow down many ECS operations. */ #ifdef FLECS_SANITIZE static void flecs_table_check_sanity(ecs_table_t *table) { int32_t size = ecs_vec_size(&table->data.entities); int32_t count = ecs_vec_count(&table->data.entities); ecs_assert(size == ecs_vec_size(&table->data.records), ECS_INTERNAL_ERROR, NULL); ecs_assert(count == ecs_vec_count(&table->data.records), ECS_INTERNAL_ERROR, NULL); int32_t i; int32_t sw_offset = table->sw_offset; int32_t sw_count = table->sw_count; int32_t bs_offset = table->bs_offset; int32_t bs_count = table->bs_count; int32_t type_count = table->type.count; ecs_id_t *ids = table->type.array; ecs_assert((sw_count + sw_offset) <= type_count, ECS_INTERNAL_ERROR, NULL); ecs_assert((bs_count + bs_offset) <= type_count, ECS_INTERNAL_ERROR, NULL); ecs_table_t *storage_table = table->storage_table; if (storage_table) { ecs_assert(table->storage_count == storage_table->type.count, ECS_INTERNAL_ERROR, NULL); ecs_assert(table->storage_ids == storage_table->type.array, ECS_INTERNAL_ERROR, NULL); int32_t storage_count = table->storage_count; ecs_assert(type_count >= storage_count, ECS_INTERNAL_ERROR, NULL); int32_t *storage_map = table->storage_map; ecs_assert(storage_map != NULL, ECS_INTERNAL_ERROR, NULL); ecs_id_t *storage_ids = table->storage_ids; for (i = 0; i < type_count; i ++) { if (storage_map[i] != -1) { ecs_assert(ids[i] == storage_ids[storage_map[i]], ECS_INTERNAL_ERROR, NULL); } } ecs_assert(table->data.columns != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(table->type_info != NULL, ECS_INTERNAL_ERROR, NULL); for (i = 0; i < storage_count; i ++) { ecs_vec_t *column = &table->data.columns[i]; ecs_assert(size == column->size, ECS_INTERNAL_ERROR, NULL); ecs_assert(count == column->count, ECS_INTERNAL_ERROR, NULL); int32_t storage_map_id = storage_map[i + type_count]; ecs_assert(storage_map_id >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(ids[storage_map_id] == storage_ids[i], ECS_INTERNAL_ERROR, NULL); } } else { ecs_assert(table->storage_count == 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(table->storage_ids == NULL, ECS_INTERNAL_ERROR, NULL); } if (sw_count) { ecs_assert(table->data.sw_columns != NULL, ECS_INTERNAL_ERROR, NULL); for (i = 0; i < sw_count; i ++) { ecs_switch_t *sw = &table->data.sw_columns[i]; ecs_assert(ecs_vec_count(&sw->values) == count, ECS_INTERNAL_ERROR, NULL); ecs_assert(ECS_PAIR_FIRST(ids[i + sw_offset]) == EcsUnion, ECS_INTERNAL_ERROR, NULL); } } if (bs_count) { ecs_assert(table->data.bs_columns != NULL, ECS_INTERNAL_ERROR, NULL); for (i = 0; i < bs_count; i ++) { ecs_bitset_t *bs = &table->data.bs_columns[i]; ecs_assert(flecs_bitset_count(bs) == count, ECS_INTERNAL_ERROR, NULL); ecs_assert(ECS_HAS_ID_FLAG(ids[i + bs_offset], TOGGLE), ECS_INTERNAL_ERROR, NULL); } } ecs_assert((table->observed_count == 0) || (table->flags & EcsTableHasObserved), ECS_INTERNAL_ERROR, NULL); } #else #define flecs_table_check_sanity(table) #endif static void flecs_table_init_storage_map( ecs_world_t *world, ecs_table_t *table) { ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); if (!table->storage_table) { return; } ecs_type_t type = table->type; ecs_id_t *ids = type.array; int32_t t, ids_count = type.count; ecs_id_t *storage_ids = table->storage_ids; int32_t s, storage_ids_count = table->storage_count; if (!ids_count) { table->storage_map = NULL; return; } table->storage_map = flecs_walloc_n(world, int32_t, ids_count + storage_ids_count); int32_t *t2s = table->storage_map; int32_t *s2t = &table->storage_map[ids_count]; for (s = 0, t = 0; (t < ids_count) && (s < storage_ids_count); ) { ecs_id_t id = ids[t]; ecs_id_t storage_id = storage_ids[s]; if (id == storage_id) { t2s[t] = s; s2t[s] = t; } else { t2s[t] = -1; } /* Ids can never get ahead of storage id, as ids are a superset of the * storage ids */ ecs_assert(id <= storage_id, ECS_INTERNAL_ERROR, NULL); t += (id <= storage_id); s += (id == storage_id); } /* Storage ids is always a subset of ids, so all should be iterated */ ecs_assert(s == storage_ids_count, ECS_INTERNAL_ERROR, NULL); /* Initialize remainder of type -> storage_type map */ for (; (t < ids_count); t ++) { t2s[t] = -1; } } static ecs_flags32_t flecs_type_info_flags( const ecs_type_info_t *ti) { ecs_flags32_t flags = 0; if (ti->hooks.ctor) { flags |= EcsTableHasCtors; } if (ti->hooks.on_add) { flags |= EcsTableHasCtors; } if (ti->hooks.dtor) { flags |= EcsTableHasDtors; } if (ti->hooks.on_remove) { flags |= EcsTableHasDtors; } if (ti->hooks.copy) { flags |= EcsTableHasCopy; } if (ti->hooks.move) { flags |= EcsTableHasMove; } return flags; } static void flecs_table_init_type_info( ecs_world_t *world, ecs_table_t *table) { ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(table->storage_table == table, ECS_INTERNAL_ERROR, NULL); ecs_assert(table->type_info == NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_record_t *records = table->records; int32_t i, count = table->type.count; table->type_info = flecs_walloc_n(world, ecs_type_info_t*, count); for (i = 0; i < count; i ++) { ecs_table_record_t *tr = &records[i]; ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; /* All ids in the storage table must be components with type info */ const ecs_type_info_t *ti = idr->type_info; ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); table->flags |= flecs_type_info_flags(ti); table->type_info[i] = (ecs_type_info_t*)ti; } } static void flecs_table_init_storage_table( ecs_world_t *world, ecs_table_t *table) { if (table->storage_table) { return; } ecs_type_t type = table->type; int32_t i, count = type.count; ecs_id_t *ids = type.array; ecs_table_record_t *records = table->records; ecs_id_t array[ECS_ID_CACHE_SIZE]; ecs_type_t storage_ids = { .array = array }; if (count > ECS_ID_CACHE_SIZE) { storage_ids.array = flecs_walloc_n(world, ecs_id_t, count); } for (i = 0; i < count; i ++) { ecs_table_record_t *tr = &records[i]; ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; ecs_id_t id = ids[i]; if (idr->type_info != NULL) { storage_ids.array[storage_ids.count ++] = id; } } if (storage_ids.count && storage_ids.count != count) { ecs_table_t *storage_table = flecs_table_find_or_create(world, &storage_ids); table->storage_table = storage_table; table->storage_count = flecs_ito(uint16_t, storage_ids.count); table->storage_ids = storage_table->type.array; table->type_info = storage_table->type_info; table->flags |= storage_table->flags; storage_table->refcount ++; } else if (storage_ids.count) { table->storage_table = table; table->storage_count = flecs_ito(uint16_t, count); table->storage_ids = type.array; flecs_table_init_type_info(world, table); } if (storage_ids.array != array) { flecs_wfree_n(world, ecs_id_t, count, storage_ids.array); } if (!table->storage_map) { flecs_table_init_storage_map(world, table); } } void flecs_table_init_data( ecs_world_t *world, ecs_table_t *table) { int32_t sw_count = table->sw_count; int32_t bs_count = table->bs_count; ecs_data_t *storage = &table->data; int32_t i, count = table->storage_count; /* Root tables don't have columns */ if (!count && !sw_count && !bs_count) { storage->columns = NULL; } ecs_vec_init_t(NULL, &storage->entities, ecs_entity_t, 0); ecs_vec_init_t(NULL, &storage->records, ecs_record_t*, 0); if (count) { ecs_vec_t *columns = flecs_wcalloc_n(world, ecs_vec_t, count); storage->columns = columns; #ifdef FLECS_DEBUG ecs_type_info_t **ti = table->type_info; for (i = 0; i < count; i ++) { ecs_vec_init(NULL, &columns[i], ti[i]->size, 0); } #endif } if (sw_count) { storage->sw_columns = flecs_wcalloc_n(world, ecs_switch_t, sw_count); for (i = 0; i < sw_count; i ++) { flecs_switch_init(&storage->sw_columns[i], &world->allocator, 0); } } if (bs_count) { storage->bs_columns = flecs_wcalloc_n(world, ecs_bitset_t, bs_count); for (i = 0; i < bs_count; i ++) { flecs_bitset_init(&storage->bs_columns[i]); } } } static void flecs_table_init_flags( ecs_world_t *world, ecs_table_t *table) { ecs_id_t *ids = table->type.array; int32_t count = table->type.count; /* Iterate components to initialize table flags */ int32_t i; for (i = 0; i < count; i ++) { ecs_id_t id = ids[i]; /* As we're iterating over the table components, also set the table * flags. These allow us to quickly determine if the table contains * data that needs to be handled in a special way. */ if (id <= EcsLastInternalComponentId) { table->flags |= EcsTableHasBuiltins; } if (id == EcsModule) { table->flags |= EcsTableHasBuiltins; table->flags |= EcsTableHasModule; } else if (id == EcsPrefab) { table->flags |= EcsTableIsPrefab; } else if (id == EcsDisabled) { table->flags |= EcsTableIsDisabled; } else { if (ECS_IS_PAIR(id)) { ecs_entity_t r = ECS_PAIR_FIRST(id); table->flags |= EcsTableHasPairs; if (r == EcsIsA) { table->flags |= EcsTableHasIsA; } else if (r == EcsChildOf) { table->flags |= EcsTableHasChildOf; ecs_entity_t obj = ecs_pair_second(world, id); ecs_assert(obj != 0, ECS_INTERNAL_ERROR, NULL); if (obj == EcsFlecs || obj == EcsFlecsCore || ecs_has_id(world, obj, EcsModule)) { /* If table contains entities that are inside one of the * builtin modules, it contains builtin entities */ table->flags |= EcsTableHasBuiltins; table->flags |= EcsTableHasModule; } } else if (r == EcsUnion) { table->flags |= EcsTableHasUnion; if (!table->sw_count) { table->sw_offset = flecs_ito(int16_t, i); } table->sw_count ++; } else if (r == ecs_id(EcsPoly)) { table->flags |= EcsTableHasBuiltins; } } else { if (ECS_HAS_ID_FLAG(id, TOGGLE)) { table->flags |= EcsTableHasToggle; if (!table->bs_count) { table->bs_offset = flecs_ito(int16_t, i); } table->bs_count ++; } if (ECS_HAS_ID_FLAG(id, OVERRIDE)) { table->flags |= EcsTableHasOverrides; } } } } } static void flecs_table_append_to_records( ecs_world_t *world, ecs_table_t *table, ecs_vec_t *records, ecs_id_t id, int32_t column) { /* To avoid a quadratic search, use the O(1) lookup that the index * already provides. */ ecs_id_record_t *idr = flecs_id_record_ensure(world, id); ecs_table_record_t *tr = (ecs_table_record_t*)flecs_id_record_get_table( idr, table); if (!tr) { tr = ecs_vec_append_t(&world->allocator, records, ecs_table_record_t); tr->column = column; tr->count = 1; ecs_table_cache_insert(&idr->cache, table, &tr->hdr); } else { tr->count ++; } ecs_assert(tr->hdr.cache != NULL, ECS_INTERNAL_ERROR, NULL); } void flecs_table_init( ecs_world_t *world, ecs_table_t *table, ecs_table_t *from) { flecs_table_init_flags(world, table); int32_t dst_i = 0, dst_count = table->type.count; int32_t src_i = 0, src_count = 0; ecs_id_t *dst_ids = table->type.array; ecs_id_t *src_ids = NULL; ecs_table_record_t *tr = NULL, *src_tr = NULL; if (from) { src_count = from->type.count; src_ids = from->type.array; src_tr = from->records; } /* We don't know in advance how large the records array will be, so use * cached vector. This eliminates unnecessary allocations, and/or expensive * iterations to determine how many records we need. */ ecs_allocator_t *a = &world->allocator; ecs_vec_t *records = &world->store.records; ecs_vec_reset_t(a, records, ecs_table_record_t); ecs_id_record_t *idr; int32_t last_id = -1; /* Track last regular (non-pair) id */ int32_t first_pair = -1; /* Track the first pair in the table */ int32_t first_role = -1; /* Track first id with role */ /* Scan to find boundaries of regular ids, pairs and roles */ for (dst_i = 0; dst_i < dst_count; dst_i ++) { ecs_id_t dst_id = dst_ids[dst_i]; if (first_pair == -1 && ECS_IS_PAIR(dst_id)) { first_pair = dst_i; } if ((dst_id & ECS_COMPONENT_MASK) == dst_id) { last_id = dst_i; } else if (first_role == -1 && !ECS_IS_PAIR(dst_id)) { first_role = dst_i; } } /* The easy part: initialize a record for every id in the type */ for (dst_i = 0; (dst_i < dst_count) && (src_i < src_count); ) { ecs_id_t dst_id = dst_ids[dst_i]; ecs_id_t src_id = src_ids[src_i]; idr = NULL; if (dst_id == src_id) { idr = (ecs_id_record_t*)src_tr[src_i].hdr.cache; } else if (dst_id < src_id) { idr = flecs_id_record_ensure(world, dst_id); } if (idr) { tr = ecs_vec_append_t(a, records, ecs_table_record_t); tr->hdr.cache = (ecs_table_cache_t*)idr; tr->column = dst_i; tr->count = 1; } dst_i += dst_id <= src_id; src_i += dst_id >= src_id; } /* Add remaining ids that the "from" table didn't have */ for (; (dst_i < dst_count); dst_i ++) { ecs_id_t dst_id = dst_ids[dst_i]; tr = ecs_vec_append_t(a, records, ecs_table_record_t); idr = flecs_id_record_ensure(world, dst_id); tr->hdr.cache = (ecs_table_cache_t*)idr; ecs_assert(tr->hdr.cache != NULL, ECS_INTERNAL_ERROR, NULL); tr->column = dst_i; tr->count = 1; } /* We're going to insert records from the vector into the index that * will get patched up later. To ensure the record pointers don't get * invalidated we need to grow the vector so that it won't realloc as * we're adding the next set of records */ if (first_role != -1 || first_pair != -1) { int32_t start = first_role; if (first_pair != -1 && (start != -1 || first_pair < start)) { start = first_pair; } /* Total number of records can never be higher than * - number of regular (non-pair) ids + * - three records for pairs: (R,T), (R,*), (*,T) * - one wildcard (*), one any (_) and one pair wildcard (*,*) record * - one record for (ChildOf, 0) */ int32_t flag_id_count = dst_count - start; int32_t record_count = start + 3 * flag_id_count + 3 + 1; ecs_vec_set_min_size_t(a, records, ecs_table_record_t, record_count); } /* Add records for ids with roles (used by cleanup logic) */ if (first_role != -1) { for (dst_i = first_role; dst_i < dst_count; dst_i ++) { ecs_id_t id = dst_ids[dst_i]; if (!ECS_IS_PAIR(id)) { ecs_entity_t first = 0; ecs_entity_t second = 0; if (ECS_HAS_ID_FLAG(id, PAIR)) { first = ECS_PAIR_FIRST(id); second = ECS_PAIR_SECOND(id); } else { first = id & ECS_COMPONENT_MASK; } if (first) { flecs_table_append_to_records(world, table, records, ecs_pair(EcsFlag, first), dst_i); } if (second) { flecs_table_append_to_records(world, table, records, ecs_pair(EcsFlag, second), dst_i); } } } } int32_t last_pair = -1; bool has_childof = table->flags & EcsTableHasChildOf; if (first_pair != -1) { /* Add a (Relationship, *) record for each relationship. */ ecs_entity_t r = 0; for (dst_i = first_pair; dst_i < dst_count; dst_i ++) { ecs_id_t dst_id = dst_ids[dst_i]; if (!ECS_IS_PAIR(dst_id)) { break; /* no more pairs */ } if (r != ECS_PAIR_FIRST(dst_id)) { /* New relationship, new record */ tr = ecs_vec_get_t(records, ecs_table_record_t, dst_i); idr = ((ecs_id_record_t*)tr->hdr.cache)->parent; /* (R, *) */ ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); tr = ecs_vec_append_t(a, records, ecs_table_record_t); tr->hdr.cache = (ecs_table_cache_t*)idr; tr->column = dst_i; tr->count = 0; r = ECS_PAIR_FIRST(dst_id); } ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); tr->count ++; } last_pair = dst_i; /* Add a (*, Target) record for each relationship target. Type * ids are sorted relationship-first, so we can't simply do a single linear * scan to find all occurrences for a target. */ for (dst_i = first_pair; dst_i < last_pair; dst_i ++) { ecs_id_t dst_id = dst_ids[dst_i]; ecs_id_t tgt_id = ecs_pair(EcsWildcard, ECS_PAIR_SECOND(dst_id)); flecs_table_append_to_records( world, table, records, tgt_id, dst_i); } } /* Lastly, add records for all-wildcard ids */ if (last_id >= 0) { tr = ecs_vec_append_t(a, records, ecs_table_record_t); tr->hdr.cache = (ecs_table_cache_t*)world->idr_wildcard; tr->column = 0; tr->count = last_id + 1; } if (last_pair - first_pair) { tr = ecs_vec_append_t(a, records, ecs_table_record_t); tr->hdr.cache = (ecs_table_cache_t*)world->idr_wildcard_wildcard; tr->column = first_pair; tr->count = last_pair - first_pair; } if (dst_count) { tr = ecs_vec_append_t(a, records, ecs_table_record_t); tr->hdr.cache = (ecs_table_cache_t*)world->idr_any; tr->column = 0; tr->count = 1; } if (dst_count && !has_childof) { tr = ecs_vec_append_t(a, records, ecs_table_record_t); tr->hdr.cache = (ecs_table_cache_t*)world->idr_childof_0; tr->column = 0; tr->count = 1; } /* Now that all records have been added, copy them to array */ int32_t i, dst_record_count = ecs_vec_count(records); ecs_table_record_t *dst_tr = flecs_wdup_n(world, ecs_table_record_t, dst_record_count, ecs_vec_first_t(records, ecs_table_record_t)); table->record_count = flecs_ito(uint16_t, dst_record_count); table->records = dst_tr; /* Register & patch up records */ for (i = 0; i < dst_record_count; i ++) { tr = &dst_tr[i]; idr = (ecs_id_record_t*)dst_tr[i].hdr.cache; ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); if (ecs_table_cache_get(&idr->cache, table)) { /* If this is a target wildcard record it has already been * registered, but the record is now at a different location in * memory. Patch up the linked list with the new address */ ecs_table_cache_replace(&idr->cache, table, &tr->hdr); } else { /* Other records are not registered yet */ ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_cache_insert(&idr->cache, table, &tr->hdr); } /* Claim id record so it stays alive as long as the table exists */ flecs_id_record_claim(world, idr); /* Initialize event flags */ table->flags |= idr->flags & EcsIdEventMask; if (idr->flags & EcsIdAlwaysOverride) { table->flags |= EcsTableHasOverrides; } } flecs_table_init_storage_table(world, table); flecs_table_init_data(world, table); } static void flecs_table_records_unregister( ecs_world_t *world, ecs_table_t *table) { uint64_t table_id = table->id; int32_t i, count = table->record_count; for (i = 0; i < count; i ++) { ecs_table_record_t *tr = &table->records[i]; ecs_table_cache_t *cache = tr->hdr.cache; ecs_id_t id = ((ecs_id_record_t*)cache)->id; ecs_assert(tr->hdr.cache == cache, ECS_INTERNAL_ERROR, NULL); ecs_assert(tr->hdr.table == table, ECS_INTERNAL_ERROR, NULL); ecs_assert(flecs_id_record_get(world, id) == (ecs_id_record_t*)cache, ECS_INTERNAL_ERROR, NULL); (void)id; ecs_table_cache_remove(cache, table_id, &tr->hdr); flecs_id_record_release(world, (ecs_id_record_t*)cache); } flecs_wfree_n(world, ecs_table_record_t, count, table->records); } static void flecs_table_add_trigger_flags( ecs_world_t *world, ecs_table_t *table, ecs_entity_t event) { (void)world; if (event == EcsOnAdd) { table->flags |= EcsTableHasOnAdd; } else if (event == EcsOnRemove) { table->flags |= EcsTableHasOnRemove; } else if (event == EcsOnSet) { table->flags |= EcsTableHasOnSet; } else if (event == EcsUnSet) { table->flags |= EcsTableHasUnSet; } } static void flecs_table_notify_on_remove( ecs_world_t *world, ecs_table_t *table, ecs_data_t *data) { int32_t count = data->entities.count; if (count) { flecs_notify_on_remove(world, table, NULL, 0, count, &table->type); } } /* -- Private functions -- */ static void flecs_on_component_callback( ecs_world_t *world, ecs_table_t *table, ecs_iter_action_t callback, ecs_entity_t event, ecs_vec_t *column, ecs_entity_t *entities, ecs_id_t id, int32_t row, int32_t count, ecs_type_info_t *ti) { ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); void *ptr = ecs_vec_get(column, ti->size, row); flecs_invoke_hook( world, table, count, row, entities, ptr, id, ti, event, callback); } static void flecs_ctor_component( ecs_type_info_t *ti, ecs_vec_t *column, int32_t row, int32_t count) { ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); ecs_xtor_t ctor = ti->hooks.ctor; if (ctor) { void *ptr = ecs_vec_get(column, ti->size, row); ctor(ptr, count, ti); } } static void flecs_dtor_component( ecs_type_info_t *ti, ecs_vec_t *column, int32_t row, int32_t count) { ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); ecs_xtor_t dtor = ti->hooks.dtor; if (dtor) { void *ptr = ecs_vec_get(column, ti->size, row); dtor(ptr, count, ti); } } static void flecs_run_add_hooks( ecs_world_t *world, ecs_table_t *table, ecs_type_info_t *ti, ecs_vec_t *column, ecs_entity_t *entities, ecs_id_t id, int32_t row, int32_t count, bool construct) { ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); if (construct) { flecs_ctor_component(ti, column, row, count); } ecs_iter_action_t on_add = ti->hooks.on_add; if (on_add) { flecs_on_component_callback(world, table, on_add, EcsOnAdd, column, entities, id, row, count, ti); } } static void flecs_run_remove_hooks( ecs_world_t *world, ecs_table_t *table, ecs_type_info_t *ti, ecs_vec_t *column, ecs_entity_t *entities, ecs_id_t id, int32_t row, int32_t count, bool dtor) { ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); ecs_iter_action_t on_remove = ti->hooks.on_remove; if (on_remove) { flecs_on_component_callback(world, table, on_remove, EcsOnRemove, column, entities, id, row, count, ti); } if (dtor) { flecs_dtor_component(ti, column, row, count); } } static void flecs_dtor_all_components( ecs_world_t *world, ecs_table_t *table, ecs_data_t *data, int32_t row, int32_t count, bool update_entity_index, bool is_delete) { /* Can't delete and not update the entity index */ ecs_assert(!is_delete || update_entity_index, ECS_INTERNAL_ERROR, NULL); ecs_id_t *ids = table->storage_ids; int32_t ids_count = table->storage_count; ecs_record_t **records = data->records.array; ecs_entity_t *entities = data->entities.array; int32_t i, c, end = row + count; (void)records; if (is_delete && table->observed_count) { /* If table contains monitored entities with traversable relationships, * make sure to invalidate observer cache */ flecs_emit_propagate_invalidate(world, table, row, count); } /* If table has components with destructors, iterate component columns */ if (table->flags & EcsTableHasDtors) { /* Throw up a lock just to be sure */ table->lock = true; /* Run on_remove callbacks first before destructing components */ for (c = 0; c < ids_count; c++) { ecs_vec_t *column = &data->columns[c]; ecs_type_info_t *ti = table->type_info[c]; ecs_iter_action_t on_remove = ti->hooks.on_remove; if (on_remove) { flecs_on_component_callback(world, table, on_remove, EcsOnRemove, column, &entities[row], ids[c], row, count, ti); } } /* Destruct components */ for (c = 0; c < ids_count; c++) { flecs_dtor_component(table->type_info[c], &data->columns[c], row, count); } /* Iterate entities first, then components. This ensures that only one * entity is invalidated at a time, which ensures that destructors can * safely access other entities. */ for (i = row; i < end; i ++) { /* Update entity index after invoking destructors so that entity can * be safely used in destructor callbacks. */ if (update_entity_index) { ecs_entity_t e = entities[i]; ecs_assert(!e || ecs_is_valid(world, e), ECS_INTERNAL_ERROR, NULL); ecs_assert(!e || records[i] == flecs_entities_get(world, e), ECS_INTERNAL_ERROR, NULL); ecs_assert(!e || records[i]->table == table, ECS_INTERNAL_ERROR, NULL); if (is_delete) { flecs_entities_remove(world, e); ecs_assert(ecs_is_valid(world, e) == false, ECS_INTERNAL_ERROR, NULL); } else { // If this is not a delete, clear the entity index record records[i]->table = NULL; records[i]->row = 0; } } else { /* This should only happen in rare cases, such as when the data * cleaned up is not part of the world (like with snapshots) */ } } table->lock = false; /* If table does not have destructors, just update entity index */ } else if (update_entity_index) { if (is_delete) { for (i = row; i < end; i ++) { ecs_entity_t e = entities[i]; ecs_assert(!e || ecs_is_valid(world, e), ECS_INTERNAL_ERROR, NULL); ecs_assert(!e || records[i] == flecs_entities_get(world, e), ECS_INTERNAL_ERROR, NULL); ecs_assert(!e || records[i]->table == table, ECS_INTERNAL_ERROR, NULL); flecs_entities_remove(world, e); ecs_assert(!ecs_is_valid(world, e), ECS_INTERNAL_ERROR, NULL); } } else { for (i = row; i < end; i ++) { ecs_entity_t e = entities[i]; ecs_assert(!e || ecs_is_valid(world, e), ECS_INTERNAL_ERROR, NULL); ecs_assert(!e || records[i] == flecs_entities_get(world, e), ECS_INTERNAL_ERROR, NULL); ecs_assert(!e || records[i]->table == table, ECS_INTERNAL_ERROR, NULL); records[i]->table = NULL; records[i]->row = records[i]->row & ECS_ROW_FLAGS_MASK; (void)e; } } } } static void flecs_table_fini_data( ecs_world_t *world, ecs_table_t *table, ecs_data_t *data, bool do_on_remove, bool update_entity_index, bool is_delete, bool deactivate) { ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); if (!data) { return; } if (do_on_remove) { flecs_table_notify_on_remove(world, table, data); } int32_t count = flecs_table_data_count(data); if (count) { flecs_dtor_all_components(world, table, data, 0, count, update_entity_index, is_delete); } /* Sanity check */ ecs_assert(data->records.count == data->entities.count, ECS_INTERNAL_ERROR, NULL); ecs_vec_t *columns = data->columns; if (columns) { int32_t c, column_count = table->storage_count; for (c = 0; c < column_count; c ++) { /* Sanity check */ ecs_assert(columns[c].count == data->entities.count, ECS_INTERNAL_ERROR, NULL); ecs_vec_fini(&world->allocator, &columns[c], table->type_info[c]->size); } flecs_wfree_n(world, ecs_vec_t, column_count, columns); data->columns = NULL; } ecs_switch_t *sw_columns = data->sw_columns; if (sw_columns) { int32_t c, column_count = table->sw_count; for (c = 0; c < column_count; c ++) { flecs_switch_fini(&sw_columns[c]); } flecs_wfree_n(world, ecs_switch_t, column_count, sw_columns); data->sw_columns = NULL; } ecs_bitset_t *bs_columns = data->bs_columns; if (bs_columns) { int32_t c, column_count = table->bs_count; for (c = 0; c < column_count; c ++) { flecs_bitset_fini(&bs_columns[c]); } flecs_wfree_n(world, ecs_bitset_t, column_count, bs_columns); data->bs_columns = NULL; } ecs_vec_fini_t(&world->allocator, &data->entities, ecs_entity_t); ecs_vec_fini_t(&world->allocator, &data->records, ecs_record_t*); if (deactivate && count) { flecs_table_set_empty(world, table); } table->observed_count = 0; table->flags &= ~EcsTableHasObserved; } /* Cleanup, no OnRemove, don't update entity index, don't deactivate table */ void flecs_table_clear_data( ecs_world_t *world, ecs_table_t *table, ecs_data_t *data) { flecs_table_fini_data(world, table, data, false, false, false, false); } /* Cleanup, no OnRemove, clear entity index, deactivate table */ void flecs_table_clear_entities_silent( ecs_world_t *world, ecs_table_t *table) { flecs_table_fini_data(world, table, &table->data, false, true, false, true); } /* Cleanup, run OnRemove, clear entity index, deactivate table */ void flecs_table_clear_entities( ecs_world_t *world, ecs_table_t *table) { flecs_table_fini_data(world, table, &table->data, true, true, false, true); } /* Cleanup, run OnRemove, delete from entity index, deactivate table */ void flecs_table_delete_entities( ecs_world_t *world, ecs_table_t *table) { flecs_table_fini_data(world, table, &table->data, true, true, true, true); } /* Unset all components in table. This function is called before a table is * deleted, and invokes all UnSet handlers, if any */ void flecs_table_remove_actions( ecs_world_t *world, ecs_table_t *table) { (void)world; flecs_table_notify_on_remove(world, table, &table->data); } /* Free table resources. */ void flecs_table_free( ecs_world_t *world, ecs_table_t *table) { bool is_root = table == &world->store.root; ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); ecs_assert(is_root || table->id != 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(is_root || flecs_sparse_is_alive(&world->store.tables, table->id), ECS_INTERNAL_ERROR, NULL); (void)world; ecs_assert(table->refcount == 0, ECS_INTERNAL_ERROR, NULL); if (!is_root && !(world->flags & EcsWorldQuit)) { flecs_emit(world, world, &(ecs_event_desc_t) { .ids = &table->type, .event = EcsOnTableDelete, .table = table, .flags = EcsEventTableOnly, .observable = world }); } if (ecs_should_log_2()) { char *expr = ecs_type_str(world, &table->type); ecs_dbg_2( "#[green]table#[normal] [%s] #[red]deleted#[reset] with id %d", expr, table->id); ecs_os_free(expr); ecs_log_push_2(); } world->info.empty_table_count -= (ecs_table_count(table) == 0); /* Cleanup data, no OnRemove, delete from entity index, don't deactivate */ flecs_table_fini_data(world, table, &table->data, false, true, true, false); flecs_table_clear_edges(world, table); if (!is_root) { ecs_type_t ids = { .array = table->type.array, .count = table->type.count }; flecs_hashmap_remove(&world->store.table_map, &ids, ecs_table_t*); } flecs_wfree_n(world, int32_t, table->storage_count + 1, table->dirty_state); flecs_wfree_n(world, int32_t, table->storage_count + table->type.count, table->storage_map); flecs_table_records_unregister(world, table); ecs_table_t *storage_table = table->storage_table; if (storage_table == table) { if (table->type_info) { flecs_wfree_n(world, ecs_type_info_t*, table->storage_count, table->type_info); } } else if (storage_table) { flecs_table_release(world, storage_table); } /* Update counters */ world->info.table_count --; world->info.table_record_count -= table->record_count; world->info.table_storage_count -= table->storage_count; world->info.table_delete_total ++; if (!table->storage_count) { world->info.tag_table_count --; } else { world->info.trivial_table_count -= !(table->flags & EcsTableIsComplex); } if (!(world->flags & EcsWorldFini)) { ecs_assert(!is_root, ECS_INTERNAL_ERROR, NULL); flecs_table_free_type(world, table); flecs_sparse_remove_t(&world->store.tables, ecs_table_t, table->id); } ecs_log_pop_2(); } void flecs_table_claim( ecs_world_t *world, ecs_table_t *table) { ecs_poly_assert(world, ecs_world_t); ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(table->refcount > 0, ECS_INTERNAL_ERROR, NULL); table->refcount ++; (void)world; } bool flecs_table_release( ecs_world_t *world, ecs_table_t *table) { ecs_poly_assert(world, ecs_world_t); ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(table->refcount > 0, ECS_INTERNAL_ERROR, NULL); if (--table->refcount == 0) { flecs_table_free(world, table); return true; } return false; } /* Free table type. Do this separately from freeing the table as types can be * in use by application destructors. */ void flecs_table_free_type( ecs_world_t *world, ecs_table_t *table) { flecs_wfree_n(world, ecs_id_t, table->type.count, table->type.array); } /* Reset a table to its initial state. */ void flecs_table_reset( ecs_world_t *world, ecs_table_t *table) { ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); flecs_table_clear_edges(world, table); } void flecs_table_observer_add( ecs_table_t *table, int32_t value) { int32_t result = table->observed_count += value; ecs_assert(result >= 0, ECS_INTERNAL_ERROR, NULL); if (result == 0) { table->flags &= ~EcsTableHasObserved; } else if (result == value) { table->flags |= EcsTableHasObserved; } } static void flecs_table_mark_table_dirty( ecs_world_t *world, ecs_table_t *table, int32_t index) { (void)world; if (table->dirty_state) { table->dirty_state[index] ++; } } void flecs_table_mark_dirty( ecs_world_t *world, ecs_table_t *table, ecs_entity_t component) { ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); if (table->dirty_state) { int32_t index = ecs_search(world, table->storage_table, component, 0); ecs_assert(index != -1, ECS_INTERNAL_ERROR, NULL); table->dirty_state[index + 1] ++; } } static void flecs_table_move_switch_columns( ecs_table_t *dst_table, int32_t dst_index, ecs_table_t *src_table, int32_t src_index, int32_t count, bool clear) { int32_t i_old = 0, src_column_count = src_table->sw_count; int32_t i_new = 0, dst_column_count = dst_table->sw_count; if (!src_column_count && !dst_column_count) { return; } ecs_switch_t *src_columns = src_table->data.sw_columns; ecs_switch_t *dst_columns = dst_table->data.sw_columns; ecs_type_t dst_type = dst_table->type; ecs_type_t src_type = src_table->type; int32_t offset_new = dst_table->sw_offset; int32_t offset_old = src_table->sw_offset; ecs_id_t *dst_ids = dst_type.array; ecs_id_t *src_ids = src_type.array; for (; (i_new < dst_column_count) && (i_old < src_column_count);) { ecs_entity_t dst_id = dst_ids[i_new + offset_new]; ecs_entity_t src_id = src_ids[i_old + offset_old]; if (dst_id == src_id) { ecs_switch_t *src_switch = &src_columns[i_old]; ecs_switch_t *dst_switch = &dst_columns[i_new]; flecs_switch_ensure(dst_switch, dst_index + count); int i; for (i = 0; i < count; i ++) { uint64_t value = flecs_switch_get(src_switch, src_index + i); flecs_switch_set(dst_switch, dst_index + i, value); } if (clear) { ecs_assert(count == flecs_switch_count(src_switch), ECS_INTERNAL_ERROR, NULL); flecs_switch_clear(src_switch); } } else if (dst_id > src_id) { ecs_switch_t *src_switch = &src_columns[i_old]; flecs_switch_clear(src_switch); } i_new += dst_id <= src_id; i_old += dst_id >= src_id; } /* Clear remaining columns */ if (clear) { for (; (i_old < src_column_count); i_old ++) { ecs_switch_t *src_switch = &src_columns[i_old]; ecs_assert(count == flecs_switch_count(src_switch), ECS_INTERNAL_ERROR, NULL); flecs_switch_clear(src_switch); } } } static void flecs_table_move_bitset_columns( ecs_table_t *dst_table, int32_t dst_index, ecs_table_t *src_table, int32_t src_index, int32_t count, bool clear) { int32_t i_old = 0, src_column_count = src_table->bs_count; int32_t i_new = 0, dst_column_count = dst_table->bs_count; if (!src_column_count && !dst_column_count) { return; } ecs_bitset_t *src_columns = src_table->data.bs_columns; ecs_bitset_t *dst_columns = dst_table->data.bs_columns; ecs_type_t dst_type = dst_table->type; ecs_type_t src_type = src_table->type; int32_t offset_new = dst_table->bs_offset; int32_t offset_old = src_table->bs_offset; ecs_id_t *dst_ids = dst_type.array; ecs_id_t *src_ids = src_type.array; for (; (i_new < dst_column_count) && (i_old < src_column_count);) { ecs_id_t dst_id = dst_ids[i_new + offset_new]; ecs_id_t src_id = src_ids[i_old + offset_old]; if (dst_id == src_id) { ecs_bitset_t *src_bs = &src_columns[i_old]; ecs_bitset_t *dst_bs = &dst_columns[i_new]; flecs_bitset_ensure(dst_bs, dst_index + count); int i; for (i = 0; i < count; i ++) { uint64_t value = flecs_bitset_get(src_bs, src_index + i); flecs_bitset_set(dst_bs, dst_index + i, value); } if (clear) { ecs_assert(count == flecs_bitset_count(src_bs), ECS_INTERNAL_ERROR, NULL); flecs_bitset_fini(src_bs); } } else if (dst_id > src_id) { ecs_bitset_t *src_bs = &src_columns[i_old]; flecs_bitset_fini(src_bs); } i_new += dst_id <= src_id; i_old += dst_id >= src_id; } /* Clear remaining columns */ if (clear) { for (; (i_old < src_column_count); i_old ++) { ecs_bitset_t *src_bs = &src_columns[i_old]; ecs_assert(count == flecs_bitset_count(src_bs), ECS_INTERNAL_ERROR, NULL); flecs_bitset_fini(src_bs); } } } static void* flecs_table_grow_column( ecs_world_t *world, ecs_vec_t *column, ecs_type_info_t *ti, int32_t to_add, int32_t dst_size, bool construct) { ecs_assert(column != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); int32_t size = ti->size; int32_t count = column->count; int32_t src_size = column->size; int32_t dst_count = count + to_add; bool can_realloc = dst_size != src_size; void *result = NULL; ecs_assert(dst_size >= dst_count, ECS_INTERNAL_ERROR, NULL); /* If the array could possibly realloc and the component has a move action * defined, move old elements manually */ ecs_move_t move_ctor; if (count && can_realloc && (move_ctor = ti->hooks.ctor_move_dtor)) { ecs_xtor_t ctor = ti->hooks.ctor; ecs_assert(ctor != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(move_ctor != NULL, ECS_INTERNAL_ERROR, NULL); /* Create vector */ ecs_vec_t dst; ecs_vec_init(&world->allocator, &dst, size, dst_size); dst.count = dst_count; void *src_buffer = column->array; void *dst_buffer = dst.array; /* Move (and construct) existing elements to new vector */ move_ctor(dst_buffer, src_buffer, count, ti); if (construct) { /* Construct new element(s) */ result = ECS_ELEM(dst_buffer, size, count); ctor(result, to_add, ti); } /* Free old vector */ ecs_vec_fini(&world->allocator, column, ti->size); *column = dst; } else { /* If array won't realloc or has no move, simply add new elements */ if (can_realloc) { ecs_vec_set_size(&world->allocator, column, size, dst_size); } result = ecs_vec_grow(&world->allocator, column, size, to_add); ecs_xtor_t ctor; if (construct && (ctor = ti->hooks.ctor)) { /* If new elements need to be constructed and component has a * constructor, construct */ ctor(result, to_add, ti); } } ecs_assert(column->size == dst_size, ECS_INTERNAL_ERROR, NULL); return result; } static int32_t flecs_table_grow_data( ecs_world_t *world, ecs_table_t *table, ecs_data_t *data, int32_t to_add, int32_t size, const ecs_entity_t *ids) { ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(data != NULL, ECS_INTERNAL_ERROR, NULL); int32_t cur_count = flecs_table_data_count(data); int32_t column_count = table->storage_count; int32_t sw_count = table->sw_count; int32_t bs_count = table->bs_count; ecs_vec_t *columns = data->columns; ecs_switch_t *sw_columns = data->sw_columns; ecs_bitset_t *bs_columns = data->bs_columns; /* Add record to record ptr array */ ecs_vec_set_size_t(&world->allocator, &data->records, ecs_record_t*, size); ecs_record_t **r = ecs_vec_last_t(&data->records, ecs_record_t*) + 1; data->records.count += to_add; if (data->records.size > size) { size = data->records.size; } /* Add entity to column with entity ids */ ecs_vec_set_size_t(&world->allocator, &data->entities, ecs_entity_t, size); ecs_entity_t *e = ecs_vec_last_t(&data->entities, ecs_entity_t) + 1; data->entities.count += to_add; ecs_assert(data->entities.size == size, ECS_INTERNAL_ERROR, NULL); /* Initialize entity ids and record ptrs */ int32_t i; if (ids) { ecs_os_memcpy_n(e, ids, ecs_entity_t, to_add); } else { ecs_os_memset(e, 0, ECS_SIZEOF(ecs_entity_t) * to_add); } ecs_os_memset(r, 0, ECS_SIZEOF(ecs_record_t*) * to_add); /* Add elements to each column array */ ecs_type_info_t **type_info = table->type_info; for (i = 0; i < column_count; i ++) { ecs_vec_t *column = &columns[i]; ecs_type_info_t *ti = type_info[i]; flecs_table_grow_column(world, column, ti, to_add, size, true); ecs_assert(columns[i].size == size, ECS_INTERNAL_ERROR, NULL); flecs_run_add_hooks(world, table, ti, column, e, table->type.array[i], cur_count, to_add, false); } /* Add elements to each switch column */ for (i = 0; i < sw_count; i ++) { ecs_switch_t *sw = &sw_columns[i]; flecs_switch_addn(sw, to_add); } /* Add elements to each bitset column */ for (i = 0; i < bs_count; i ++) { ecs_bitset_t *bs = &bs_columns[i]; flecs_bitset_addn(bs, to_add); } /* If the table is monitored indicate that there has been a change */ flecs_table_mark_table_dirty(world, table, 0); if (!(world->flags & EcsWorldReadonly) && !cur_count) { flecs_table_set_empty(world, table); } /* Return index of first added entity */ return cur_count; } static void flecs_table_fast_append( ecs_world_t *world, ecs_type_info_t **type_info, ecs_vec_t *columns, int32_t count) { /* Add elements to each column array */ int32_t i; for (i = 0; i < count; i ++) { ecs_type_info_t *ti = type_info[i]; ecs_vec_t *column = &columns[i]; ecs_vec_append(&world->allocator, column, ti->size); } } int32_t flecs_table_append( ecs_world_t *world, ecs_table_t *table, ecs_entity_t entity, ecs_record_t *record, bool construct, bool on_add) { ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); flecs_table_check_sanity(table); /* Get count & size before growing entities array. This tells us whether the * arrays will realloc */ ecs_data_t *data = &table->data; int32_t count = data->entities.count; int32_t column_count = table->storage_count; ecs_vec_t *columns = table->data.columns; /* Grow buffer with entity ids, set new element to new entity */ ecs_entity_t *e = ecs_vec_append_t(&world->allocator, &data->entities, ecs_entity_t); ecs_assert(e != NULL, ECS_INTERNAL_ERROR, NULL); *e = entity; /* Add record ptr to array with record ptrs */ ecs_record_t **r = ecs_vec_append_t(&world->allocator, &data->records, ecs_record_t*); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); *r = record; /* If the table is monitored indicate that there has been a change */ flecs_table_mark_table_dirty(world, table, 0); ecs_assert(count >= 0, ECS_INTERNAL_ERROR, NULL); ecs_type_info_t **type_info = table->type_info; /* Fast path: no switch columns, no lifecycle actions */ if (!(table->flags & EcsTableIsComplex)) { flecs_table_fast_append(world, type_info, columns, column_count); if (!count) { flecs_table_set_empty(world, table); /* See below */ } return count; } int32_t sw_count = table->sw_count; int32_t bs_count = table->bs_count; ecs_switch_t *sw_columns = data->sw_columns; ecs_bitset_t *bs_columns = data->bs_columns; ecs_entity_t *entities = data->entities.array; /* Reobtain size to ensure that the columns have the same size as the * entities and record vectors. This keeps reasoning about when allocations * occur easier. */ int32_t size = data->entities.size; /* Grow component arrays with 1 element */ int32_t i; for (i = 0; i < column_count; i ++) { ecs_vec_t *column = &columns[i]; ecs_type_info_t *ti = type_info[i]; flecs_table_grow_column(world, column, ti, 1, size, construct); ecs_iter_action_t on_add_hook; if (on_add && (on_add_hook = ti->hooks.on_add)) { flecs_on_component_callback(world, table, on_add_hook, EcsOnAdd, column, &entities[count], table->storage_ids[i], count, 1, ti); } ecs_assert(columns[i].size == data->entities.size, ECS_INTERNAL_ERROR, NULL); ecs_assert(columns[i].count == data->entities.count, ECS_INTERNAL_ERROR, NULL); } /* Add element to each switch column */ for (i = 0; i < sw_count; i ++) { ecs_assert(sw_columns != NULL, ECS_INTERNAL_ERROR, NULL); ecs_switch_t *sw = &sw_columns[i]; flecs_switch_add(sw); } /* Add element to each bitset column */ for (i = 0; i < bs_count; i ++) { ecs_assert(bs_columns != NULL, ECS_INTERNAL_ERROR, NULL); ecs_bitset_t *bs = &bs_columns[i]; flecs_bitset_addn(bs, 1); } /* If this is the first entity in this table, signal queries so that the * table moves from an inactive table to an active table. */ if (!count) { flecs_table_set_empty(world, table); } flecs_table_check_sanity(table); return count; } static void flecs_table_fast_delete_last( ecs_vec_t *columns, int32_t column_count) { int i; for (i = 0; i < column_count; i ++) { ecs_vec_remove_last(&columns[i]); } } static void flecs_table_fast_delete( ecs_type_info_t **type_info, ecs_vec_t *columns, int32_t column_count, int32_t index) { int i; for (i = 0; i < column_count; i ++) { ecs_type_info_t *ti = type_info[i]; ecs_vec_t *column = &columns[i]; ecs_vec_remove(column, ti->size, index); } } void flecs_table_delete( ecs_world_t *world, ecs_table_t *table, int32_t index, bool destruct) { ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); flecs_table_check_sanity(table); ecs_data_t *data = &table->data; int32_t count = data->entities.count; ecs_assert(count > 0, ECS_INTERNAL_ERROR, NULL); count --; ecs_assert(index <= count, ECS_INTERNAL_ERROR, NULL); /* Move last entity id to index */ ecs_entity_t *entities = data->entities.array; ecs_entity_t entity_to_move = entities[count]; ecs_entity_t entity_to_delete = entities[index]; entities[index] = entity_to_move; ecs_vec_remove_last(&data->entities); /* Move last record ptr to index */ ecs_assert(count < data->records.count, ECS_INTERNAL_ERROR, NULL); ecs_record_t **records = data->records.array; ecs_record_t *record_to_move = records[count]; records[index] = record_to_move; ecs_vec_remove_last(&data->records); /* Update record of moved entity in entity index */ if (index != count) { if (record_to_move) { uint32_t row_flags = record_to_move->row & ECS_ROW_FLAGS_MASK; record_to_move->row = ECS_ROW_TO_RECORD(index, row_flags); ecs_assert(record_to_move->table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(record_to_move->table == table, ECS_INTERNAL_ERROR, NULL); } } /* If the table is monitored indicate that there has been a change */ flecs_table_mark_table_dirty(world, table, 0); /* If table is empty, deactivate it */ if (!count) { flecs_table_set_empty(world, table); } /* Destruct component data */ ecs_type_info_t **type_info = table->type_info; ecs_vec_t *columns = data->columns; int32_t column_count = table->storage_count; int32_t i; /* If this is a table without lifecycle callbacks or special columns, take * fast path that just remove an element from the array(s) */ if (!(table->flags & EcsTableIsComplex)) { if (index == count) { flecs_table_fast_delete_last(columns, column_count); } else { flecs_table_fast_delete(type_info, columns, column_count, index); } flecs_table_check_sanity(table); return; } ecs_id_t *ids = table->storage_ids; /* Last element, destruct & remove */ if (index == count) { /* If table has component destructors, invoke */ if (destruct && (table->flags & EcsTableHasDtors)) { for (i = 0; i < column_count; i ++) { flecs_run_remove_hooks(world, table, type_info[i], &columns[i], &entity_to_delete, ids[i], index, 1, true); } } flecs_table_fast_delete_last(columns, column_count); /* Not last element, move last element to deleted element & destruct */ } else { /* If table has component destructors, invoke */ if ((table->flags & (EcsTableHasDtors | EcsTableHasMove))) { for (i = 0; i < column_count; i ++) { ecs_vec_t *column = &columns[i]; ecs_type_info_t *ti = type_info[i]; ecs_size_t size = ti->size; void *dst = ecs_vec_get(column, size, index); void *src = ecs_vec_last(column, size); ecs_iter_action_t on_remove = ti->hooks.on_remove; if (destruct && on_remove) { flecs_on_component_callback(world, table, on_remove, EcsOnRemove, column, &entity_to_delete, ids[i], index, 1, ti); } ecs_move_t move_dtor = ti->hooks.move_dtor; if (move_dtor) { move_dtor(dst, src, 1, ti); } else { ecs_os_memcpy(dst, src, size); } ecs_vec_remove_last(column); } } else { flecs_table_fast_delete(type_info, columns, column_count, index); } } /* Remove elements from switch columns */ ecs_switch_t *sw_columns = data->sw_columns; int32_t sw_count = table->sw_count; for (i = 0; i < sw_count; i ++) { flecs_switch_remove(&sw_columns[i], index); } /* Remove elements from bitset columns */ ecs_bitset_t *bs_columns = data->bs_columns; int32_t bs_count = table->bs_count; for (i = 0; i < bs_count; i ++) { flecs_bitset_remove(&bs_columns[i], index); } flecs_table_check_sanity(table); } static void flecs_table_fast_move( ecs_table_t *dst_table, int32_t dst_index, ecs_table_t *src_table, int32_t src_index) { int32_t i_new = 0, dst_column_count = dst_table->storage_count; int32_t i_old = 0, src_column_count = src_table->storage_count; ecs_id_t *dst_ids = dst_table->storage_ids; ecs_id_t *src_ids = src_table->storage_ids; ecs_vec_t *src_columns = src_table->data.columns; ecs_vec_t *dst_columns = dst_table->data.columns; ecs_type_info_t **dst_type_info = dst_table->type_info; for (; (i_new < dst_column_count) && (i_old < src_column_count);) { ecs_id_t dst_id = dst_ids[i_new]; ecs_id_t src_id = src_ids[i_old]; if (dst_id == src_id) { ecs_vec_t *dst_column = &dst_columns[i_new]; ecs_vec_t *src_column = &src_columns[i_old]; ecs_type_info_t *ti = dst_type_info[i_new]; int32_t size = ti->size; void *dst = ecs_vec_get(dst_column, size, dst_index); void *src = ecs_vec_get(src_column, size, src_index); ecs_os_memcpy(dst, src, size); } i_new += dst_id <= src_id; i_old += dst_id >= src_id; } } void flecs_table_move( ecs_world_t *world, ecs_entity_t dst_entity, ecs_entity_t src_entity, ecs_table_t *dst_table, int32_t dst_index, ecs_table_t *src_table, int32_t src_index, bool construct) { ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(src_table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(!dst_table->lock, ECS_LOCKED_STORAGE, NULL); ecs_assert(!src_table->lock, ECS_LOCKED_STORAGE, NULL); ecs_assert(src_index >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(dst_index >= 0, ECS_INTERNAL_ERROR, NULL); flecs_table_check_sanity(dst_table); flecs_table_check_sanity(src_table); if (!((dst_table->flags | src_table->flags) & EcsTableIsComplex)) { flecs_table_fast_move(dst_table, dst_index, src_table, src_index); flecs_table_check_sanity(dst_table); flecs_table_check_sanity(src_table); return; } flecs_table_move_switch_columns(dst_table, dst_index, src_table, src_index, 1, false); flecs_table_move_bitset_columns(dst_table, dst_index, src_table, src_index, 1, false); /* If the source and destination entities are the same, move component * between tables. If the entities are not the same (like when cloning) use * a copy. */ bool same_entity = dst_entity == src_entity; /* Call move_dtor for moved away from storage only if the entity is at the * last index in the source table. If it isn't the last entity, the last * entity in the table will be moved to the src storage, which will take * care of cleaning up resources. */ bool use_move_dtor = ecs_table_count(src_table) == (src_index + 1); ecs_type_info_t **dst_type_info = dst_table->type_info; ecs_type_info_t **src_type_info = src_table->type_info; int32_t i_new = 0, dst_column_count = dst_table->storage_count; int32_t i_old = 0, src_column_count = src_table->storage_count; ecs_id_t *dst_ids = dst_table->storage_ids; ecs_id_t *src_ids = src_table->storage_ids; ecs_vec_t *src_columns = src_table->data.columns; ecs_vec_t *dst_columns = dst_table->data.columns; for (; (i_new < dst_column_count) && (i_old < src_column_count); ) { ecs_id_t dst_id = dst_ids[i_new]; ecs_id_t src_id = src_ids[i_old]; if (dst_id == src_id) { ecs_vec_t *dst_column = &dst_columns[i_new]; ecs_vec_t *src_column = &src_columns[i_old]; ecs_type_info_t *ti = dst_type_info[i_new]; int32_t size = ti->size; ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); void *dst = ecs_vec_get(dst_column, size, dst_index); void *src = ecs_vec_get(src_column, size, src_index); if (same_entity) { ecs_move_t move = ti->hooks.move_ctor; if (use_move_dtor || !move) { /* Also use move_dtor if component doesn't have a move_ctor * registered, to ensure that the dtor gets called to * cleanup resources. */ move = ti->hooks.ctor_move_dtor; } if (move) { move(dst, src, 1, ti); } else { ecs_os_memcpy(dst, src, size); } } else { ecs_copy_t copy = ti->hooks.copy_ctor; if (copy) { copy(dst, src, 1, ti); } else { ecs_os_memcpy(dst, src, size); } } } else { if (dst_id < src_id) { flecs_run_add_hooks(world, dst_table, dst_type_info[i_new], &dst_columns[i_new], &dst_entity, dst_id, dst_index, 1, construct); } else { flecs_run_remove_hooks(world, src_table, src_type_info[i_old], &src_columns[i_old], &src_entity, src_id, src_index, 1, use_move_dtor); } } i_new += dst_id <= src_id; i_old += dst_id >= src_id; } for (; (i_new < dst_column_count); i_new ++) { flecs_run_add_hooks(world, dst_table, dst_type_info[i_new], &dst_columns[i_new], &dst_entity, dst_ids[i_new], dst_index, 1, construct); } for (; (i_old < src_column_count); i_old ++) { flecs_run_remove_hooks(world, src_table, src_type_info[i_old], &src_columns[i_old], &src_entity, src_ids[i_old], src_index, 1, use_move_dtor); } flecs_table_check_sanity(dst_table); flecs_table_check_sanity(src_table); } int32_t flecs_table_appendn( ecs_world_t *world, ecs_table_t *table, ecs_data_t *data, int32_t to_add, const ecs_entity_t *ids) { ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); flecs_table_check_sanity(table); int32_t cur_count = flecs_table_data_count(data); int32_t result = flecs_table_grow_data( world, table, data, to_add, cur_count + to_add, ids); flecs_table_check_sanity(table); return result; } void flecs_table_set_size( ecs_world_t *world, ecs_table_t *table, ecs_data_t *data, int32_t size) { ecs_assert(table != NULL, ECS_LOCKED_STORAGE, NULL); ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); flecs_table_check_sanity(table); int32_t cur_count = flecs_table_data_count(data); if (cur_count < size) { flecs_table_grow_data(world, table, data, 0, size, NULL); flecs_table_check_sanity(table); } } bool flecs_table_shrink( ecs_world_t *world, ecs_table_t *table) { ecs_assert(table != NULL, ECS_LOCKED_STORAGE, NULL); ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); (void)world; flecs_table_check_sanity(table); ecs_data_t *data = &table->data; bool has_payload = data->entities.array != NULL; ecs_vec_reclaim_t(&world->allocator, &data->entities, ecs_entity_t); ecs_vec_reclaim_t(&world->allocator, &data->records, ecs_record_t*); int32_t i, count = table->storage_count; ecs_type_info_t **type_info = table->type_info; for (i = 0; i < count; i ++) { ecs_vec_t *column = &data->columns[i]; ecs_type_info_t *ti = type_info[i]; ecs_vec_reclaim(&world->allocator, column, ti->size); } return has_payload; } int32_t flecs_table_data_count( const ecs_data_t *data) { return data ? data->entities.count : 0; } static void flecs_table_swap_switch_columns( ecs_table_t *table, ecs_data_t *data, int32_t row_1, int32_t row_2) { int32_t i = 0, column_count = table->sw_count; if (!column_count) { return; } ecs_switch_t *columns = data->sw_columns; for (i = 0; i < column_count; i ++) { ecs_switch_t *sw = &columns[i]; flecs_switch_swap(sw, row_1, row_2); } } static void flecs_table_swap_bitset_columns( ecs_table_t *table, ecs_data_t *data, int32_t row_1, int32_t row_2) { int32_t i = 0, column_count = table->bs_count; if (!column_count) { return; } ecs_bitset_t *columns = data->bs_columns; for (i = 0; i < column_count; i ++) { ecs_bitset_t *bs = &columns[i]; flecs_bitset_swap(bs, row_1, row_2); } } void flecs_table_swap( ecs_world_t *world, ecs_table_t *table, int32_t row_1, int32_t row_2) { (void)world; ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); ecs_assert(row_1 >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(row_2 >= 0, ECS_INTERNAL_ERROR, NULL); flecs_table_check_sanity(table); if (row_1 == row_2) { return; } /* If the table is monitored indicate that there has been a change */ flecs_table_mark_table_dirty(world, table, 0); ecs_entity_t *entities = table->data.entities.array; ecs_entity_t e1 = entities[row_1]; ecs_entity_t e2 = entities[row_2]; ecs_record_t **records = table->data.records.array; ecs_record_t *record_ptr_1 = records[row_1]; ecs_record_t *record_ptr_2 = records[row_2]; ecs_assert(record_ptr_1 != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(record_ptr_2 != NULL, ECS_INTERNAL_ERROR, NULL); /* Keep track of whether entity is watched */ uint32_t flags_1 = ECS_RECORD_TO_ROW_FLAGS(record_ptr_1->row); uint32_t flags_2 = ECS_RECORD_TO_ROW_FLAGS(record_ptr_2->row); /* Swap entities & records */ entities[row_1] = e2; entities[row_2] = e1; record_ptr_1->row = ECS_ROW_TO_RECORD(row_2, flags_1); record_ptr_2->row = ECS_ROW_TO_RECORD(row_1, flags_2); records[row_1] = record_ptr_2; records[row_2] = record_ptr_1; flecs_table_swap_switch_columns(table, &table->data, row_1, row_2); flecs_table_swap_bitset_columns(table, &table->data, row_1, row_2); ecs_vec_t *columns = table->data.columns; if (!columns) { flecs_table_check_sanity(table); return; } ecs_type_info_t **type_info = table->type_info; /* Find the maximum size of column elements * and allocate a temporary buffer for swapping */ int32_t i, temp_buffer_size = ECS_SIZEOF(uint64_t), column_count = table->storage_count; for (i = 0; i < column_count; i++) { ecs_type_info_t* ti = type_info[i]; temp_buffer_size = ECS_MAX(temp_buffer_size, ti->size); } void* tmp = ecs_os_alloca(temp_buffer_size); /* Swap columns */ for (i = 0; i < column_count; i ++) { ecs_type_info_t *ti = type_info[i]; int32_t size = ti->size; ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); void *ptr = columns[i].array; void *el_1 = ECS_ELEM(ptr, size, row_1); void *el_2 = ECS_ELEM(ptr, size, row_2); ecs_os_memcpy(tmp, el_1, size); ecs_os_memcpy(el_1, el_2, size); ecs_os_memcpy(el_2, tmp, size); } flecs_table_check_sanity(table); } static void flecs_merge_column( ecs_world_t *world, ecs_vec_t *dst, ecs_vec_t *src, int32_t size, int32_t column_size, ecs_type_info_t *ti) { int32_t dst_count = dst->count; if (!dst_count) { ecs_vec_fini(&world->allocator, dst, size); *dst = *src; src->array = NULL; src->count = 0; src->size = 0; /* If the new table is not empty, copy the contents from the * src into the dst. */ } else { int32_t src_count = src->count; if (ti) { flecs_table_grow_column(world, dst, ti, src_count, column_size, true); } else { if (column_size) { ecs_vec_set_size(&world->allocator, dst, size, column_size); } ecs_vec_set_count(&world->allocator, dst, size, dst_count + src_count); } void *dst_ptr = ECS_ELEM(dst->array, size, dst_count); void *src_ptr = src->array; /* Move values into column */ ecs_move_t move = NULL; if (ti) { move = ti->hooks.move_dtor; } if (move) { move(dst_ptr, src_ptr, src_count, ti); } else { ecs_os_memcpy(dst_ptr, src_ptr, size * src_count); } ecs_vec_fini(&world->allocator, src, size); } } static void flecs_merge_table_data( ecs_world_t *world, ecs_table_t *dst_table, ecs_table_t *src_table, int32_t src_count, int32_t dst_count, ecs_data_t *src_data, ecs_data_t *dst_data) { int32_t i_new = 0, dst_column_count = dst_table->storage_count; int32_t i_old = 0, src_column_count = src_table->storage_count; ecs_id_t *dst_ids = dst_table->storage_ids; ecs_id_t *src_ids = src_table->storage_ids; ecs_type_info_t **dst_type_info = dst_table->type_info; ecs_type_info_t **src_type_info = src_table->type_info; ecs_vec_t *src = src_data->columns; ecs_vec_t *dst = dst_data->columns; ecs_assert(!dst_column_count || dst, ECS_INTERNAL_ERROR, NULL); if (!src_count) { return; } /* Merge entities */ flecs_merge_column(world, &dst_data->entities, &src_data->entities, ECS_SIZEOF(ecs_entity_t), 0, NULL); ecs_assert(dst_data->entities.count == src_count + dst_count, ECS_INTERNAL_ERROR, NULL); int32_t column_size = dst_data->entities.size; /* Merge record pointers */ flecs_merge_column(world, &dst_data->records, &src_data->records, ECS_SIZEOF(ecs_record_t*), 0, NULL); for (; (i_new < dst_column_count) && (i_old < src_column_count); ) { ecs_id_t dst_id = dst_ids[i_new]; ecs_id_t src_id = src_ids[i_old]; ecs_type_info_t *dst_ti = dst_type_info[i_new]; int32_t size = dst_ti->size; ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); if (dst_id == src_id) { flecs_merge_column(world, &dst[i_new], &src[i_old], size, column_size, dst_ti); flecs_table_mark_table_dirty(world, dst_table, i_new + 1); ecs_assert(dst[i_new].size == dst_data->entities.size, ECS_INTERNAL_ERROR, NULL); i_new ++; i_old ++; } else if (dst_id < src_id) { /* New column, make sure vector is large enough. */ ecs_vec_t *column = &dst[i_new]; ecs_vec_set_count(&world->allocator, column, size, src_count + dst_count); flecs_ctor_component(dst_ti, column, 0, src_count + dst_count); i_new ++; } else if (dst_id > src_id) { /* Old column does not occur in new table, destruct */ ecs_vec_t *column = &src[i_old]; ecs_type_info_t *ti = src_type_info[i_old]; flecs_dtor_component(ti, column, 0, src_count); ecs_vec_fini(&world->allocator, column, ti->size); i_old ++; } } flecs_table_move_switch_columns(dst_table, dst_count, src_table, 0, src_count, true); flecs_table_move_bitset_columns(dst_table, dst_count, src_table, 0, src_count, true); /* Initialize remaining columns */ for (; i_new < dst_column_count; i_new ++) { ecs_vec_t *column = &dst[i_new]; ecs_type_info_t *ti = dst_type_info[i_new]; int32_t size = ti->size; ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); ecs_vec_set_count(&world->allocator, column, size, src_count + dst_count); flecs_ctor_component(ti, column, 0, src_count + dst_count); } /* Destruct remaining columns */ for (; i_old < src_column_count; i_old ++) { ecs_vec_t *column = &src[i_old]; ecs_type_info_t *ti = src_type_info[i_old]; flecs_dtor_component(ti, column, 0, src_count); ecs_vec_fini(&world->allocator, column, ti->size); } /* Mark entity column as dirty */ flecs_table_mark_table_dirty(world, dst_table, 0); } int32_t ecs_table_count( const ecs_table_t *table) { ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); return flecs_table_data_count(&table->data); } void flecs_table_merge( ecs_world_t *world, ecs_table_t *dst_table, ecs_table_t *src_table, ecs_data_t *dst_data, ecs_data_t *src_data) { ecs_assert(src_table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(!src_table->lock, ECS_LOCKED_STORAGE, NULL); flecs_table_check_sanity(dst_table); flecs_table_check_sanity(src_table); bool move_data = false; /* If there is nothing to merge to, just clear the old table */ if (!dst_table) { flecs_table_clear_data(world, src_table, src_data); flecs_table_check_sanity(src_table); return; } else { ecs_assert(!dst_table->lock, ECS_LOCKED_STORAGE, NULL); } /* If there is no data to merge, drop out */ if (!src_data) { return; } if (!dst_data) { dst_data = &dst_table->data; if (dst_table == src_table) { move_data = true; } } ecs_entity_t *src_entities = src_data->entities.array; int32_t src_count = src_data->entities.count; int32_t dst_count = dst_data->entities.count; ecs_record_t **src_records = src_data->records.array; /* First, update entity index so old entities point to new type */ int32_t i; for(i = 0; i < src_count; i ++) { ecs_record_t *record; if (dst_table != src_table) { record = src_records[i]; ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); } else { record = flecs_entities_ensure(world, src_entities[i]); } uint32_t flags = ECS_RECORD_TO_ROW_FLAGS(record->row); record->row = ECS_ROW_TO_RECORD(dst_count + i, flags); record->table = dst_table; } /* Merge table columns */ if (move_data) { *dst_data = *src_data; } else { flecs_merge_table_data(world, dst_table, src_table, src_count, dst_count, src_data, dst_data); } if (src_count) { if (!dst_count) { flecs_table_set_empty(world, dst_table); } flecs_table_set_empty(world, src_table); flecs_table_observer_add(dst_table, src_table->observed_count); flecs_table_observer_add(src_table, -src_table->observed_count); ecs_assert(src_table->observed_count == 0, ECS_INTERNAL_ERROR, NULL); } flecs_table_check_sanity(src_table); flecs_table_check_sanity(dst_table); } void flecs_table_replace_data( ecs_world_t *world, ecs_table_t *table, ecs_data_t *data) { int32_t prev_count = 0; ecs_data_t *table_data = &table->data; ecs_assert(!data || data != table_data, ECS_INTERNAL_ERROR, NULL); ecs_assert(!table->lock, ECS_LOCKED_STORAGE, NULL); flecs_table_check_sanity(table); prev_count = table_data->entities.count; flecs_table_notify_on_remove(world, table, table_data); flecs_table_clear_data(world, table, table_data); if (data) { table->data = *data; } else { flecs_table_init_data(world, table); } int32_t count = ecs_table_count(table); if (!prev_count && count) { flecs_table_set_empty(world, table); } else if (prev_count && !count) { flecs_table_set_empty(world, table); } flecs_table_check_sanity(table); } int32_t* flecs_table_get_dirty_state( ecs_world_t *world, ecs_table_t *table) { if (!table->dirty_state) { int32_t column_count = table->storage_count; table->dirty_state = flecs_alloc_n(&world->allocator, int32_t, column_count + 1); ecs_assert(table->dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); for (int i = 0; i < column_count + 1; i ++) { table->dirty_state[i] = 1; } } return table->dirty_state; } void flecs_table_notify( ecs_world_t *world, ecs_table_t *table, ecs_table_event_t *event) { if (world->flags & EcsWorldFini) { return; } switch(event->kind) { case EcsTableTriggersForId: flecs_table_add_trigger_flags(world, table, event->event); break; case EcsTableNoTriggersForId: break; } } void ecs_table_lock( ecs_world_t *world, ecs_table_t *table) { if (table) { if (ecs_poly_is(world, ecs_world_t) && !(world->flags & EcsWorldReadonly)) { table->lock ++; } } } void ecs_table_unlock( ecs_world_t *world, ecs_table_t *table) { if (table) { if (ecs_poly_is(world, ecs_world_t) && !(world->flags & EcsWorldReadonly)) { table->lock --; ecs_assert(table->lock >= 0, ECS_INVALID_OPERATION, NULL); } } } bool ecs_table_has_module( ecs_table_t *table) { return table->flags & EcsTableHasModule; } ecs_vec_t* ecs_table_column_for_id( const ecs_world_t *world, const ecs_table_t *table, ecs_id_t id) { ecs_table_t *storage_table = table->storage_table; if (!storage_table) { return NULL; } ecs_table_record_t *tr = flecs_table_record_get(world, storage_table, id); if (tr) { return &table->data.columns[tr->column]; } return NULL; } const ecs_type_t* ecs_table_get_type( const ecs_table_t *table) { if (table) { return &table->type; } else { return NULL; } } ecs_table_t* ecs_table_get_storage_table( const ecs_table_t *table) { return table->storage_table; } int32_t ecs_table_type_to_storage_index( const ecs_table_t *table, int32_t index) { ecs_assert(index >= 0, ECS_INVALID_PARAMETER, NULL); ecs_check(index < table->type.count, ECS_INVALID_PARAMETER, NULL); int32_t *storage_map = table->storage_map; if (storage_map) { return storage_map[index]; } error: return -1; } int32_t ecs_table_storage_to_type_index( const ecs_table_t *table, int32_t index) { ecs_check(index < table->storage_count, ECS_INVALID_PARAMETER, NULL); ecs_check(table->storage_map != NULL, ECS_INVALID_PARAMETER, NULL); int32_t offset = table->type.count; return table->storage_map[offset + index]; error: return -1; } int32_t flecs_table_column_to_union_index( const ecs_table_t *table, int32_t column) { int32_t sw_count = table->sw_count; if (sw_count) { int32_t sw_offset = table->sw_offset; if (column >= sw_offset && column < (sw_offset + sw_count)){ return column - sw_offset; } } return -1; } ecs_record_t* ecs_record_find( const ecs_world_t *world, ecs_entity_t entity) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); ecs_record_t *r = flecs_entities_get(world, entity); if (r) { return r; } error: return NULL; } void* ecs_table_get_column( const ecs_table_t *table, int32_t index, int32_t offset) { ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(index < table->type.count, ECS_INVALID_PARAMETER, NULL); int32_t storage_index = table->storage_map[index]; if (storage_index == -1) { return NULL; } void *result = table->data.columns[storage_index].array; if (offset) { ecs_size_t size = table->type_info[storage_index]->size; result = ECS_ELEM(result, size, offset); } return result; error: return NULL; } int32_t ecs_table_get_index( const ecs_world_t *world, const ecs_table_t *table, ecs_id_t id) { ecs_poly_assert(world, ecs_world_t); ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); ecs_id_record_t *idr = flecs_id_record_get(world, id); if (!idr) { return -1; } const ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); if (!tr) { return -1; } return tr->column; error: return -1; } void* ecs_table_get_id( const ecs_world_t *world, const ecs_table_t *table, ecs_id_t id, int32_t offset) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); int32_t index = ecs_table_get_index(world, table, id); if (index == -1) { return NULL; } return ecs_table_get_column(table, index, offset); error: return NULL; } int32_t ecs_table_get_depth( const ecs_world_t *world, const ecs_table_t *table, ecs_entity_t rel) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_id_is_valid(world, rel), ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_has_id(world, rel, EcsAcyclic), ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); return flecs_relation_depth(world, rel, table); error: return -1; } void* ecs_record_get_column( const ecs_record_t *r, int32_t column, size_t c_size) { (void)c_size; ecs_table_t *table = r->table; ecs_check(column < table->storage_count, ECS_INVALID_PARAMETER, NULL); ecs_type_info_t *ti = table->type_info[column]; ecs_vec_t *c = &table->data.columns[column]; ecs_assert(c != NULL, ECS_INTERNAL_ERROR, NULL); ecs_check(!flecs_utosize(c_size) || flecs_utosize(c_size) == ti->size, ECS_INVALID_PARAMETER, NULL); return ecs_vec_get(c, ti->size, ECS_RECORD_TO_ROW(r->row)); error: return NULL; } void ecs_table_swap_rows( ecs_world_t* world, ecs_table_t* table, int32_t row_1, int32_t row_2) { flecs_table_swap(world, table, row_1, row_2); } int32_t flecs_table_observed_count( const ecs_table_t *table) { return table->observed_count; } /** * @file poly.c * @brief Functions for managing poly objects. * * The poly framework makes it possible to generalize common functionality for * different kinds of API objects, as well as improved type safety checks. Poly * objects have a header that identifiers what kind of object it is. This can * then be used to discover a set of "mixins" implemented by the type. * * Mixins are like a vtable, but for members. Each type populates the table with * offsets to the members that correspond with the mixin. If an entry in the * mixin table is not set, the type does not support the mixin. * * An example is the Iterable mixin, which makes it possible to create an * iterator for any poly object (like filters, queries, the world) that * implements the Iterable mixin. */ static const char* mixin_kind_str[] = { [EcsMixinWorld] = "world", [EcsMixinEntity] = "entity", [EcsMixinObservable] = "observable", [EcsMixinIterable] = "iterable", [EcsMixinDtor] = "dtor", [EcsMixinMax] = "max (should never be requested by application)" }; ecs_mixins_t ecs_world_t_mixins = { .type_name = "ecs_world_t", .elems = { [EcsMixinWorld] = offsetof(ecs_world_t, self), [EcsMixinObservable] = offsetof(ecs_world_t, observable), [EcsMixinIterable] = offsetof(ecs_world_t, iterable) } }; ecs_mixins_t ecs_stage_t_mixins = { .type_name = "ecs_stage_t", .elems = { [EcsMixinWorld] = offsetof(ecs_stage_t, world) } }; ecs_mixins_t ecs_query_t_mixins = { .type_name = "ecs_query_t", .elems = { [EcsMixinWorld] = offsetof(ecs_query_t, filter.world), [EcsMixinEntity] = offsetof(ecs_query_t, filter.entity), [EcsMixinIterable] = offsetof(ecs_query_t, iterable), [EcsMixinDtor] = offsetof(ecs_query_t, dtor) } }; ecs_mixins_t ecs_observer_t_mixins = { .type_name = "ecs_observer_t", .elems = { [EcsMixinWorld] = offsetof(ecs_observer_t, filter.world), [EcsMixinEntity] = offsetof(ecs_observer_t, filter.entity), [EcsMixinDtor] = offsetof(ecs_observer_t, dtor) } }; ecs_mixins_t ecs_filter_t_mixins = { .type_name = "ecs_filter_t", .elems = { [EcsMixinWorld] = offsetof(ecs_filter_t, world), [EcsMixinEntity] = offsetof(ecs_filter_t, entity), [EcsMixinIterable] = offsetof(ecs_filter_t, iterable), [EcsMixinDtor] = offsetof(ecs_filter_t, dtor) } }; static void* assert_mixin( const ecs_poly_t *poly, ecs_mixin_kind_t kind) { ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(kind < EcsMixinMax, ECS_INVALID_PARAMETER, NULL); const ecs_header_t *hdr = poly; ecs_assert(hdr != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(hdr->magic == ECS_OBJECT_MAGIC, ECS_INVALID_PARAMETER, NULL); const ecs_mixins_t *mixins = hdr->mixins; ecs_assert(mixins != NULL, ECS_INVALID_PARAMETER, NULL); ecs_size_t offset = mixins->elems[kind]; ecs_assert(offset != 0, ECS_INVALID_PARAMETER, "mixin %s not available for type %s", mixin_kind_str[kind], mixins ? mixins->type_name : "unknown"); (void)mixin_kind_str; /* Object has mixin, return its address */ return ECS_OFFSET(hdr, offset); } void* _ecs_poly_init( ecs_poly_t *poly, int32_t type, ecs_size_t size, ecs_mixins_t *mixins) { ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); ecs_header_t *hdr = poly; ecs_os_memset(poly, 0, size); hdr->magic = ECS_OBJECT_MAGIC; hdr->type = type; hdr->mixins = mixins; return poly; } void _ecs_poly_fini( ecs_poly_t *poly, int32_t type) { ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); (void)type; ecs_header_t *hdr = poly; /* Don't deinit poly that wasn't initialized */ ecs_assert(hdr->magic == ECS_OBJECT_MAGIC, ECS_INVALID_PARAMETER, NULL); ecs_assert(hdr->type == type, ECS_INVALID_PARAMETER, NULL); hdr->magic = 0; } EcsPoly* _ecs_poly_bind( ecs_world_t *world, ecs_entity_t entity, ecs_entity_t tag) { /* Add tag to the entity for easy querying. This will make it possible to * query for `Query` instead of `(Poly, Query) */ if (!ecs_has_id(world, entity, tag)) { ecs_add_id(world, entity, tag); } /* Never defer creation of a poly object */ bool deferred = false; if (ecs_is_deferred(world)) { deferred = true; ecs_defer_suspend(world); } /* If this is a new poly, leave the actual creation up to the caller so they * call tell the difference between a create or an update */ EcsPoly *result = ecs_get_mut_pair(world, entity, EcsPoly, tag); if (deferred) { ecs_defer_resume(world); } return result; } void _ecs_poly_modified( ecs_world_t *world, ecs_entity_t entity, ecs_entity_t tag) { ecs_modified_pair(world, entity, ecs_id(EcsPoly), tag); } const EcsPoly* _ecs_poly_bind_get( const ecs_world_t *world, ecs_entity_t entity, ecs_entity_t tag) { return ecs_get_pair(world, entity, EcsPoly, tag); } ecs_poly_t* _ecs_poly_get( const ecs_world_t *world, ecs_entity_t entity, ecs_entity_t tag) { const EcsPoly *p = _ecs_poly_bind_get(world, entity, tag); if (p) { return p->poly; } return NULL; } #define assert_object(cond, file, line, type_name)\ _ecs_assert((cond), ECS_INVALID_PARAMETER, #cond, file, line, type_name);\ assert(cond) #ifndef FLECS_NDEBUG void* _ecs_poly_assert( const ecs_poly_t *poly, int32_t type, const char *file, int32_t line) { assert_object(poly != NULL, file, line, 0); const ecs_header_t *hdr = poly; const char *type_name = hdr->mixins->type_name; assert_object(hdr->magic == ECS_OBJECT_MAGIC, file, line, type_name); assert_object(hdr->type == type, file, line, type_name); return (ecs_poly_t*)poly; } #endif bool _ecs_poly_is( const ecs_poly_t *poly, int32_t type) { ecs_assert(poly != NULL, ECS_INVALID_PARAMETER, NULL); const ecs_header_t *hdr = poly; ecs_assert(hdr->magic == ECS_OBJECT_MAGIC, ECS_INVALID_PARAMETER, NULL); return hdr->type == type; } ecs_iterable_t* ecs_get_iterable( const ecs_poly_t *poly) { return (ecs_iterable_t*)assert_mixin(poly, EcsMixinIterable); } ecs_observable_t* ecs_get_observable( const ecs_poly_t *poly) { return (ecs_observable_t*)assert_mixin(poly, EcsMixinObservable); } const ecs_world_t* ecs_get_world( const ecs_poly_t *poly) { if (((ecs_header_t*)poly)->type == ecs_world_t_magic) { return poly; } return *(ecs_world_t**)assert_mixin(poly, EcsMixinWorld); } ecs_entity_t ecs_get_entity( const ecs_poly_t *poly) { return *(ecs_entity_t*)assert_mixin(poly, EcsMixinEntity); } ecs_poly_dtor_t* ecs_get_dtor( const ecs_poly_t *poly) { return (ecs_poly_dtor_t*)assert_mixin(poly, EcsMixinDtor); } /** * @file entity.c * @brief Entity API. * * This file contains the implementation for the entity API, which includes * creating/deleting entities, adding/removing/setting components, instantiating * prefabs, and several other APIs for retrieving entity data. * * The file also contains the implementation of the command buffer, which is * located here so it can call functions private to the compilation unit. */ #include static const ecs_entity_t* flecs_bulk_new( ecs_world_t *world, ecs_table_t *table, const ecs_entity_t *entities, ecs_type_t *component_ids, int32_t count, void **c_info, bool move, int32_t *row_out, ecs_table_diff_t *diff); typedef struct { ecs_type_info_t *ti; void *ptr; } flecs_component_ptr_t; static flecs_component_ptr_t flecs_get_component_w_index( ecs_table_t *table, int32_t column_index, int32_t row) { ecs_check(column_index < table->storage_count, ECS_NOT_A_COMPONENT, NULL); ecs_type_info_t *ti = table->type_info[column_index]; ecs_vec_t *column = &table->data.columns[column_index]; return (flecs_component_ptr_t){ .ti = ti, .ptr = ecs_vec_get(column, ti->size, row) }; error: return (flecs_component_ptr_t){0}; } static flecs_component_ptr_t flecs_get_component_ptr( const ecs_world_t *world, ecs_table_t *table, int32_t row, ecs_id_t id) { ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL); if (!table->storage_table) { ecs_check(ecs_search(world, table, id, 0) == -1, ECS_NOT_A_COMPONENT, NULL); return (flecs_component_ptr_t){0}; } ecs_table_record_t *tr = flecs_table_record_get( world, table->storage_table, id); if (!tr) { ecs_check(ecs_search(world, table, id, 0) == -1, ECS_NOT_A_COMPONENT, NULL); return (flecs_component_ptr_t){0}; } return flecs_get_component_w_index(table, tr->column, row); error: return (flecs_component_ptr_t){0}; } static void* flecs_get_component( const ecs_world_t *world, ecs_table_t *table, int32_t row, ecs_id_t id) { return flecs_get_component_ptr(world, table, row, id).ptr; } void* flecs_get_base_component( const ecs_world_t *world, ecs_table_t *table, ecs_id_t id, ecs_id_record_t *table_index, int32_t recur_depth) { /* Cycle detected in IsA relationship */ ecs_check(recur_depth < ECS_MAX_RECURSION, ECS_INVALID_PARAMETER, NULL); /* Table (and thus entity) does not have component, look for base */ if (!(table->flags & EcsTableHasIsA)) { return NULL; } /* Exclude Name */ if (id == ecs_pair(ecs_id(EcsIdentifier), EcsName)) { return NULL; } /* Table should always be in the table index for (IsA, *), otherwise the * HasBase flag should not have been set */ const ecs_table_record_t *tr_isa = flecs_id_record_get_table( world->idr_isa_wildcard, table); ecs_check(tr_isa != NULL, ECS_INTERNAL_ERROR, NULL); ecs_type_t type = table->type; ecs_id_t *ids = type.array; int32_t i = tr_isa->column, end = tr_isa->count + tr_isa->column; void *ptr = NULL; do { ecs_id_t pair = ids[i ++]; ecs_entity_t base = ecs_pair_second(world, pair); ecs_record_t *r = flecs_entities_get(world, base); if (!r) { continue; } table = r->table; if (!table) { continue; } const ecs_table_record_t *tr = NULL; ecs_table_t *storage_table = table->storage_table; if (storage_table) { tr = flecs_id_record_get_table(table_index, storage_table); } else { ecs_check(!ecs_owns_id(world, base, id), ECS_NOT_A_COMPONENT, NULL); } if (!tr) { ptr = flecs_get_base_component(world, table, id, table_index, recur_depth + 1); } else { int32_t row = ECS_RECORD_TO_ROW(r->row); ptr = flecs_get_component_w_index(table, tr->column, row).ptr; } } while (!ptr && (i < end)); return ptr; error: return NULL; } static void flecs_instantiate_slot( ecs_world_t *world, ecs_entity_t base, ecs_entity_t instance, ecs_entity_t slot_of, ecs_entity_t slot, ecs_entity_t child) { if (base == slot_of) { /* Instance inherits from slot_of, add slot to instance */ ecs_add_pair(world, instance, slot, child); } else { /* Slot is registered for other prefab, travel hierarchy * upwards to find instance that inherits from slot_of */ ecs_entity_t parent = instance; int32_t depth = 0; do { if (ecs_has_pair(world, parent, EcsIsA, slot_of)) { const char *name = ecs_get_name(world, slot); if (name == NULL) { char *slot_of_str = ecs_get_fullpath(world, slot_of); ecs_throw(ECS_INVALID_OPERATION, "prefab '%s' has unnamed " "slot (slots must be named)", slot_of_str); ecs_os_free(slot_of_str); return; } /* The 'slot' variable is currently pointing to a child (or * grandchild) of the current base. Find the original slot by * looking it up under the prefab it was registered. */ if (depth == 0) { /* If the current instance is an instance of slot_of, just * lookup the slot by name, which is faster than having to * create a relative path. */ slot = ecs_lookup_child(world, slot_of, name); } else { /* If the slot is more than one level away from the slot_of * parent, use a relative path to find the slot */ char *path = ecs_get_path_w_sep(world, parent, child, ".", NULL); slot = ecs_lookup_path_w_sep(world, slot_of, path, ".", NULL, false); ecs_os_free(path); } if (slot == 0) { char *slot_of_str = ecs_get_fullpath(world, slot_of); char *slot_str = ecs_get_fullpath(world, slot); ecs_throw(ECS_INVALID_OPERATION, "'%s' is not in hierarchy for slot '%s'", slot_of_str, slot_str); ecs_os_free(slot_of_str); ecs_os_free(slot_str); } ecs_add_pair(world, parent, slot, child); break; } depth ++; } while ((parent = ecs_get_target(world, parent, EcsChildOf, 0))); if (parent == 0) { char *slot_of_str = ecs_get_fullpath(world, slot_of); char *slot_str = ecs_get_fullpath(world, slot); ecs_throw(ECS_INVALID_OPERATION, "'%s' is not in hierarchy for slot '%s'", slot_of_str, slot_str); ecs_os_free(slot_of_str); ecs_os_free(slot_str); } } error: return; } static ecs_table_t* flecs_find_table_add( ecs_world_t *world, ecs_table_t *table, ecs_id_t id, ecs_table_diff_builder_t *diff) { ecs_table_diff_t temp_diff = ECS_TABLE_DIFF_INIT; table = flecs_table_traverse_add(world, table, &id, &temp_diff); ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); flecs_table_diff_build_append_table(world, diff, &temp_diff); return table; error: return NULL; } static ecs_table_t* flecs_find_table_remove( ecs_world_t *world, ecs_table_t *table, ecs_id_t id, ecs_table_diff_builder_t *diff) { ecs_table_diff_t temp_diff = ECS_TABLE_DIFF_INIT; table = flecs_table_traverse_remove(world, table, &id, &temp_diff); ecs_check(table != NULL, ECS_INVALID_PARAMETER, NULL); flecs_table_diff_build_append_table(world, diff, &temp_diff); return table; error: return NULL; } static void flecs_instantiate_children( ecs_world_t *world, ecs_entity_t base, ecs_table_t *table, int32_t row, int32_t count, ecs_table_t *child_table) { if (!ecs_table_count(child_table)) { return; } ecs_type_t type = child_table->type; ecs_data_t *child_data = &child_table->data; ecs_entity_t slot_of = 0; ecs_entity_t *ids = type.array; int32_t type_count = type.count; /* Instantiate child table for each instance */ /* Create component array for creating the table */ ecs_type_t components = { .array = ecs_os_alloca_n(ecs_entity_t, type_count + 1) }; void **component_data = ecs_os_alloca_n(void*, type_count + 1); /* Copy in component identifiers. Find the base index in the component * array, since we'll need this to replace the base with the instance id */ int j, i, childof_base_index = -1, pos = 0; for (i = 0; i < type_count; i ++) { ecs_id_t id = ids[i]; /* If id has DontInherit flag don't inherit it, except for the name * and ChildOf pairs. The name is preserved so applications can lookup * the instantiated children by name. The ChildOf pair is replaced later * with the instance parent. */ if ((id != ecs_pair(ecs_id(EcsIdentifier), EcsName)) && ECS_PAIR_FIRST(id) != EcsChildOf) { if (id == EcsUnion) { /* This should eventually be handled by the DontInherit property * but right now there is no way to selectively apply it to * EcsUnion itself: it would also apply to (Union, *) pairs, * which would make all union relationships uninheritable. * * The reason this is explicitly skipped is so that slot * instances don't all end up with the Union property. */ continue; } ecs_table_record_t *tr = &child_table->records[i]; ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; if (idr->flags & EcsIdDontInherit) { continue; } } /* If child is a slot, keep track of which parent to add it to, but * don't add slot relationship to child of instance. If this is a child * of a prefab, keep the SlotOf relationship intact. */ if (!(table->flags & EcsTableIsPrefab)) { if (ECS_IS_PAIR(id) && ECS_PAIR_FIRST(id) == EcsSlotOf) { ecs_assert(slot_of == 0, ECS_INTERNAL_ERROR, NULL); slot_of = ecs_pair_second(world, id); continue; } } /* Keep track of the element that creates the ChildOf relationship with * the prefab parent. We need to replace this element to make sure the * created children point to the instance and not the prefab */ if (ECS_HAS_RELATION(id, EcsChildOf) && (ECS_PAIR_SECOND(id) == base)) { childof_base_index = pos; } int32_t storage_index = ecs_table_type_to_storage_index(child_table, i); if (storage_index != -1) { ecs_vec_t *column = &child_data->columns[storage_index]; component_data[pos] = ecs_vec_first(column); } else { component_data[pos] = NULL; } components.array[pos] = id; pos ++; } /* Table must contain children of base */ ecs_assert(childof_base_index != -1, ECS_INTERNAL_ERROR, NULL); /* If children are added to a prefab, make sure they are prefabs too */ if (table->flags & EcsTableIsPrefab) { components.array[pos] = EcsPrefab; component_data[pos] = NULL; pos ++; } components.count = pos; /* Instantiate the prefab child table for each new instance */ ecs_entity_t *instances = ecs_vec_first(&table->data.entities); int32_t child_count = ecs_vec_count(&child_data->entities); bool has_union = child_table->flags & EcsTableHasUnion; for (i = row; i < count + row; i ++) { ecs_entity_t instance = instances[i]; ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT; flecs_table_diff_builder_init(world, &diff); ecs_table_t *i_table = NULL; /* Replace ChildOf element in the component array with instance id */ components.array[childof_base_index] = ecs_pair(EcsChildOf, instance); /* Find or create table */ for (j = 0; j < components.count; j ++) { i_table = flecs_find_table_add( world, i_table, components.array[j], &diff); } ecs_assert(i_table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(i_table->type.count == components.count, ECS_INTERNAL_ERROR, NULL); /* The instance is trying to instantiate from a base that is also * its parent. This would cause the hierarchy to instantiate itself * which would cause infinite recursion. */ ecs_entity_t *children = ecs_vec_first(&child_data->entities); #ifdef FLECS_DEBUG for (j = 0; j < child_count; j ++) { ecs_entity_t child = children[j]; ecs_check(child != instance, ECS_INVALID_PARAMETER, NULL); } #else /* Bit of boilerplate to ensure that we don't get warnings about the * error label not being used. */ ecs_check(true, ECS_INVALID_OPERATION, NULL); #endif /* Create children */ int32_t child_row; ecs_table_diff_t table_diff; flecs_table_diff_build_noalloc(&diff, &table_diff); const ecs_entity_t *i_children = flecs_bulk_new(world, i_table, NULL, &components, child_count, component_data, false, &child_row, &table_diff); flecs_table_diff_builder_fini(world, &diff); /* If children have union relationships, initialize */ if (has_union) { int32_t u, u_count = child_table->sw_count; for (u = 0; u < u_count; u ++) { ecs_switch_t *src_sw = &child_table->data.sw_columns[i]; ecs_switch_t *dst_sw = &i_table->data.sw_columns[i]; ecs_vec_t *v_src_values = flecs_switch_values(src_sw); ecs_vec_t *v_dst_values = flecs_switch_values(dst_sw); uint64_t *src_values = ecs_vec_first(v_src_values); uint64_t *dst_values = ecs_vec_first(v_dst_values); for (j = 0; j < child_count; j ++) { dst_values[j] = src_values[j]; } } } /* If children are slots, add slot relationships to parent */ if (slot_of) { for (j = 0; j < child_count; j ++) { ecs_entity_t child = children[j]; ecs_entity_t i_child = i_children[j]; flecs_instantiate_slot(world, base, instance, slot_of, child, i_child); } } /* If prefab child table has children itself, recursively instantiate */ for (j = 0; j < child_count; j ++) { ecs_entity_t child = children[j]; flecs_instantiate(world, child, i_table, child_row + j, 1); } } error: return; } void flecs_instantiate( ecs_world_t *world, ecs_entity_t base, ecs_table_t *table, int32_t row, int32_t count) { ecs_table_t *base_table = ecs_get_table(world, ecs_get_alive(world, base)); if (!base_table || !(base_table->flags & EcsTableIsPrefab)) { /* Don't instantiate children from base entities that aren't prefabs */ return; } ecs_id_record_t *idr = flecs_id_record_get(world, ecs_childof(base)); ecs_table_cache_iter_t it; if (idr && flecs_table_cache_all_iter((ecs_table_cache_t*)idr, &it)) { const ecs_table_record_t *tr; while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { flecs_instantiate_children( world, base, table, row, count, tr->hdr.table); } } } static void flecs_set_union( ecs_world_t *world, ecs_table_t *table, int32_t row, int32_t count, const ecs_type_t *ids) { ecs_id_t *array = ids->array; int32_t i, id_count = ids->count; for (i = 0; i < id_count; i ++) { ecs_id_t id = array[i]; if (ECS_HAS_ID_FLAG(id, PAIR)) { ecs_id_record_t *idr = flecs_id_record_get(world, ecs_pair(EcsUnion, ECS_PAIR_FIRST(id))); if (!idr) { continue; } const ecs_table_record_t *tr = flecs_id_record_get_table( idr, table); ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); int32_t column = tr->column - table->sw_offset; ecs_switch_t *sw = &table->data.sw_columns[column]; ecs_entity_t union_case = 0; union_case = ECS_PAIR_SECOND(id); int32_t r; for (r = 0; r < count; r ++) { flecs_switch_set(sw, row + r, union_case); } } } } static void flecs_notify_on_add( ecs_world_t *world, ecs_table_t *table, ecs_table_t *other_table, int32_t row, int32_t count, const ecs_type_t *added, ecs_flags32_t flags) { ecs_assert(added != NULL, ECS_INTERNAL_ERROR, NULL); if (added->count) { ecs_flags32_t table_flags = table->flags; if (table_flags & EcsTableHasUnion) { flecs_set_union(world, table, row, count, added); } if (table_flags & (EcsTableHasOnAdd|EcsTableHasIsA|EcsTableHasObserved)) { flecs_emit(world, world, &(ecs_event_desc_t){ .event = EcsOnAdd, .ids = added, .table = table, .other_table = other_table, .offset = row, .count = count, .observable = world, .flags = flags }); } } } void flecs_notify_on_remove( ecs_world_t *world, ecs_table_t *table, ecs_table_t *other_table, int32_t row, int32_t count, const ecs_type_t *removed) { ecs_assert(removed != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(count != 0, ECS_INTERNAL_ERROR, NULL); if (removed->count && (table->flags & (EcsTableHasOnRemove|EcsTableHasUnSet|EcsTableHasIsA|EcsTableHasObserved))) { flecs_emit(world, world, &(ecs_event_desc_t) { .event = EcsOnRemove, .ids = removed, .table = table, .other_table = other_table, .offset = row, .count = count, .observable = world }); } } static ecs_record_t* flecs_new_entity( ecs_world_t *world, ecs_entity_t entity, ecs_record_t *record, ecs_table_t *table, ecs_table_diff_t *diff, bool ctor, ecs_flags32_t evt_flags) { ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); int32_t row = flecs_table_append(world, table, entity, record, ctor, true); record->table = table; record->row = ECS_ROW_TO_RECORD(row, record->row & ECS_ROW_FLAGS_MASK); ecs_assert(ecs_vec_count(&table->data.entities) > row, ECS_INTERNAL_ERROR, NULL); flecs_notify_on_add(world, table, NULL, row, 1, &diff->added, evt_flags); return record; } static void flecs_move_entity( ecs_world_t *world, ecs_entity_t entity, ecs_record_t *record, ecs_table_t *dst_table, ecs_table_diff_t *diff, bool ctor, ecs_flags32_t evt_flags) { ecs_table_t *src_table = record->table; int32_t src_row = ECS_RECORD_TO_ROW(record->row); ecs_assert(src_table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(src_table != dst_table, ECS_INTERNAL_ERROR, NULL); ecs_assert(src_table->type.count > 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(src_row >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(ecs_vec_count(&src_table->data.entities) > src_row, ECS_INTERNAL_ERROR, NULL); ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(record == flecs_entities_get(world, entity), ECS_INTERNAL_ERROR, NULL); /* Append new row to destination table */ int32_t dst_row = flecs_table_append(world, dst_table, entity, record, false, false); /* Invoke remove actions for removed components */ flecs_notify_on_remove( world, src_table, dst_table, src_row, 1, &diff->removed); /* Copy entity & components from src_table to dst_table */ flecs_table_move(world, entity, entity, dst_table, dst_row, src_table, src_row, ctor); /* Update entity index & delete old data after running remove actions */ record->table = dst_table; record->row = ECS_ROW_TO_RECORD(dst_row, record->row & ECS_ROW_FLAGS_MASK); flecs_table_delete(world, src_table, src_row, false); flecs_notify_on_add( world, dst_table, src_table, dst_row, 1, &diff->added, evt_flags); error: return; } static void flecs_delete_entity( ecs_world_t *world, ecs_record_t *record, ecs_table_diff_t *diff) { ecs_table_t *table = record->table; int32_t row = ECS_RECORD_TO_ROW(record->row); /* Invoke remove actions before deleting */ flecs_notify_on_remove(world, table, NULL, row, 1, &diff->removed); flecs_table_delete(world, table, row, true); } /* Updating component monitors is a relatively expensive operation that only * happens for entities that are monitored. The approach balances the amount of * processing between the operation on the entity vs the amount of work that * needs to be done to rematch queries, as a simple brute force approach does * not scale when there are many tables / queries. Therefore we need to do a bit * of bookkeeping that is more intelligent than simply flipping a flag */ static void flecs_update_component_monitor_w_array( ecs_world_t *world, ecs_type_t *ids) { if (!ids) { return; } int i; for (i = 0; i < ids->count; i ++) { ecs_entity_t id = ids->array[i]; if (ECS_HAS_ID_FLAG(id, PAIR)) { flecs_monitor_mark_dirty(world, ecs_pair(ECS_PAIR_FIRST(id), EcsWildcard)); } flecs_monitor_mark_dirty(world, id); } } static void flecs_update_component_monitors( ecs_world_t *world, ecs_type_t *added, ecs_type_t *removed) { flecs_update_component_monitor_w_array(world, added); flecs_update_component_monitor_w_array(world, removed); } static void flecs_commit( ecs_world_t *world, ecs_entity_t entity, ecs_record_t *record, ecs_table_t *dst_table, ecs_table_diff_t *diff, bool construct, ecs_flags32_t evt_flags) { ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INTERNAL_ERROR, NULL); flecs_journal_begin(world, EcsJournalMove, entity, &diff->added, &diff->removed); ecs_table_t *src_table = NULL; uint32_t row_flags = 0; int observed = 0; if (record) { src_table = record->table; row_flags = record->row & ECS_ROW_FLAGS_MASK; observed = (row_flags & EcsEntityIsTraversable) != 0; } if (src_table == dst_table) { /* If source and destination table are the same no action is needed * * However, if a component was added in the process of traversing a * table, this suggests that a union relationship could have changed. */ if (src_table) { flecs_notify_on_add(world, src_table, src_table, ECS_RECORD_TO_ROW(record->row), 1, &diff->added, evt_flags); } flecs_journal_end(); return; } if (src_table) { ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); flecs_table_observer_add(dst_table, observed); if (dst_table->type.count) { flecs_move_entity(world, entity, record, dst_table, diff, construct, evt_flags); } else { flecs_delete_entity(world, record, diff); record->table = NULL; } flecs_table_observer_add(src_table, -observed); } else { flecs_table_observer_add(dst_table, observed); if (dst_table->type.count) { flecs_new_entity(world, entity, record, dst_table, diff, construct, evt_flags); } } /* If the entity is being watched, it is being monitored for changes and * requires rematching systems when components are added or removed. This * ensures that systems that rely on components from containers or prefabs * update the matched tables when the application adds or removes a * component from, for example, a container. */ if (row_flags) { flecs_update_component_monitors(world, &diff->added, &diff->removed); } if ((!src_table || !src_table->type.count) && world->range_check_enabled) { ecs_check(!world->info.max_id || entity <= world->info.max_id, ECS_OUT_OF_RANGE, 0); ecs_check(entity >= world->info.min_id, ECS_OUT_OF_RANGE, 0); } error: flecs_journal_end(); return; } static const ecs_entity_t* flecs_bulk_new( ecs_world_t *world, ecs_table_t *table, const ecs_entity_t *entities, ecs_type_t *component_ids, int32_t count, void **component_data, bool is_move, int32_t *row_out, ecs_table_diff_t *diff) { int32_t sparse_count = 0; if (!entities) { sparse_count = flecs_entities_count(world); entities = flecs_entities_new_ids(world, count); } if (!table) { return entities; } ecs_type_t type = table->type; if (!type.count) { return entities; } ecs_type_t component_array = { 0 }; if (!component_ids) { component_ids = &component_array; component_array.array = type.array; component_array.count = type.count; } ecs_data_t *data = &table->data; int32_t row = flecs_table_appendn(world, table, data, count, entities); /* Update entity index. */ int i; ecs_record_t **records = ecs_vec_first(&data->records); for (i = 0; i < count; i ++) { ecs_record_t *r = flecs_entities_get(world, entities[i]); r->table = table; r->row = ECS_ROW_TO_RECORD(row + i, 0); records[row + i] = r; } flecs_defer_begin(world, &world->stages[0]); flecs_notify_on_add(world, table, NULL, row, count, &diff->added, (component_data == NULL) ? 0 : EcsEventNoOnSet); if (component_data) { int32_t c_i; ecs_table_t *storage_table = table->storage_table; for (c_i = 0; c_i < component_ids->count; c_i ++) { void *src_ptr = component_data[c_i]; if (!src_ptr) { continue; } /* Find component in storage type */ ecs_entity_t id = component_ids->array[c_i]; const ecs_table_record_t *tr = flecs_table_record_get( world, storage_table, id); ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(tr->count == 1, ECS_INTERNAL_ERROR, NULL); int32_t index = tr->column; ecs_type_info_t *ti = table->type_info[index]; ecs_vec_t *column = &table->data.columns[index]; int32_t size = ti->size; ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); void *ptr = ecs_vec_get(column, size, row); ecs_copy_t copy; ecs_move_t move; if (is_move && (move = ti->hooks.move)) { move(ptr, src_ptr, count, ti); } else if (!is_move && (copy = ti->hooks.copy)) { copy(ptr, src_ptr, count, ti); } else { ecs_os_memcpy(ptr, src_ptr, size * count); } }; flecs_notify_on_set(world, table, row, count, NULL, true); } flecs_defer_end(world, &world->stages[0]); if (row_out) { *row_out = row; } if (sparse_count) { entities = flecs_sparse_ids(ecs_eis(world)); return &entities[sparse_count]; } else { return entities; } } static void flecs_add_id_w_record( ecs_world_t *world, ecs_entity_t entity, ecs_record_t *record, ecs_id_t id, bool construct) { ecs_assert(record != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_t *src_table = record->table; ecs_table_diff_t diff = ECS_TABLE_DIFF_INIT; ecs_table_t *dst_table = flecs_table_traverse_add( world, src_table, &id, &diff); flecs_commit(world, entity, record, dst_table, &diff, construct, EcsEventNoOnSet); /* No OnSet, this function is only called from * functions that are about to set the component. */ } static void flecs_add_id( ecs_world_t *world, ecs_entity_t entity, ecs_id_t id) { ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_add(stage, entity, id)) { return; } ecs_record_t *r = flecs_entities_get(world, entity); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_diff_t diff = ECS_TABLE_DIFF_INIT; ecs_table_t *src_table = r->table; ecs_table_t *dst_table = flecs_table_traverse_add( world, src_table, &id, &diff); flecs_commit(world, entity, r, dst_table, &diff, true, 0); flecs_defer_end(world, stage); } static void flecs_remove_id( ecs_world_t *world, ecs_entity_t entity, ecs_id_t id) { ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_remove(stage, entity, id)) { return; } ecs_record_t *r = flecs_entities_get(world, entity); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); ecs_table_t *src_table = r->table; ecs_table_diff_t diff = ECS_TABLE_DIFF_INIT; ecs_table_t *dst_table = flecs_table_traverse_remove( world, src_table, &id, &diff); flecs_commit(world, entity, r, dst_table, &diff, true, 0); flecs_defer_end(world, stage); } static flecs_component_ptr_t flecs_get_mut( ecs_world_t *world, ecs_entity_t entity, ecs_entity_t id, ecs_record_t *r) { flecs_component_ptr_t dst = {0}; ecs_poly_assert(world, ecs_world_t); ecs_check(id != 0, ECS_INVALID_PARAMETER, NULL); ecs_check(r != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check((id & ECS_COMPONENT_MASK) == id || ECS_HAS_ID_FLAG(id, PAIR), ECS_INVALID_PARAMETER, NULL); if (r->table) { dst = flecs_get_component_ptr( world, r->table, ECS_RECORD_TO_ROW(r->row), id); if (dst.ptr) { return dst; } } /* If entity didn't have component yet, add it */ flecs_add_id_w_record(world, entity, r, id, true); /* Flush commands so the pointer we're fetching is stable */ flecs_defer_end(world, &world->stages[0]); flecs_defer_begin(world, &world->stages[0]); ecs_assert(r->table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(r->table->storage_table != NULL, ECS_INTERNAL_ERROR, NULL); dst = flecs_get_component_ptr( world, r->table, ECS_RECORD_TO_ROW(r->row), id); error: return dst; } void flecs_invoke_hook( ecs_world_t *world, ecs_table_t *table, int32_t count, int32_t row, ecs_entity_t *entities, void *ptr, ecs_id_t id, const ecs_type_info_t *ti, ecs_entity_t event, ecs_iter_action_t hook) { ecs_assert(ti->size != 0, ECS_INVALID_PARAMETER, NULL); ecs_iter_t it = { .field_count = 1}; it.entities = entities; flecs_iter_init(world, &it, flecs_iter_cache_all); it.world = world; it.real_world = world; it.table = table; it.ptrs[0] = ptr; it.sizes[0] = ti->size; it.ids[0] = id; it.event = event; it.event_id = id; it.ctx = ti->hooks.ctx; it.binding_ctx = ti->hooks.binding_ctx; it.count = count; it.offset = row; flecs_iter_validate(&it); hook(&it); ecs_iter_fini(&it); } void flecs_notify_on_set( ecs_world_t *world, ecs_table_t *table, int32_t row, int32_t count, ecs_type_t *ids, bool owned) { ecs_data_t *data = &table->data; ecs_entity_t *entities = ecs_vec_get_t( &data->entities, ecs_entity_t, row); ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert((row + count) <= ecs_vec_count(&data->entities), ECS_INTERNAL_ERROR, NULL); ecs_type_t local_ids; if (!ids) { local_ids.array = table->storage_ids; local_ids.count = table->storage_count; ids = &local_ids; } if (owned) { ecs_table_t *storage_table = table->storage_table; int i; for (i = 0; i < ids->count; i ++) { ecs_id_t id = ids->array[i]; const ecs_table_record_t *tr = flecs_table_record_get(world, storage_table, id); ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(tr->count == 1, ECS_INTERNAL_ERROR, NULL); int32_t column = tr->column; const ecs_type_info_t *ti = table->type_info[column]; ecs_iter_action_t on_set = ti->hooks.on_set; if (on_set) { ecs_vec_t *c = &table->data.columns[column]; void *ptr = ecs_vec_get(c, ti->size, row); flecs_invoke_hook(world, table, count, row, entities, ptr, id, ti, EcsOnSet, on_set); } } } /* Run OnSet notifications */ if (table->flags & EcsTableHasOnSet && ids->count) { flecs_emit(world, world, &(ecs_event_desc_t) { .event = EcsOnSet, .ids = ids, .table = table, .offset = row, .count = count, .observable = world }); } } ecs_record_t* flecs_add_flag( ecs_world_t *world, ecs_entity_t entity, uint32_t flag) { ecs_record_t *record = flecs_entities_get(world, entity); if (!record) { ecs_record_t *r = flecs_entities_ensure(world, entity); r->row = flag; r->table = NULL; } else { if (flag == EcsEntityIsTraversable) { if (!(record->row & flag)) { ecs_table_t *table = record->table; if (table) { flecs_table_observer_add(table, 1); } } } record->row |= flag; } return record; } /* -- Public functions -- */ bool ecs_commit( ecs_world_t *world, ecs_entity_t entity, ecs_record_t *record, ecs_table_t *table, const ecs_type_t *added, const ecs_type_t *removed) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_table_t *src_table = NULL; if (!record) { record = flecs_entities_get(world, entity); src_table = record->table; } ecs_table_diff_t diff = ECS_TABLE_DIFF_INIT; if (added) { diff.added = *added; } if (removed) { diff.added = *removed; } flecs_commit(world, entity, record, table, &diff, true, 0); return src_table != table; error: return false; } ecs_entity_t ecs_set_with( ecs_world_t *world, ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); ecs_id_t prev = stage->with; stage->with = id; return prev; error: return 0; } ecs_id_t ecs_get_with( const ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); const ecs_stage_t *stage = flecs_stage_from_readonly_world(world); return stage->with; error: return 0; } ecs_entity_t ecs_new_id( ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); const ecs_stage_t *stage = flecs_stage_from_readonly_world(world); /* It is possible that the world passed to this function is a stage, so * make sure we have the actual world. Cast away const since this is one of * the few functions that may modify the world while it is in readonly mode, * since it is thread safe (uses atomic inc when in threading mode) */ ecs_world_t *unsafe_world = (ecs_world_t*)ecs_get_world(world); ecs_entity_t entity; if (stage->async || (unsafe_world->flags & EcsWorldMultiThreaded)) { /* When using an async stage or world is in multithreading mode, make * sure OS API has threading functions initialized */ ecs_assert(ecs_os_has_threading(), ECS_INVALID_OPERATION, NULL); /* Can't atomically increase number above max int */ ecs_assert(unsafe_world->info.last_id < UINT_MAX, ECS_INVALID_OPERATION, NULL); entity = (ecs_entity_t)ecs_os_ainc( (int32_t*)&unsafe_world->info.last_id); } else { entity = flecs_entities_new_id(unsafe_world); } ecs_assert(!unsafe_world->info.max_id || ecs_entity_t_lo(entity) <= unsafe_world->info.max_id, ECS_OUT_OF_RANGE, NULL); flecs_journal(world, EcsJournalNew, entity, 0, 0); return entity; error: return 0; } ecs_entity_t ecs_new_low_id( ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); /* It is possible that the world passed to this function is a stage, so * make sure we have the actual world. Cast away const since this is one of * the few functions that may modify the world while it is in readonly mode, * but only if single threaded. */ ecs_world_t *unsafe_world = (ecs_world_t*)ecs_get_world(world); if (unsafe_world->flags & EcsWorldReadonly) { /* Can't issue new comp id while iterating when in multithreaded mode */ ecs_check(ecs_get_stage_count(world) <= 1, ECS_INVALID_WHILE_READONLY, NULL); } ecs_entity_t id = 0; if (unsafe_world->info.last_component_id < ECS_HI_COMPONENT_ID) { do { id = unsafe_world->info.last_component_id ++; } while (ecs_exists(unsafe_world, id) && id <= ECS_HI_COMPONENT_ID); } if (!id || id >= ECS_HI_COMPONENT_ID) { /* If the low component ids are depleted, return a regular entity id */ id = ecs_new_id(unsafe_world); } else { flecs_entities_ensure(world, id); } ecs_assert(ecs_get_type(world, id) == NULL, ECS_INTERNAL_ERROR, NULL); return id; error: return 0; } ecs_entity_t ecs_new_w_id( ecs_world_t *world, ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(!id || ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); ecs_entity_t entity = ecs_new_id(world); ecs_id_t ids[3]; ecs_type_t to_add = { .array = ids, .count = 0 }; if (id) { ids[to_add.count ++] = id; } ecs_id_t with = stage->with; if (with) { ids[to_add.count ++] = with; } ecs_entity_t scope = stage->scope; if (scope) { if (!id || !ECS_HAS_RELATION(id, EcsChildOf)) { ids[to_add.count ++] = ecs_pair(EcsChildOf, scope); } } if (to_add.count) { if (flecs_defer_add(stage, entity, to_add.array[0])) { int i; for (i = 1; i < to_add.count; i ++) { flecs_defer_add(stage, entity, to_add.array[i]); } return entity; } int32_t i, count = to_add.count; ecs_table_t *table = &world->store.root; ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT; flecs_table_diff_builder_init(world, &diff); for (i = 0; i < count; i ++) { table = flecs_find_table_add( world, table, to_add.array[i], &diff); } ecs_table_diff_t table_diff; flecs_table_diff_build_noalloc(&diff, &table_diff); ecs_record_t *r = flecs_entities_get(world, entity); flecs_new_entity(world, entity, r, table, &table_diff, true, true); flecs_table_diff_builder_fini(world, &diff); } else { if (flecs_defer_cmd(stage)) { return entity; } flecs_entities_ensure(world, entity); } flecs_defer_end(world, stage); return entity; error: return 0; } ecs_entity_t ecs_new_w_table( ecs_world_t *world, ecs_table_t *table) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); flecs_stage_from_world(&world); ecs_entity_t entity = ecs_new_id(world); ecs_record_t *r = flecs_entities_get(world, entity); ecs_table_diff_t table_diff = { .added = table->type }; flecs_new_entity(world, entity, r, table, &table_diff, true, true); return entity; error: return 0; } #ifdef FLECS_PARSER /* Traverse table graph by either adding or removing identifiers parsed from the * passed in expression. */ static ecs_table_t *flecs_traverse_from_expr( ecs_world_t *world, ecs_table_t *table, const char *name, const char *expr, ecs_table_diff_builder_t *diff, bool replace_and, bool *error) { const char *ptr = expr; if (ptr) { ecs_term_t term = {0}; while (ptr[0] && (ptr = ecs_parse_term(world, name, expr, ptr, &term))){ if (!ecs_term_is_initialized(&term)) { break; } if (!(term.first.flags & (EcsSelf|EcsUp))) { term.first.flags = EcsSelf; } if (!(term.second.flags & (EcsSelf|EcsUp))) { term.second.flags = EcsSelf; } if (!(term.src.flags & (EcsSelf|EcsUp))) { term.src.flags = EcsSelf; } if (ecs_term_finalize(world, &term)) { ecs_term_fini(&term); if (error) { *error = true; } return NULL; } if (!ecs_id_is_valid(world, term.id)) { ecs_term_fini(&term); ecs_parser_error(name, expr, (ptr - expr), "invalid term for add expression"); return NULL; } if (term.oper == EcsAnd || !replace_and) { /* Regular AND expression */ table = flecs_find_table_add(world, table, term.id, diff); } ecs_term_fini(&term); } if (!ptr) { if (error) { *error = true; } return NULL; } } return table; } /* Add/remove components based on the parsed expression. This operation is * slower than flecs_traverse_from_expr, but safe to use from a deferred context. */ static void flecs_defer_from_expr( ecs_world_t *world, ecs_entity_t entity, const char *name, const char *expr, bool is_add, bool replace_and) { const char *ptr = expr; if (ptr) { ecs_term_t term = {0}; while (ptr[0] && (ptr = ecs_parse_term(world, name, expr, ptr, &term))){ if (!ecs_term_is_initialized(&term)) { break; } if (ecs_term_finalize(world, &term)) { return; } if (!ecs_id_is_valid(world, term.id)) { ecs_term_fini(&term); ecs_parser_error(name, expr, (ptr - expr), "invalid term for add expression"); return; } if (term.oper == EcsAnd || !replace_and) { /* Regular AND expression */ if (is_add) { ecs_add_id(world, entity, term.id); } else { ecs_remove_id(world, entity, term.id); } } ecs_term_fini(&term); } } } #endif /* If operation is not deferred, add components by finding the target * table and moving the entity towards it. */ static int flecs_traverse_add( ecs_world_t *world, ecs_entity_t result, const char *name, const ecs_entity_desc_t *desc, ecs_entity_t scope, ecs_id_t with, bool flecs_new_entity, bool name_assigned) { const char *sep = desc->sep; const char *root_sep = desc->root_sep; ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT; flecs_table_diff_builder_init(world, &diff); /* Find existing table */ ecs_table_t *src_table = NULL, *table = NULL; ecs_record_t *r = flecs_entities_get(world, result); table = r->table; /* If a name is provided but not yet assigned, add the Name component */ if (name && !name_assigned) { table = flecs_find_table_add(world, table, ecs_pair(ecs_id(EcsIdentifier), EcsName), &diff); } /* Add components from the 'add' id array */ int32_t i = 0; ecs_id_t id; const ecs_id_t *ids = desc->add; while ((i < ECS_ID_CACHE_SIZE) && (id = ids[i ++])) { bool should_add = true; if (ECS_HAS_ID_FLAG(id, PAIR) && ECS_PAIR_FIRST(id) == EcsChildOf) { scope = ECS_PAIR_SECOND(id); if ((!desc->id && desc->name) || (name && !name_assigned)) { /* If name is added to entity, pass scope to add_path instead * of adding it to the table. The provided name may have nested * elements, in which case the parent provided here is not the * parent the entity will end up with. */ should_add = false; } } if (should_add) { table = flecs_find_table_add(world, table, id, &diff); } } /* Find destination table */ /* If this is a new entity without a name, add the scope. If a name is * provided, the scope will be added by the add_path_w_sep function */ if (flecs_new_entity) { if (flecs_new_entity && scope && !name && !name_assigned) { table = flecs_find_table_add( world, table, ecs_pair(EcsChildOf, scope), &diff); } if (with) { table = flecs_find_table_add(world, table, with, &diff); } } /* Add components from the 'add_expr' expression */ if (desc->add_expr && ecs_os_strcmp(desc->add_expr, "0")) { #ifdef FLECS_PARSER bool error = false; table = flecs_traverse_from_expr( world, table, name, desc->add_expr, &diff, true, &error); if (error) { flecs_table_diff_builder_fini(world, &diff); return -1; } #else ecs_abort(ECS_UNSUPPORTED, "parser addon is not available"); #endif } /* Commit entity to destination table */ if (src_table != table) { flecs_defer_begin(world, &world->stages[0]); ecs_table_diff_t table_diff; flecs_table_diff_build_noalloc(&diff, &table_diff); flecs_commit(world, result, r, table, &table_diff, true, 0); flecs_table_diff_builder_fini(world, &diff); flecs_defer_end(world, &world->stages[0]); } /* Set name */ if (name && !name_assigned) { ecs_add_path_w_sep(world, result, scope, name, sep, root_sep); ecs_assert(ecs_get_name(world, result) != NULL, ECS_INTERNAL_ERROR, NULL); } if (desc->symbol && desc->symbol[0]) { const char *sym = ecs_get_symbol(world, result); if (sym) { ecs_assert(!ecs_os_strcmp(desc->symbol, sym), ECS_INCONSISTENT_NAME, desc->symbol); } else { ecs_set_symbol(world, result, desc->symbol); } } flecs_table_diff_builder_fini(world, &diff); return 0; } /* When in deferred mode, we need to add/remove components one by one using * the regular operations. */ static void flecs_deferred_add_remove( ecs_world_t *world, ecs_entity_t entity, const char *name, const ecs_entity_desc_t *desc, ecs_entity_t scope, ecs_id_t with, bool flecs_new_entity, bool name_assigned) { const char *sep = desc->sep; const char *root_sep = desc->root_sep; /* If this is a new entity without a name, add the scope. If a name is * provided, the scope will be added by the add_path_w_sep function */ if (flecs_new_entity) { if (flecs_new_entity && scope && !name && !name_assigned) { ecs_add_id(world, entity, ecs_pair(EcsChildOf, scope)); } if (with) { ecs_add_id(world, entity, with); } } /* Add components from the 'add' id array */ int32_t i = 0; ecs_id_t id; const ecs_id_t *ids = desc->add; while ((i < ECS_ID_CACHE_SIZE) && (id = ids[i ++])) { bool defer = true; if (ECS_HAS_ID_FLAG(id, PAIR) && ECS_PAIR_FIRST(id) == EcsChildOf) { scope = ECS_PAIR_SECOND(id); if (name && (!desc->id || !name_assigned)) { /* New named entities are created by temporarily going out of * readonly mode to ensure no duplicates are created. */ defer = false; } } if (defer) { ecs_add_id(world, entity, id); } } /* Add components from the 'add_expr' expression */ if (desc->add_expr) { #ifdef FLECS_PARSER flecs_defer_from_expr(world, entity, name, desc->add_expr, true, true); #else ecs_abort(ECS_UNSUPPORTED, "parser addon is not available"); #endif } int32_t thread_count = ecs_get_stage_count(world); /* Set name */ if (name && !name_assigned) { ecs_add_path_w_sep(world, entity, scope, name, sep, root_sep); } /* Set symbol */ if (desc->symbol) { const char *sym = ecs_get_symbol(world, entity); if (!sym || ecs_os_strcmp(sym, desc->symbol)) { if (thread_count <= 1) { /* See above */ ecs_suspend_readonly_state_t state; ecs_world_t *real_world = flecs_suspend_readonly(world, &state); ecs_set_symbol(world, entity, desc->symbol); flecs_resume_readonly(real_world, &state); } else { ecs_set_symbol(world, entity, desc->symbol); } } } } ecs_entity_t ecs_entity_init( ecs_world_t *world, const ecs_entity_desc_t *desc) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); ecs_entity_t scope = stage->scope; ecs_id_t with = ecs_get_with(world); ecs_entity_t result = desc->id; const char *name = desc->name; const char *sep = desc->sep; if (!sep) { sep = "."; } if (name) { if (!name[0]) { name = NULL; } else if (flecs_name_is_id(name)){ ecs_entity_t id = flecs_name_to_id(world, name); if (!id) { return 0; } if (result && (id != result)) { ecs_err("name id conflicts with provided id"); return 0; } name = NULL; result = id; } } const char *root_sep = desc->root_sep; bool flecs_new_entity = false; bool name_assigned = false; /* Remove optional prefix from name. Entity names can be derived from * language identifiers, such as components (typenames) and systems * function names). Because C does not have namespaces, such identifiers * often encode the namespace as a prefix. * To ensure interoperability between C and C++ (and potentially other * languages with namespacing) the entity must be stored without this prefix * and with the proper namespace, which is what the name_prefix is for */ const char *prefix = world->info.name_prefix; if (name && prefix) { ecs_size_t len = ecs_os_strlen(prefix); if (!ecs_os_strncmp(name, prefix, len) && (isupper(name[len]) || name[len] == '_')) { if (name[len] == '_') { name = name + len + 1; } else { name = name + len; } } } /* Find or create entity */ if (!result) { if (name) { /* If add array contains a ChildOf pair, use it as scope instead */ const ecs_id_t *ids = desc->add; ecs_id_t id; int32_t i = 0; while ((i < ECS_ID_CACHE_SIZE) && (id = ids[i ++])) { if (ECS_HAS_ID_FLAG(id, PAIR) && (ECS_PAIR_FIRST(id) == EcsChildOf)) { scope = ECS_PAIR_SECOND(id); } } result = ecs_lookup_path_w_sep( world, scope, name, sep, root_sep, false); if (result) { name_assigned = true; } } if (!result) { if (desc->use_low_id) { result = ecs_new_low_id(world); } else { result = ecs_new_id(world); } flecs_new_entity = true; ecs_assert(ecs_get_type(world, result) == NULL, ECS_INTERNAL_ERROR, NULL); } } else { /* Make sure provided id is either alive or revivable */ ecs_ensure(world, result); name_assigned = ecs_has_pair( world, result, ecs_id(EcsIdentifier), EcsName); if (name && name_assigned) { /* If entity has name, verify that name matches. The name provided * to the function could either have been relative to the current * scope, or fully qualified. */ char *path; ecs_size_t root_sep_len = root_sep ? ecs_os_strlen(root_sep) : 0; if (root_sep && !ecs_os_strncmp(name, root_sep, root_sep_len)) { /* Fully qualified name was provided, so make sure to * compare with fully qualified name */ path = ecs_get_path_w_sep(world, 0, result, sep, root_sep); } else { /* Relative name was provided, so make sure to compare with * relative name */ path = ecs_get_path_w_sep(world, scope, result, sep, ""); } if (path) { if (ecs_os_strcmp(path, name)) { /* Mismatching name */ ecs_err("existing entity '%s' is initialized with " "conflicting name '%s'", path, name); ecs_os_free(path); return 0; } ecs_os_free(path); } } } ecs_assert(name_assigned == ecs_has_pair( world, result, ecs_id(EcsIdentifier), EcsName), ECS_INTERNAL_ERROR, NULL); if (stage->defer) { flecs_deferred_add_remove((ecs_world_t*)stage, result, name, desc, scope, with, flecs_new_entity, name_assigned); } else { if (flecs_traverse_add(world, result, name, desc, scope, with, flecs_new_entity, name_assigned)) { return 0; } } return result; error: return 0; } const ecs_entity_t* ecs_bulk_init( ecs_world_t *world, const ecs_bulk_desc_t *desc) { ecs_poly_assert(world, ecs_world_t); ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INTERNAL_ERROR, NULL); ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, NULL); const ecs_entity_t *entities = desc->entities; int32_t count = desc->count; int32_t sparse_count = 0; if (!entities) { sparse_count = flecs_entities_count(world); entities = flecs_sparse_new_ids(ecs_eis(world), count); ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL); } else { int i; for (i = 0; i < count; i ++) { ecs_ensure(world, entities[i]); } } ecs_type_t ids; ecs_table_t *table = desc->table; if (!table) { ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT; flecs_table_diff_builder_init(world, &diff); int32_t i = 0; ecs_id_t id; while ((id = desc->ids[i])) { table = flecs_find_table_add(world, table, id, &diff); i ++; } ids.array = (ecs_id_t*)desc->ids; ids.count = i; ecs_table_diff_t table_diff; flecs_table_diff_build_noalloc(&diff, &table_diff); flecs_bulk_new(world, table, entities, &ids, count, desc->data, true, NULL, &table_diff); flecs_table_diff_builder_fini(world, &diff); } else { ecs_table_diff_t diff = { .added.array = table->type.array, .added.count = table->type.count }; ids = (ecs_type_t){.array = diff.added.array, .count = diff.added.count}; flecs_bulk_new(world, table, entities, &ids, count, desc->data, true, NULL, &diff); } if (!sparse_count) { return entities; } else { /* Refetch entity ids, in case the underlying array was reallocated */ entities = flecs_sparse_ids(ecs_eis(world)); return &entities[sparse_count]; } error: return NULL; } static void flecs_check_component( ecs_world_t *world, ecs_entity_t result, const EcsComponent *ptr, ecs_size_t size, ecs_size_t alignment) { if (ptr->size != size) { char *path = ecs_get_fullpath(world, result); ecs_abort(ECS_INVALID_COMPONENT_SIZE, path); ecs_os_free(path); } if (ptr->alignment != alignment) { char *path = ecs_get_fullpath(world, result); ecs_abort(ECS_INVALID_COMPONENT_ALIGNMENT, path); ecs_os_free(path); } } ecs_entity_t ecs_component_init( ecs_world_t *world, const ecs_component_desc_t *desc) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, NULL); /* If existing entity is provided, check if it is already registered as a * component and matches the size/alignment. This can prevent having to * suspend readonly mode, and increases the number of scenarios in which * this function can be called in multithreaded mode. */ ecs_entity_t result = desc->entity; if (result && ecs_is_alive(world, result)) { const EcsComponent *const_ptr = ecs_get(world, result, EcsComponent); if (const_ptr) { flecs_check_component(world, result, const_ptr, desc->type.size, desc->type.alignment); return result; } } ecs_suspend_readonly_state_t readonly_state; world = flecs_suspend_readonly(world, &readonly_state); bool new_component = true; if (!result) { result = ecs_new_low_id(world); } else { ecs_ensure(world, result); new_component = ecs_has(world, result, EcsComponent); } EcsComponent *ptr = ecs_get_mut(world, result, EcsComponent); if (!ptr->size) { ecs_assert(ptr->alignment == 0, ECS_INTERNAL_ERROR, NULL); ptr->size = desc->type.size; ptr->alignment = desc->type.alignment; if (!new_component || ptr->size != desc->type.size) { if (!ptr->size) { ecs_trace("#[green]tag#[reset] %s created", ecs_get_name(world, result)); } else { ecs_trace("#[green]component#[reset] %s created", ecs_get_name(world, result)); } } } else { flecs_check_component(world, result, ptr, desc->type.size, desc->type.alignment); } ecs_modified(world, result, EcsComponent); if (desc->type.size && !ecs_id_in_use(world, result) && !ecs_id_in_use(world, ecs_pair(result, EcsWildcard))) { ecs_set_hooks_id(world, result, &desc->type.hooks); } if (result >= world->info.last_component_id && result < ECS_HI_COMPONENT_ID) { world->info.last_component_id = result + 1; } /* Ensure components cannot be deleted */ ecs_add_pair(world, result, EcsOnDelete, EcsPanic); flecs_resume_readonly(world, &readonly_state); ecs_assert(result != 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(ecs_has(world, result, EcsComponent), ECS_INTERNAL_ERROR, NULL); return result; error: return 0; } const ecs_entity_t* ecs_bulk_new_w_id( ecs_world_t *world, ecs_id_t id, int32_t count) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); const ecs_entity_t *ids; if (flecs_defer_bulk_new(world, stage, count, id, &ids)) { return ids; } ecs_table_t *table = &world->store.root; ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT; flecs_table_diff_builder_init(world, &diff); if (id) { table = flecs_find_table_add(world, table, id, &diff); } ecs_table_diff_t td; flecs_table_diff_build_noalloc(&diff, &td); ids = flecs_bulk_new(world, table, NULL, NULL, count, NULL, false, NULL, &td); flecs_table_diff_builder_fini(world, &diff); flecs_defer_end(world, stage); return ids; error: return NULL; } void ecs_clear( ecs_world_t *world, ecs_entity_t entity) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_clear(stage, entity)) { return; } ecs_record_t *r = flecs_entities_get(world, entity); if (!r) { return; /* Nothing to clear */ } ecs_table_t *table = r->table; if (table) { ecs_table_diff_t diff = { .removed = table->type }; flecs_delete_entity(world, r, &diff); r->table = NULL; if (r->row & EcsEntityIsTraversable) { flecs_table_observer_add(table, -1); } } flecs_defer_end(world, stage); error: return; } static void flecs_throw_invalid_delete( ecs_world_t *world, ecs_id_t id) { char *id_str = NULL; if (!(world->flags & EcsWorldQuit)) { id_str = ecs_id_str(world, id); ecs_throw(ECS_CONSTRAINT_VIOLATED, id_str); } error: ecs_os_free(id_str); } static void flecs_marked_id_push( ecs_world_t *world, ecs_id_record_t* idr, ecs_entity_t action, bool delete_id) { ecs_marked_id_t *m = ecs_vec_append_t(&world->allocator, &world->store.marked_ids, ecs_marked_id_t); m->idr = idr; m->id = idr->id; m->action = action; m->delete_id = delete_id; flecs_id_record_claim(world, idr); } static void flecs_id_mark_for_delete( ecs_world_t *world, ecs_id_record_t *idr, ecs_entity_t action, bool delete_id); static void flecs_targets_mark_for_delete( ecs_world_t *world, ecs_table_t *table) { ecs_id_record_t *idr; ecs_entity_t *entities = ecs_vec_first(&table->data.entities); ecs_record_t **records = ecs_vec_first(&table->data.records); int32_t i, count = ecs_vec_count(&table->data.entities); for (i = 0; i < count; i ++) { ecs_record_t *r = records[i]; if (!r) { continue; } /* If entity is not used as id or as relationship target, there won't * be any tables with a reference to it. */ ecs_flags32_t flags = r->row & ECS_ROW_FLAGS_MASK; if (!(flags & (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_OBJECT(idr->flags), true); } if ((idr = flecs_id_record_get(world, ecs_pair(EcsFlag, e)))) { flecs_id_mark_for_delete(world, idr, ECS_ID_ON_DELETE_OBJECT(idr->flags), true); } } } } static bool flecs_id_is_delete_target( ecs_id_t id, ecs_entity_t action) { if (!action && ecs_id_is_pair(id) && ECS_PAIR_FIRST(id) == EcsWildcard) { /* If no explicit delete action is provided, and the id we're deleting * has the form (*, Target), use OnDeleteTarget action */ return true; } return false; } static ecs_entity_t flecs_get_delete_action( ecs_table_t *table, ecs_table_record_t *tr, ecs_entity_t action, bool delete_target) { ecs_entity_t result = action; if (!result && delete_target) { /* If action is not specified and we're deleting a relationship target, * derive the action from the current record */ ecs_table_record_t *trr = &table->records[tr->column]; ecs_id_record_t *idrr = (ecs_id_record_t*)trr->hdr.cache; result = ECS_ID_ON_DELETE_OBJECT(idrr->flags); } return result; } static void flecs_update_monitors_for_delete( ecs_world_t *world, ecs_id_t id) { flecs_update_component_monitors(world, NULL, &(ecs_type_t){ .array = (ecs_id_t[]){id}, .count = 1 }); } static void flecs_id_mark_for_delete( ecs_world_t *world, ecs_id_record_t *idr, ecs_entity_t action, bool delete_id) { if (idr->flags & EcsIdMarkedForDelete) { return; } idr->flags |= EcsIdMarkedForDelete; flecs_marked_id_push(world, idr, action, delete_id); ecs_id_t id = idr->id; bool delete_target = flecs_id_is_delete_target(id, action); /* Mark all tables with the id for delete */ ecs_table_cache_iter_t it; if (flecs_table_cache_iter(&idr->cache, &it)) { ecs_table_record_t *tr; while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { ecs_table_t *table = tr->hdr.table; if (table->flags & EcsTableMarkedForDelete) { continue; } ecs_entity_t cur_action = flecs_get_delete_action(table, tr, action, delete_target); /* If this is a Delete action, recursively mark ids & tables */ if (cur_action == EcsDelete) { table->flags |= EcsTableMarkedForDelete; ecs_log_push_2(); flecs_targets_mark_for_delete(world, table); ecs_log_pop_2(); } else if (cur_action == EcsPanic) { flecs_throw_invalid_delete(world, id); } } } /* Same for empty tables */ if (flecs_table_cache_empty_iter(&idr->cache, &it)) { ecs_table_record_t *tr; while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { tr->hdr.table->flags |= EcsTableMarkedForDelete; } } /* Signal query cache monitors */ flecs_update_monitors_for_delete(world, id); /* If id is a wildcard pair, update cache monitors for non-wildcard ids */ if (ecs_id_is_wildcard(id)) { ecs_assert(ECS_HAS_ID_FLAG(id, PAIR), ECS_INTERNAL_ERROR, NULL); ecs_id_record_t *cur = idr; if (ECS_PAIR_SECOND(id) == EcsWildcard) { while ((cur = cur->first.next)) { flecs_update_monitors_for_delete(world, cur->id); } } else { ecs_assert(ECS_PAIR_FIRST(id) == EcsWildcard, ECS_INTERNAL_ERROR, NULL); while ((cur = cur->second.next)) { flecs_update_monitors_for_delete(world, cur->id); } } } } static bool flecs_on_delete_mark( ecs_world_t *world, ecs_id_t id, ecs_entity_t action, bool delete_id) { ecs_id_record_t *idr = flecs_id_record_get(world, id); if (!idr) { /* If there's no id record, there's nothing to delete */ return false; } if (!action) { /* If no explicit action is provided, derive it */ if (!ecs_id_is_pair(id) || ECS_PAIR_SECOND(id) == EcsWildcard) { /* Delete actions are determined by the component, or in the case * of a pair by the relationship. */ action = ECS_ID_ON_DELETE(idr->flags); } } if (action == EcsPanic) { /* This id is protected from deletion */ flecs_throw_invalid_delete(world, id); return false; } flecs_id_mark_for_delete(world, idr, action, delete_id); return true; } static void flecs_remove_from_table( ecs_world_t *world, ecs_table_t *table) { ecs_table_diff_t temp_diff; ecs_table_diff_builder_t diff = ECS_TABLE_DIFF_INIT; flecs_table_diff_builder_init(world, &diff); ecs_table_t *dst_table = table; /* To find the dst table, remove all ids that are marked for deletion */ int32_t i, t, count = ecs_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->column; do { ecs_id_t id = dst_table->type.array[t]; dst_table = flecs_table_traverse_remove( world, dst_table, &id, &temp_diff); flecs_table_diff_build_append_table(world, &diff, &temp_diff); } while (dst_table->type.count && (t = ecs_search_offset( world, dst_table, t, idr->id, NULL)) != -1); } ecs_assert(dst_table != NULL, ECS_INTERNAL_ERROR, NULL); if (!dst_table->type.count) { /* If this removes all components, clear table */ flecs_table_clear_entities(world, table); } else { /* Otherwise, merge table into dst_table */ if (dst_table != table) { int32_t table_count = ecs_table_count(table); if (diff.removed.count && table_count) { ecs_log_push_3(); ecs_table_diff_t td; flecs_table_diff_build_noalloc(&diff, &td); flecs_notify_on_remove(world, table, NULL, 0, table_count, &td.removed); ecs_log_pop_3(); } flecs_table_merge(world, dst_table, table, &dst_table->data, &table->data); } } flecs_table_diff_builder_fini(world, &diff); } static bool flecs_on_delete_clear_tables( ecs_world_t *world) { /* 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); for (i = 0; i < count; i ++) { ecs_id_record_t *idr = ids[i].idr; bool delete_id = ids[i].delete_id; flecs_id_record_release_tables(world, idr); /* Release the claim taken by flecs_marked_id_push. This may delete the * id record as all other claims may have been released. */ int32_t rc = flecs_id_record_release(world, idr); ecs_assert(rc >= 0, ECS_INTERNAL_ERROR, NULL); (void)rc; /* If 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; } } } return true; } static void flecs_on_delete( ecs_world_t *world, ecs_id_t id, ecs_entity_t action, bool delete_id) { /* Cleanup can happen recursively. If a cleanup action is already in * progress, only append ids to the marked_ids. The topmost cleanup * frame will handle the actual cleanup. */ int32_t count = ecs_vec_count(&world->store.marked_ids); /* Make sure we're evaluating a consistent list of non-empty tables */ ecs_run_aperiodic(world, EcsAperiodicEmptyTables); /* Collect all ids that need to be deleted */ flecs_on_delete_mark(world, id, action, delete_id); /* Only perform cleanup if we're the first stack frame doing it */ if (!count && ecs_vec_count(&world->store.marked_ids)) { ecs_dbg_2("#[red]delete#[reset]"); ecs_log_push_2(); /* Empty tables with all the to be deleted ids */ flecs_on_delete_clear_tables(world); /* All marked tables are empty, ensure they're in the right list */ ecs_run_aperiodic(world, EcsAperiodicEmptyTables); /* Release remaining references to the ids */ flecs_on_delete_clear_ids(world); /* Verify deleted ids are no longer in use */ #ifdef FLECS_DEBUG ecs_marked_id_t *ids = ecs_vec_first(&world->store.marked_ids); int32_t i; count = ecs_vec_count(&world->store.marked_ids); for (i = 0; i < count; i ++) { ecs_assert(!ecs_id_in_use(world, ids[i].id), ECS_INTERNAL_ERROR, NULL); } #endif ecs_assert(!ecs_id_in_use(world, id), ECS_INTERNAL_ERROR, NULL); /* Ids are deleted, clear stack */ ecs_vec_clear(&world->store.marked_ids); ecs_log_pop_2(); } } void ecs_delete_with( ecs_world_t *world, ecs_id_t id) { flecs_journal_begin(world, EcsJournalDeleteWith, id, NULL, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_on_delete_action(stage, id, EcsDelete)) { return; } flecs_on_delete(world, id, EcsDelete, false); flecs_defer_end(world, stage); flecs_journal_end(); } void ecs_remove_all( ecs_world_t *world, ecs_id_t id) { flecs_journal_begin(world, EcsJournalRemoveAll, id, NULL, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_on_delete_action(stage, id, EcsRemove)) { return; } flecs_on_delete(world, id, EcsRemove, false); flecs_defer_end(world, stage); flecs_journal_end(); } void ecs_delete( ecs_world_t *world, ecs_entity_t entity) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_delete(stage, entity)) { return; } ecs_record_t *r = flecs_entities_try(world, entity); if (r) { flecs_journal_begin(world, EcsJournalDelete, entity, NULL, NULL); ecs_flags32_t row_flags = ECS_RECORD_TO_ROW_FLAGS(r->row); ecs_table_t *table; if (row_flags) { if (row_flags & EcsEntityIsTarget) { flecs_on_delete(world, ecs_pair(EcsFlag, entity), 0, true); flecs_on_delete(world, ecs_pair(EcsWildcard, entity), 0, true); r->idr = NULL; } if (row_flags & EcsEntityIsId) { flecs_on_delete(world, entity, 0, true); flecs_on_delete(world, ecs_pair(entity, EcsWildcard), 0, true); } if (row_flags & EcsEntityIsTraversable) { table = r->table; if (table) { flecs_table_observer_add(table, -1); } } /* Merge operations before deleting entity */ flecs_defer_end(world, stage); flecs_defer_begin(world, stage); } table = r->table; if (table) { ecs_table_diff_t diff = { .removed = table->type }; flecs_delete_entity(world, r, &diff); r->row = 0; r->table = NULL; } flecs_entities_remove(world, entity); flecs_journal_end(); } flecs_defer_end(world, stage); error: return; } void ecs_add_id( ecs_world_t *world, ecs_entity_t entity, ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); flecs_add_id(world, entity, id); error: return; } void ecs_remove_id( ecs_world_t *world, ecs_entity_t entity, ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_id_is_valid(world, id) || ecs_id_is_wildcard(id), ECS_INVALID_PARAMETER, NULL); flecs_remove_id(world, entity, id); error: return; } void ecs_override_id( ecs_world_t *world, ecs_entity_t entity, ecs_id_t id) { ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); ecs_add_id(world, entity, ECS_OVERRIDE | id); error: return; } ecs_entity_t ecs_clone( ecs_world_t *world, ecs_entity_t dst, ecs_entity_t src, bool copy_value) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(src != 0, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_alive(world, src), ECS_INVALID_PARAMETER, NULL); ecs_check(!dst || !ecs_get_table(world, dst), ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); if (!dst) { dst = ecs_new_id(world); } if (flecs_defer_clone(stage, dst, src, copy_value)) { return dst; } ecs_record_t *src_r = flecs_entities_get(world, src); ecs_table_t *src_table; if (!src_r || !(src_table = src_r->table)) { goto done; } ecs_type_t src_type = src_table->type; ecs_table_diff_t diff = { .added = src_type }; ecs_record_t *dst_r = flecs_entities_get(world, dst); flecs_new_entity(world, dst, dst_r, src_table, &diff, true, true); int32_t row = ECS_RECORD_TO_ROW(dst_r->row); if (copy_value) { flecs_table_move(world, dst, src, src_table, row, src_table, ECS_RECORD_TO_ROW(src_r->row), true); flecs_notify_on_set(world, src_table, row, 1, NULL, true); } done: flecs_defer_end(world, stage); return dst; error: return 0; } const void* ecs_get_id( const ecs_world_t *world, ecs_entity_t entity, ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_check(flecs_stage_from_readonly_world(world)->async == false, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); ecs_record_t *r = flecs_entities_get(world, entity); ecs_assert(r != NULL, ECS_INVALID_PARAMETER, NULL); ecs_table_t *table = r->table; if (!table) { return NULL; } ecs_id_record_t *idr = flecs_id_record_get(world, id); if (!idr) { return NULL; } const ecs_table_record_t *tr = NULL; ecs_table_t *storage_table = table->storage_table; if (storage_table) { tr = flecs_id_record_get_table(idr, storage_table); } else { /* If the entity does not have a storage table (has no data) but it does * have the id, the id must be a tag, and getting a tag is illegal. */ ecs_check(!ecs_owns_id(world, entity, id), ECS_NOT_A_COMPONENT, NULL); } if (!tr) { return flecs_get_base_component(world, table, id, idr, 0); } int32_t row = ECS_RECORD_TO_ROW(r->row); return flecs_get_component_w_index(table, tr->column, row).ptr; error: return NULL; } void* ecs_get_mut_id( ecs_world_t *world, ecs_entity_t entity, ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_cmd(stage)) { return flecs_defer_set( world, stage, EcsOpMut, entity, id, 0, NULL, true); } ecs_record_t *r = flecs_entities_get(world, entity); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); void *result = flecs_get_mut(world, entity, id, r).ptr; ecs_check(result != NULL, ECS_INVALID_PARAMETER, NULL); flecs_defer_end(world, stage); return result; error: return NULL; } static ecs_record_t* flecs_access_begin( ecs_world_t *stage, ecs_entity_t entity, bool write) { ecs_check(ecs_os_has_threading(), ECS_MISSING_OS_API, NULL); const ecs_world_t *world = ecs_get_world(stage); ecs_record_t *r = flecs_entities_get(world, entity); if (!r) { return NULL; } ecs_table_t *table; if (!(table = r->table)) { return NULL; } int32_t count = ecs_os_ainc(&table->lock); (void)count; if (write) { ecs_check(count == 1, ECS_ACCESS_VIOLATION, NULL); } return r; error: return NULL; } static void flecs_access_end( const ecs_record_t *r, bool write) { ecs_check(ecs_os_has_threading(), ECS_MISSING_OS_API, NULL); ecs_check(r != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(r->table != NULL, ECS_INVALID_PARAMETER, NULL); int32_t count = ecs_os_adec(&r->table->lock); (void)count; if (write) { ecs_check(count == 0, ECS_ACCESS_VIOLATION, NULL); } ecs_check(count >= 0, ECS_ACCESS_VIOLATION, NULL); error: return; } ecs_record_t* ecs_write_begin( ecs_world_t *world, ecs_entity_t entity) { return flecs_access_begin(world, entity, true); } void ecs_write_end( ecs_record_t *r) { flecs_access_end(r, true); } const ecs_record_t* ecs_read_begin( ecs_world_t *world, ecs_entity_t entity) { return flecs_access_begin(world, entity, false); } void ecs_read_end( const ecs_record_t *r) { flecs_access_end(r, false); } const void* ecs_record_get_id( ecs_world_t *stage, const ecs_record_t *r, ecs_id_t id) { const ecs_world_t *world = ecs_get_world(stage); return flecs_get_component(world, r->table, ECS_RECORD_TO_ROW(r->row), id); } void* ecs_record_get_mut_id( ecs_world_t *stage, ecs_record_t *r, ecs_id_t id) { const ecs_world_t *world = ecs_get_world(stage); return flecs_get_component(world, r->table, ECS_RECORD_TO_ROW(r->row), id); } ecs_ref_t ecs_ref_init_id( const ecs_world_t *world, ecs_entity_t entity, ecs_id_t id) { ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); ecs_record_t *record = flecs_entities_get(world, entity); ecs_check(record != NULL, ECS_INVALID_PARAMETER, "cannot create ref for empty entity"); ecs_ref_t result = { .entity = entity, .id = id, .record = record }; ecs_table_t *table = record->table; if (table) { result.tr = flecs_table_record_get(world, table, id); } return result; error: return (ecs_ref_t){0}; } void ecs_ref_update( const ecs_world_t *world, ecs_ref_t *ref) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ref != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ref->entity != 0, ECS_INVALID_PARAMETER, NULL); ecs_check(ref->id != 0, ECS_INVALID_PARAMETER, NULL); ecs_check(ref->record != NULL, ECS_INVALID_PARAMETER, NULL); ecs_record_t *r = ref->record; ecs_table_t *table = r->table; if (!table) { return; } ecs_table_record_t *tr = ref->tr; if (!tr || tr->hdr.table != table) { tr = ref->tr = flecs_table_record_get(world, table, ref->id); if (!tr) { return; } ecs_assert(tr->hdr.table == r->table, ECS_INTERNAL_ERROR, NULL); } error: return; } void* ecs_ref_get_id( const ecs_world_t *world, ecs_ref_t *ref, ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ref != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ref->entity != 0, ECS_INVALID_PARAMETER, NULL); ecs_check(ref->id != 0, ECS_INVALID_PARAMETER, NULL); ecs_check(ref->record != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(id == ref->id, ECS_INVALID_PARAMETER, NULL); ecs_record_t *r = ref->record; ecs_table_t *table = r->table; if (!table) { return NULL; } int32_t row = ECS_RECORD_TO_ROW(r->row); ecs_check(row < ecs_table_count(table), ECS_INTERNAL_ERROR, NULL); ecs_table_record_t *tr = ref->tr; if (!tr || tr->hdr.table != table) { tr = ref->tr = flecs_table_record_get(world, table, id); if (!tr) { return NULL; } ecs_assert(tr->hdr.table == r->table, ECS_INTERNAL_ERROR, NULL); } int32_t column = ecs_table_type_to_storage_index(table, tr->column); ecs_assert(column != -1, ECS_INTERNAL_ERROR, NULL); return flecs_get_component_w_index(table, column, row).ptr; error: return NULL; } void* ecs_emplace_id( ecs_world_t *world, ecs_entity_t entity, ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); ecs_check(!ecs_has_id(world, entity, id), ECS_INVALID_PARAMETER, "cannot emplace a component the entity already has"); ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_cmd(stage)) { return flecs_defer_set(world, stage, EcsOpEmplace, entity, id, 0, NULL, true); } ecs_record_t *r = flecs_entities_get(world, entity); flecs_add_id_w_record(world, entity, r, id, false /* Add without ctor */); void *ptr = flecs_get_component(world, r->table, ECS_RECORD_TO_ROW(r->row), id); ecs_check(ptr != NULL, ECS_INVALID_PARAMETER, NULL); flecs_defer_end(world, stage); return ptr; error: return NULL; } static void flecs_modified_id_if( ecs_world_t *world, ecs_entity_t entity, ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_modified(stage, entity, id)) { return; } ecs_record_t *r = flecs_entities_get(world, entity); ecs_table_t *table = r->table; if (!flecs_table_record_get(world, table, id)) { flecs_defer_end(world, stage); return; } ecs_type_t ids = { .array = &id, .count = 1 }; flecs_notify_on_set(world, table, ECS_RECORD_TO_ROW(r->row), 1, &ids, true); flecs_table_mark_dirty(world, table, id); flecs_defer_end(world, stage); error: return; } void ecs_modified_id( ecs_world_t *world, ecs_entity_t entity, ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_modified(stage, entity, id)) { return; } /* If the entity does not have the component, calling ecs_modified is * invalid. The assert needs to happen after the defer statement, as the * entity may not have the component when this function is called while * operations are being deferred. */ ecs_check(ecs_has_id(world, entity, id), ECS_INVALID_PARAMETER, NULL); ecs_record_t *r = flecs_entities_get(world, entity); ecs_table_t *table = r->table; ecs_type_t ids = { .array = &id, .count = 1 }; flecs_notify_on_set(world, table, ECS_RECORD_TO_ROW(r->row), 1, &ids, true); flecs_table_mark_dirty(world, table, id); flecs_defer_end(world, stage); error: return; } static void flecs_copy_ptr_w_id( ecs_world_t *world, ecs_stage_t *stage, ecs_entity_t entity, ecs_id_t id, size_t size, void *ptr) { if (flecs_defer_cmd(stage)) { flecs_defer_set(world, stage, EcsOpSet, entity, id, flecs_utosize(size), ptr, false); return; } ecs_record_t *r = flecs_entities_get(world, entity); flecs_component_ptr_t dst = flecs_get_mut(world, entity, id, r); const ecs_type_info_t *ti = dst.ti; ecs_check(dst.ptr != NULL, ECS_INVALID_PARAMETER, NULL); if (ptr) { ecs_copy_t copy = ti->hooks.copy; if (copy) { copy(dst.ptr, ptr, 1, ti); } else { ecs_os_memcpy(dst.ptr, ptr, flecs_utosize(size)); } } else { ecs_os_memset(dst.ptr, 0, size); } flecs_table_mark_dirty(world, r->table, id); ecs_table_t *table = r->table; if (table->flags & EcsTableHasOnSet || ti->hooks.on_set) { ecs_type_t ids = { .array = &id, .count = 1 }; flecs_notify_on_set( world, table, ECS_RECORD_TO_ROW(r->row), 1, &ids, true); } flecs_defer_end(world, stage); error: return; } static void flecs_move_ptr_w_id( ecs_world_t *world, ecs_stage_t *stage, ecs_entity_t entity, ecs_id_t id, size_t size, void *ptr, ecs_cmd_kind_t cmd_kind) { if (flecs_defer_cmd(stage)) { flecs_defer_set(world, stage, cmd_kind, entity, id, flecs_utosize(size), ptr, false); return; } ecs_record_t *r = flecs_entities_get(world, entity); flecs_component_ptr_t dst = flecs_get_mut(world, entity, id, r); ecs_check(dst.ptr != NULL, ECS_INVALID_PARAMETER, NULL); const ecs_type_info_t *ti = dst.ti; ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); ecs_move_t move; if (cmd_kind != EcsOpEmplace) { /* ctor will have happened by get_mut */ 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 == EcsOpSet) { ecs_table_t *table = r->table; if (table->flags & EcsTableHasOnSet || ti->hooks.on_set) { ecs_type_t ids = { .array = &id, .count = 1 }; flecs_notify_on_set( world, table, ECS_RECORD_TO_ROW(r->row), 1, &ids, true); } } flecs_defer_end(world, stage); error: return; } ecs_entity_t ecs_set_id( ecs_world_t *world, ecs_entity_t entity, ecs_id_t id, size_t size, const void *ptr) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(!entity || ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); if (!entity) { entity = ecs_new_id(world); ecs_entity_t scope = stage->scope; if (scope) { ecs_add_pair(world, entity, EcsChildOf, scope); } } /* Safe to cast away const: function won't modify if move arg is false */ flecs_copy_ptr_w_id(world, stage, entity, id, size, (void*)ptr); return entity; error: return 0; } void ecs_enable_id( ecs_world_t *world, ecs_entity_t entity, ecs_id_t id, bool enable) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); if (flecs_defer_enable(stage, entity, id, enable)) { return; } else { /* Operations invoked by enable/disable should not be deferred */ stage->defer --; } ecs_record_t *r = flecs_entities_get(world, entity); ecs_entity_t bs_id = id | ECS_TOGGLE; ecs_table_t *table = r->table; int32_t index = -1; if (table) { index = ecs_search(world, table, bs_id, 0); } if (index == -1) { ecs_add_id(world, entity, bs_id); ecs_enable_id(world, entity, id, enable); return; } index -= table->bs_offset; ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL); /* Data cannot be NULl, since entity is stored in the table */ ecs_bitset_t *bs = &table->data.bs_columns[index]; ecs_assert(bs != NULL, ECS_INTERNAL_ERROR, NULL); flecs_bitset_set(bs, ECS_RECORD_TO_ROW(r->row), enable); error: return; } bool ecs_is_enabled_id( const ecs_world_t *world, ecs_entity_t entity, ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_id_is_valid(world, id), ECS_INVALID_PARAMETER, NULL); /* Make sure we're not working with a stage */ world = ecs_get_world(world); ecs_record_t *r = flecs_entities_get(world, entity); ecs_table_t *table; if (!r || !(table = r->table)) { return false; } ecs_entity_t bs_id = id | ECS_TOGGLE; int32_t index = ecs_search(world, table, bs_id, 0); if (index == -1) { /* If table does not have TOGGLE column for component, component is * always enabled, if the entity has it */ return ecs_has_id(world, entity, id); } index -= table->bs_offset; ecs_assert(index >= 0, ECS_INTERNAL_ERROR, NULL); ecs_bitset_t *bs = &table->data.bs_columns[index]; return flecs_bitset_get(bs, ECS_RECORD_TO_ROW(r->row)); error: return false; } bool ecs_has_id( const ecs_world_t *world, ecs_entity_t entity, ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_check(id != 0, ECS_INVALID_PARAMETER, NULL); /* Make sure we're not working with a stage */ world = ecs_get_world(world); ecs_record_t *r = flecs_entities_get(world, entity); ecs_table_t *table; if (!r || !(table = r->table)) { return false; } ecs_id_record_t *idr = flecs_id_record_get(world, id); int32_t column; if (idr) { const ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); if (tr) { return true; } } if (!(table->flags & (EcsTableHasUnion|EcsTableHasIsA))) { return false; } ecs_table_record_t *tr; column = ecs_search_relation(world, table, 0, id, EcsIsA, 0, 0, 0, &tr); if (column == -1) { return false; } table = tr->hdr.table; if ((table->flags & EcsTableHasUnion) && ECS_HAS_ID_FLAG(id, PAIR) && ECS_PAIR_SECOND(id) != EcsWildcard) { if (ECS_PAIR_FIRST(table->type.array[column]) == EcsUnion) { ecs_switch_t *sw = &table->data.sw_columns[ column - table->sw_offset]; int32_t row = ECS_RECORD_TO_ROW(r->row); uint64_t value = flecs_switch_get(sw, row); return value == ECS_PAIR_SECOND(id); } } return true; error: return false; } ecs_entity_t ecs_get_target( const ecs_world_t *world, ecs_entity_t entity, ecs_entity_t rel, int32_t index) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_check(rel != 0, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); ecs_record_t *r = flecs_entities_get(world, entity); ecs_table_t *table; if (!r || !(table = r->table)) { goto not_found; } ecs_id_t wc = ecs_pair(rel, EcsWildcard); ecs_id_record_t *idr = flecs_id_record_get(world, wc); const ecs_table_record_t *tr = NULL; if (idr) { tr = flecs_id_record_get_table(idr, table); } if (!tr) { if (table->flags & EcsTableHasUnion) { wc = ecs_pair(EcsUnion, rel); tr = flecs_table_record_get(world, table, wc); if (tr) { ecs_switch_t *sw = &table->data.sw_columns[ tr->column - table->sw_offset]; int32_t row = ECS_RECORD_TO_ROW(r->row); return flecs_switch_get(sw, row); } } if (!idr || !(idr->flags & EcsIdDontInherit)) { goto look_in_base; } else { return 0; } } if (index >= tr->count) { index -= tr->count; goto look_in_base; } return ecs_pair_second(world, table->type.array[tr->column + index]); look_in_base: if (table->flags & EcsTableHasIsA) { const ecs_table_record_t *isa_tr = flecs_id_record_get_table( world->idr_isa_wildcard, table); ecs_assert(isa_tr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_id_t *ids = table->type.array; int32_t i = isa_tr->column, end = (i + isa_tr->count); for (; i < end; i ++) { ecs_id_t isa_pair = ids[i]; ecs_entity_t base = ecs_pair_second(world, isa_pair); ecs_assert(base != 0, ECS_INTERNAL_ERROR, NULL); ecs_entity_t t = ecs_get_target(world, base, rel, index); if (t) { return t; } } } not_found: error: return 0; } ecs_entity_t ecs_get_target_for_id( const ecs_world_t *world, ecs_entity_t entity, ecs_entity_t rel, ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); if (!id) { return ecs_get_target(world, entity, rel, 0); } world = ecs_get_world(world); ecs_table_t *table = ecs_get_table(world, entity); ecs_entity_t subject = 0; if (rel) { int32_t column = ecs_search_relation( world, table, 0, id, rel, 0, &subject, 0, 0); if (column == -1) { return 0; } } else { entity = 0; /* Don't return entity if id was not found */ if (table) { ecs_id_t *ids = table->type.array; int32_t i, count = table->type.count; for (i = 0; i < count; i ++) { ecs_id_t ent = ids[i]; if (ent & ECS_ID_FLAGS_MASK) { /* Skip ids with pairs, roles since 0 was provided for rel */ break; } if (ecs_has_id(world, ent, id)) { subject = ent; break; } } } } if (subject == 0) { return entity; } else { return subject; } error: return 0; } int32_t ecs_get_depth( const ecs_world_t *world, ecs_entity_t entity, ecs_entity_t rel) { ecs_check(ecs_is_valid(world, rel), ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_has_id(world, rel, EcsAcyclic), ECS_INVALID_PARAMETER, NULL); ecs_table_t *table = ecs_get_table(world, entity); if (table) { return ecs_table_get_depth(world, table, rel); } return 0; error: return -1; } static const char* flecs_get_identifier( const ecs_world_t *world, ecs_entity_t entity, ecs_entity_t tag) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); const EcsIdentifier *ptr = ecs_get_pair( world, entity, EcsIdentifier, tag); if (ptr) { return ptr->value; } else { return NULL; } error: return NULL; } const char* ecs_get_name( const ecs_world_t *world, ecs_entity_t entity) { return flecs_get_identifier(world, entity, EcsName); } const char* ecs_get_symbol( const ecs_world_t *world, ecs_entity_t entity) { world = ecs_get_world(world); if (ecs_owns_pair(world, entity, ecs_id(EcsIdentifier), EcsSymbol)) { return flecs_get_identifier(world, entity, EcsSymbol); } else { return NULL; } } static ecs_entity_t flecs_set_identifier( ecs_world_t *world, ecs_stage_t *stage, ecs_entity_t entity, ecs_entity_t tag, const char *name) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(entity != 0 || name != NULL, ECS_INVALID_PARAMETER, NULL); if (!entity) { entity = ecs_new_id(world); } if (!name) { ecs_remove_pair(world, entity, ecs_id(EcsIdentifier), tag); return entity; } EcsIdentifier *ptr = ecs_get_mut_pair(world, entity, EcsIdentifier, tag); ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); if (tag == EcsName) { /* Insert command after get_mut, 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_init(world, &(ecs_entity_desc_t){ .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) { return ecs_pair(relationship, target); } bool ecs_is_valid( const ecs_world_t *world, ecs_entity_t entity) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); /* 0 is not a valid entity id */ if (!entity) { return false; } /* Make sure we're not working with a stage */ world = ecs_get_world(world); /* Entity identifiers should not contain flag bits */ if (entity & ECS_ID_FLAGS_MASK) { return false; } /* Entities should not contain data in dead zone bits */ if (entity & ~0xFF00FFFFFFFFFFFF) { return false; } if (entity & ECS_ID_FLAG_BIT) { return ecs_entity_t_lo(entity) != 0; } /* If entity doesn't exist in the world, the id is valid as long as the * generation is 0. Using a non-existing id with a non-zero generation * requires calling ecs_ensure first. */ if (!ecs_exists(world, entity)) { return ECS_GENERATION(entity) == 0; } /* If id exists, it must be alive (the generation count must match) */ return ecs_is_alive(world, entity); error: return false; } ecs_id_t ecs_strip_generation( ecs_entity_t e) { /* If this is not a pair, erase the generation bits */ if (!(e & ECS_ID_FLAGS_MASK)) { e &= ~ECS_GENERATION_MASK; } return e; } bool ecs_is_alive( const ecs_world_t *world, ecs_entity_t entity) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); return flecs_entities_is_alive(world, entity); error: return false; } ecs_entity_t ecs_get_alive( const ecs_world_t *world, ecs_entity_t entity) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); if (!entity) { return 0; } if (ecs_is_alive(world, entity)) { return entity; } /* Make sure id does not have generation. This guards against accidentally * "upcasting" a not alive identifier to a alive one. */ if ((uint32_t)entity != entity) { return 0; } /* Make sure we're not working with a stage */ world = ecs_get_world(world); ecs_entity_t current = flecs_entities_get_current(world, entity); if (!current || !ecs_is_alive(world, current)) { return 0; } return current; error: return 0; } void ecs_ensure( ecs_world_t *world, ecs_entity_t entity) { ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); /* Const cast is safe, function checks for threading */ world = (ecs_world_t*)ecs_get_world(world); /* The entity index can be mutated while in staged/readonly mode, as long as * the world is not multithreaded. */ ecs_assert(!(world->flags & EcsWorldMultiThreaded), ECS_INVALID_OPERATION, NULL); /* Check if a version of the provided id is alive */ ecs_entity_t any = ecs_get_alive(world, ecs_strip_generation(entity)); if (any == entity) { /* If alive and equal to the argument, there's nothing left to do */ return; } /* If the id is currently alive but did not match the argument, fail */ ecs_check(!any, ECS_INVALID_PARAMETER, NULL); /* Set generation if not alive. The sparse set checks if the provided * id matches its own generation which is necessary for alive ids. This * check would cause ecs_ensure to fail if the generation of the 'entity' * argument doesn't match with its generation. * * While this could've been addressed in the sparse set, this is a rare * scenario that can only be triggered by ecs_ensure. Implementing it here * allows the sparse set to not do this check, which is more efficient. */ flecs_entities_set_generation(world, entity); /* Ensure id exists. The underlying datastructure will verify that the * generation count matches the provided one. */ flecs_entities_ensure(world, entity); error: return; } void ecs_ensure_id( ecs_world_t *world, ecs_id_t id) { if (ECS_HAS_ID_FLAG(id, PAIR)) { ecs_entity_t r = ECS_PAIR_FIRST(id); ecs_entity_t o = ECS_PAIR_SECOND(id); ecs_check(r != 0, ECS_INVALID_PARAMETER, NULL); ecs_check(o != 0, ECS_INVALID_PARAMETER, NULL); if (ecs_get_alive(world, r) == 0) { ecs_assert(!ecs_exists(world, r), ECS_INVALID_PARAMETER, "first element of pair is not alive"); ecs_ensure(world, r); } if (ecs_get_alive(world, o) == 0) { ecs_assert(!ecs_exists(world, o), ECS_INVALID_PARAMETER, "second element of pair is not alive"); ecs_ensure(world, o); } } else { ecs_ensure(world, id & ECS_COMPONENT_MASK); } error: return; } bool ecs_exists( const ecs_world_t *world, ecs_entity_t entity) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(entity != 0, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); return flecs_entities_exists(world, entity); error: return false; } ecs_table_t* ecs_get_table( const ecs_world_t *world, ecs_entity_t entity) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); ecs_record_t *record = flecs_entities_get(world, entity); if (record) { return record->table; } error: return NULL; } const ecs_type_t* ecs_get_type( const ecs_world_t *world, ecs_entity_t entity) { ecs_table_t *table = ecs_get_table(world, entity); if (table) { return &table->type; } return NULL; } const ecs_type_info_t* ecs_get_type_info( const ecs_world_t *world, ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); ecs_id_record_t *idr = flecs_id_record_get(world, id); if (!idr && ECS_IS_PAIR(id)) { idr = flecs_id_record_get(world, ecs_pair(ECS_PAIR_FIRST(id), EcsWildcard)); if (!idr || !idr->type_info) { idr = NULL; } if (!idr) { ecs_entity_t first = ecs_pair_first(world, id); if (!first || !ecs_has_id(world, first, EcsTag)) { idr = flecs_id_record_get(world, ecs_pair(EcsWildcard, ECS_PAIR_SECOND(id))); if (!idr || !idr->type_info) { idr = NULL; } } } } if (idr) { return idr->type_info; } else if (!(id & ECS_ID_FLAGS_MASK)) { return flecs_type_info_get(world, id); } error: return NULL; } ecs_entity_t ecs_get_typeid( const ecs_world_t *world, ecs_id_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); const ecs_type_info_t *ti = ecs_get_type_info(world, id); if (ti) { return ti->component; } error: return 0; } bool ecs_id_is_tag( const ecs_world_t *world, ecs_id_t id) { if (ecs_id_is_wildcard(id)) { /* If id is a wildcard, we can't tell if it's a tag or not, except * when the relationship part of a pair has the Tag property */ if (ECS_HAS_ID_FLAG(id, PAIR)) { if (ECS_PAIR_FIRST(id) != EcsWildcard) { ecs_entity_t rel = ecs_pair_first(world, id); if (ecs_is_valid(world, rel)) { if (ecs_has_id(world, rel, EcsTag)) { return true; } } else { /* During bootstrap it's possible that not all ids are valid * yet. Using ecs_get_typeid will ensure correct values are * returned for only those components initialized during * bootstrap, while still asserting if another invalid id * is provided. */ if (ecs_get_typeid(world, id) == 0) { return true; } } } else { /* If relationship is wildcard id is not guaranteed to be a tag */ } } } else { if (ecs_get_typeid(world, id) == 0) { return true; } } return false; } bool ecs_id_is_union( const ecs_world_t *world, ecs_id_t id) { if (!ECS_IS_PAIR(id)) { return false; } else if (ECS_PAIR_FIRST(id) == EcsUnion) { return true; } else { ecs_entity_t first = ecs_pair_first(world, id); if (ecs_has_id(world, first, EcsUnion)) { return true; } } return false; } int32_t ecs_count_id( const ecs_world_t *world, ecs_entity_t id) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); if (!id) { return 0; } int32_t count = 0; ecs_iter_t it = ecs_term_iter(world, &(ecs_term_t) { .id = id, .src.flags = EcsSelf }); it.flags |= EcsIterNoData; it.flags |= EcsIterEvalTables; while (ecs_term_next(&it)) { count += it.count; } return count; error: return 0; } void ecs_enable( ecs_world_t *world, ecs_entity_t entity, bool enabled) { if (ecs_has_id(world, entity, EcsPrefab)) { /* If entity is a type, enable/disable all entities in the type */ const ecs_type_t *type = ecs_get_type(world, entity); ecs_assert(type != NULL, ECS_INTERNAL_ERROR, NULL); ecs_id_t *ids = type->array; int32_t i, count = type->count; for (i = 0; i < count; i ++) { ecs_id_t id = ids[i]; if (ecs_id_get_flags(world, id) & EcsIdDontInherit) { continue; } ecs_enable(world, id, enabled); } } else { if (enabled) { ecs_remove_id(world, entity, EcsDisabled); } else { ecs_add_id(world, entity, EcsDisabled); } } } bool ecs_defer_begin( ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); return flecs_defer_begin(world, stage); error: return false; } bool ecs_defer_end( ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); return flecs_defer_end(world, stage); error: return false; } void ecs_defer_suspend( ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_is_deferred(world), ECS_INVALID_OPERATION, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); ecs_check(stage->defer > 0, ECS_INVALID_OPERATION, NULL); stage->defer = -stage->defer; error: return; } void ecs_defer_resume( ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_stage_t *stage = flecs_stage_from_world(&world); ecs_check(stage->defer < 0, ECS_INVALID_OPERATION, NULL); stage->defer = -stage->defer; error: return; } const char* ecs_id_flag_str( ecs_entity_t entity) { if (ECS_HAS_ID_FLAG(entity, PAIR)) { return "PAIR"; } else if (ECS_HAS_ID_FLAG(entity, TOGGLE)) { return "TOGGLE"; } else if (ECS_HAS_ID_FLAG(entity, AND)) { return "AND"; } else if (ECS_HAS_ID_FLAG(entity, OVERRIDE)) { return "OVERRIDE"; } else { return "UNKNOWN"; } } void ecs_id_str_buf( const ecs_world_t *world, ecs_id_t id, ecs_strbuf_t *buf) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); if (ECS_HAS_ID_FLAG(id, TOGGLE)) { ecs_strbuf_appendstr(buf, ecs_id_flag_str(ECS_TOGGLE)); ecs_strbuf_appendch(buf, '|'); } if (ECS_HAS_ID_FLAG(id, OVERRIDE)) { ecs_strbuf_appendstr(buf, ecs_id_flag_str(ECS_OVERRIDE)); ecs_strbuf_appendch(buf, '|'); } if (ECS_HAS_ID_FLAG(id, AND)) { ecs_strbuf_appendstr(buf, ecs_id_flag_str(ECS_AND)); ecs_strbuf_appendch(buf, '|'); } if (ECS_HAS_ID_FLAG(id, PAIR)) { ecs_entity_t rel = ECS_PAIR_FIRST(id); ecs_entity_t obj = ECS_PAIR_SECOND(id); ecs_entity_t e; if ((e = ecs_get_alive(world, rel))) { rel = e; } if ((e = ecs_get_alive(world, obj))) { obj = e; } ecs_strbuf_appendch(buf, '('); ecs_get_path_w_sep_buf(world, 0, rel, NULL, NULL, buf); ecs_strbuf_appendch(buf, ','); ecs_get_path_w_sep_buf(world, 0, obj, NULL, NULL, buf); ecs_strbuf_appendch(buf, ')'); } else { ecs_entity_t e = id & ECS_COMPONENT_MASK; ecs_get_path_w_sep_buf(world, 0, e, NULL, NULL, buf); } error: return; } char* ecs_id_str( const ecs_world_t *world, ecs_id_t id) { ecs_strbuf_t buf = ECS_STRBUF_INIT; ecs_id_str_buf(world, id, &buf); return ecs_strbuf_get(&buf); } static void ecs_type_str_buf( const ecs_world_t *world, const ecs_type_t *type, ecs_strbuf_t *buf) { ecs_entity_t *ids = type->array; int32_t i, count = type->count; for (i = 0; i < count; i ++) { ecs_entity_t id = ids[i]; if (i) { ecs_strbuf_appendch(buf, ','); ecs_strbuf_appendch(buf, ' '); } if (id == 1) { ecs_strbuf_appendlit(buf, "Component"); } else { ecs_id_str_buf(world, id, buf); } } } char* ecs_type_str( const ecs_world_t *world, const ecs_type_t *type) { if (!type) { return ecs_os_strdup(""); } ecs_strbuf_t buf = ECS_STRBUF_INIT; ecs_type_str_buf(world, type, &buf); return ecs_strbuf_get(&buf); } char* ecs_table_str( const ecs_world_t *world, const ecs_table_t *table) { if (table) { return ecs_type_str(world, &table->type); } else { return NULL; } } char* ecs_entity_str( const ecs_world_t *world, ecs_entity_t entity) { ecs_strbuf_t buf = ECS_STRBUF_INIT; ecs_check(ecs_is_alive(world, entity), ECS_INVALID_PARAMETER, NULL); ecs_get_path_w_sep_buf(world, 0, entity, 0, "", &buf); ecs_strbuf_appendlit(&buf, " ["); const ecs_type_t *type = ecs_get_type(world, entity); if (type) { ecs_type_str_buf(world, type, &buf); } ecs_strbuf_appendch(&buf, ']'); return ecs_strbuf_get(&buf); error: return NULL; } static void flecs_flush_bulk_new( ecs_world_t *world, ecs_cmd_t *cmd) { ecs_entity_t *entities = cmd->is._n.entities; if (cmd->id) { int i, count = cmd->is._n.count; for (i = 0; i < count; i ++) { flecs_entities_ensure(world, entities[i]); flecs_add_id(world, entities[i], cmd->id); } } ecs_os_free(entities); } static void flecs_dtor_value( ecs_world_t *world, ecs_id_t id, void *value, int32_t count) { const ecs_type_info_t *ti = ecs_get_type_info(world, id); ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); ecs_xtor_t dtor = ti->hooks.dtor; if (dtor) { ecs_size_t size = ti->size; void *ptr; int i; for (i = 0, ptr = value; i < count; i ++, ptr = ECS_OFFSET(ptr, size)) { dtor(ptr, 1, ti); } } } static void flecs_discard_cmd( ecs_world_t *world, ecs_cmd_t *cmd) { if (cmd->kind != EcsOpBulkNew) { void *value = cmd->is._1.value; if (value) { flecs_dtor_value(world, cmd->id, value, 1); flecs_stack_free(value, cmd->is._1.size); } } else { ecs_os_free(cmd->is._n.entities); } } static bool flecs_remove_invalid( ecs_world_t *world, ecs_id_t id, ecs_id_t *id_out) { if (ECS_HAS_ID_FLAG(id, PAIR)) { ecs_entity_t rel = ECS_PAIR_FIRST(id); if (!flecs_entities_is_valid(world, rel)) { /* After relationship is deleted we can no longer see what its * delete action was, so pretend this never happened */ *id_out = 0; return true; } else { ecs_entity_t obj = ECS_PAIR_SECOND(id); if (!flecs_entities_is_valid(world, obj)) { /* Check the relationship's policy for deleted objects */ ecs_id_record_t *idr = flecs_id_record_get(world, ecs_pair(rel, EcsWildcard)); if (idr) { ecs_entity_t action = ECS_ID_ON_DELETE_OBJECT(idr->flags); if (action == EcsDelete) { /* Entity should be deleted, don't bother checking * other ids */ return false; } else if (action == EcsPanic) { /* If policy is throw this object should not have * been deleted */ flecs_throw_invalid_delete(world, id); } else { *id_out = 0; return true; } } else { *id_out = 0; return true; } } } } else { id &= ECS_COMPONENT_MASK; if (!flecs_entities_is_valid(world, id)) { /* After relationship is deleted we can no longer see what its * delete action was, so pretend this never happened */ *id_out = 0; return true; } } return true; } static void flecs_cmd_batch_for_entity( ecs_world_t *world, ecs_table_diff_builder_t *diff, ecs_entity_t entity, ecs_cmd_t *cmds, int32_t start) { ecs_record_t *r = flecs_entities_get(world, entity); ecs_table_t *table = NULL; if (r) { table = r->table; } world->info.cmd.batched_entity_count ++; ecs_table_t *start_table = table; ecs_cmd_t *cmd; int32_t next_for_entity; ecs_table_diff_t table_diff; /* Keep track of diff for observers/hooks */ int32_t cur = start; ecs_id_t id; bool has_set = false; do { cmd = &cmds[cur]; id = cmd->id; next_for_entity = cmd->next_for_entity; if (next_for_entity < 0) { /* First command for an entity has a negative index, flip sign */ next_for_entity *= -1; } /* Check if added id is still valid (like is the parent of a ChildOf * pair still alive), if not run cleanup actions for entity */ if (id) { if (flecs_remove_invalid(world, id, &id)) { if (!id) { /* Entity should remain alive but id should not be added */ cmd->kind = EcsOpSkip; continue; } /* Entity should remain alive and id is still valid */ } else { /* Id was no longer valid and had a Delete policy */ cmd->kind = EcsOpSkip; ecs_delete(world, entity); flecs_table_diff_builder_clear(diff); return; } } ecs_cmd_kind_t kind = cmd->kind; switch(kind) { case EcsOpAddModified: /* Add is batched, but keep Modified */ cmd->kind = EcsOpModified; /* fallthrough */ case EcsOpAdd: table = flecs_find_table_add(world, table, id, diff); world->info.cmd.batched_command_count ++; break; case EcsOpModified: { if (start_table) { /* If a modified was inserted for an existing component, the value * of the component could have been changed. If this is the case, * call on_set hooks before the OnAdd/OnRemove observers are invoked * when moving the entity to a different table. * This ensures that if OnAdd/OnRemove observers access the modified * component value, the on_set hook has had the opportunity to * run first to set any computed values of the component. */ int32_t row = ECS_RECORD_TO_ROW(r->row); flecs_component_ptr_t ptr = flecs_get_component_ptr( world, start_table, row, cmd->id); if (ptr.ptr) { ecs_type_info_t *ti = ptr.ti; ecs_iter_action_t on_set; if ((on_set = ti->hooks.on_set)) { flecs_invoke_hook(world, start_table, 1, row, &entity, ptr.ptr, cmd->id, ptr.ti, EcsOnSet, on_set); } } } break; } case EcsOpSet: case EcsOpMut: table = flecs_find_table_add(world, table, id, diff); world->info.cmd.batched_command_count ++; has_set = true; break; case EcsOpEmplace: /* Don't add for emplace, as this requires a special call to ensure * the constructor is not invoked for the component */ break; case EcsOpRemove: table = flecs_find_table_remove(world, table, id, diff); world->info.cmd.batched_command_count ++; break; case EcsOpClear: table = &world->store.root; world->info.cmd.batched_command_count ++; break; default: break; } /* Add, remove and clear operations can be skipped since they have no * side effects besides adding/removing components */ if (kind == EcsOpAdd || kind == EcsOpRemove || kind == EcsOpClear) { cmd->kind = EcsOpSkip; } } while ((cur = next_for_entity)); /* Move entity to destination table in single operation */ flecs_table_diff_build_noalloc(diff, &table_diff); flecs_defer_begin(world, &world->stages[0]); flecs_commit(world, entity, r, table, &table_diff, true, 0); flecs_defer_end(world, &world->stages[0]); flecs_table_diff_builder_clear(diff); /* If ids were both removed and set, check if there are ids that were both * set and removed. If so, skip the set command so that the id won't get * re-added */ if (has_set && table_diff.removed.count) { cur = start; do { cmd = &cmds[cur]; next_for_entity = cmd->next_for_entity; if (next_for_entity < 0) { next_for_entity *= -1; } switch(cmd->kind) { case EcsOpSet: case EcsOpMut: { ecs_id_record_t *idr = cmd->idr; if (!idr) { idr = flecs_id_record_get(world, cmd->id); } if (!flecs_id_record_get_table(idr, table)) { /* Component was deleted */ cmd->kind = EcsOpSkip; world->info.cmd.batched_command_count --; world->info.cmd.discard_count ++; } break; } default: break; } } while ((cur = next_for_entity)); } } /* Leave safe section. Run all deferred commands. */ bool flecs_defer_end( ecs_world_t *world, ecs_stage_t *stage) { ecs_poly_assert(world, ecs_world_t); ecs_poly_assert(stage, ecs_stage_t); if (stage->defer < 0) { /* Defer suspending makes it possible to do operations on the storage * without flushing the commands in the queue */ return false; } ecs_assert(stage->defer > 0, ECS_INTERNAL_ERROR, NULL); if (!--stage->defer) { /* Test whether we're flushing to another queue or whether we're * flushing to the storage */ bool merge_to_world = false; if (ecs_poly_is(world, ecs_world_t)) { merge_to_world = world->stages[0].defer == 0; } ecs_stage_t *dst_stage = flecs_stage_from_world(&world); if (ecs_vec_count(&stage->commands)) { ecs_vec_t commands = stage->commands; stage->commands.array = NULL; stage->commands.count = 0; stage->commands.size = 0; ecs_cmd_t *cmds = ecs_vec_first(&commands); int32_t i, count = ecs_vec_count(&commands); ecs_vec_init_t(NULL, &stage->commands, ecs_cmd_t, 0); ecs_stack_t stack = stage->defer_stack; flecs_stack_init(&stage->defer_stack); ecs_table_diff_builder_t diff; flecs_table_diff_builder_init(world, &diff); flecs_sparse_clear(&stage->cmd_entries); for (i = 0; i < count; i ++) { ecs_cmd_t *cmd = &cmds[i]; ecs_entity_t e = cmd->entity; bool is_alive = flecs_entities_is_valid(world, e); /* A negative index indicates the first command for an entity */ if (merge_to_world && (cmd->next_for_entity < 0)) { /* Batch commands for entity to limit archetype moves */ if (is_alive) { flecs_cmd_batch_for_entity(world, &diff, e, cmds, i); } else { world->info.cmd.discard_count ++; } } /* If entity is no longer alive, this could be because the queue * contained both a delete and a subsequent add/remove/set which * should be ignored. */ ecs_cmd_kind_t kind = cmd->kind; if ((kind != EcsOpPath) && ((kind == EcsOpSkip) || (e && !is_alive))) { world->info.cmd.discard_count ++; flecs_discard_cmd(world, cmd); continue; } ecs_id_t id = cmd->id; switch(kind) { case EcsOpAdd: ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); if (flecs_remove_invalid(world, id, &id)) { if (id) { world->info.cmd.add_count ++; flecs_add_id(world, e, id); } else { world->info.cmd.discard_count ++; } } else { world->info.cmd.discard_count ++; ecs_delete(world, e); } break; case EcsOpRemove: flecs_remove_id(world, e, id); world->info.cmd.remove_count ++; break; case EcsOpClone: ecs_clone(world, e, id, cmd->is._1.clone_value); break; case EcsOpSet: flecs_move_ptr_w_id(world, dst_stage, e, cmd->id, flecs_itosize(cmd->is._1.size), cmd->is._1.value, kind); world->info.cmd.set_count ++; break; case EcsOpEmplace: if (merge_to_world) { ecs_emplace_id(world, e, id); } flecs_move_ptr_w_id(world, dst_stage, e, cmd->id, flecs_itosize(cmd->is._1.size), cmd->is._1.value, kind); world->info.cmd.get_mut_count ++; break; case EcsOpMut: flecs_move_ptr_w_id(world, dst_stage, e, cmd->id, flecs_itosize(cmd->is._1.size), cmd->is._1.value, kind); world->info.cmd.get_mut_count ++; break; case EcsOpModified: flecs_modified_id_if(world, e, id); world->info.cmd.modified_count ++; break; case EcsOpAddModified: flecs_add_id(world, e, id); flecs_modified_id_if(world, e, id); world->info.cmd.add_count ++; world->info.cmd.modified_count ++; break; case EcsOpDelete: { ecs_delete(world, e); world->info.cmd.delete_count ++; break; } case EcsOpClear: ecs_clear(world, e); world->info.cmd.clear_count ++; break; case EcsOpOnDeleteAction: ecs_defer_begin(world); flecs_on_delete(world, id, e, false); ecs_defer_end(world); world->info.cmd.other_count ++; break; case EcsOpEnable: ecs_enable_id(world, e, id, true); world->info.cmd.other_count ++; break; case EcsOpDisable: ecs_enable_id(world, e, id, false); world->info.cmd.other_count ++; break; case EcsOpBulkNew: flecs_flush_bulk_new(world, cmd); world->info.cmd.other_count ++; continue; case EcsOpPath: ecs_ensure(world, e); if (cmd->id) { ecs_add_pair(world, e, EcsChildOf, cmd->id); } ecs_set_name(world, e, cmd->is._1.value); ecs_os_free(cmd->is._1.value); cmd->is._1.value = NULL; break; case EcsOpSkip: break; } if (cmd->is._1.value) { flecs_stack_free(cmd->is._1.value, cmd->is._1.size); } } ecs_vec_fini_t(&stage->allocator, &stage->commands, ecs_cmd_t); /* Restore defer queue */ ecs_vec_clear(&commands); stage->commands = commands; /* Restore stack */ flecs_stack_fini(&stage->defer_stack); stage->defer_stack = stack; flecs_stack_reset(&stage->defer_stack); flecs_table_diff_builder_fini(world, &diff); } return true; } return false; } /* Delete operations from queue without executing them. */ bool flecs_defer_purge( ecs_world_t *world, ecs_stage_t *stage) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); if (!--stage->defer) { ecs_vec_t commands = stage->commands; if (ecs_vec_count(&commands)) { ecs_cmd_t *cmds = ecs_vec_first(&commands); int32_t i, count = ecs_vec_count(&commands); for (i = 0; i < count; i ++) { flecs_discard_cmd(world, &cmds[i]); } ecs_vec_fini_t(&stage->allocator, &stage->commands, ecs_cmd_t); ecs_vec_clear(&commands); flecs_stack_reset(&stage->defer_stack); flecs_sparse_clear(&stage->cmd_entries); } return true; } error: return false; } /** * @file stage.c * @brief Staging implementation. * * A stage is an object that can be used to temporarily store mutations to a * world while a world is in readonly mode. ECS operations that are invoked on * a stage are stored in a command buffer, which is flushed during sync points, * or manually by the user. * * Stages contain additional state to enable other API functionality without * having to mutate the world, such as setting the current scope, and allocators * that are local to a stage. * * In a multi threaded application, each thread has its own stage which allows * threads to insert mutations without having to lock administration. */ static ecs_cmd_t* flecs_cmd_alloc( ecs_stage_t *stage) { ecs_cmd_t *cmd = ecs_vec_append_t(&stage->allocator, &stage->commands, ecs_cmd_t); ecs_os_zeromem(cmd); return cmd; } static ecs_cmd_t* flecs_cmd_new( ecs_stage_t *stage, ecs_entity_t e, bool is_delete, bool can_batch) { if (e) { ecs_vec_t *cmds = &stage->commands; ecs_cmd_entry_t *entry = flecs_sparse_try_t( &stage->cmd_entries, ecs_cmd_entry_t, e); int32_t cur = ecs_vec_count(cmds); if (entry) { int32_t last = entry->last; if (entry->last == -1) { /* Entity was deleted, don't insert command */ return NULL; } if (can_batch) { ecs_cmd_t *arr = ecs_vec_first_t(cmds, ecs_cmd_t); ecs_assert(arr[last].entity == e, ECS_INTERNAL_ERROR, NULL); ecs_cmd_t *last_op = &arr[last]; last_op->next_for_entity = cur; if (last == entry->first) { /* Flip sign bit so flush logic can tell which command * is the first for an entity */ last_op->next_for_entity *= -1; } } } else if (can_batch || is_delete) { entry = flecs_sparse_ensure_t(&stage->cmd_entries, ecs_cmd_entry_t, e); entry->first = cur; } if (can_batch) { entry->last = cur; } if (is_delete) { /* Prevent insertion of more commands for entity */ entry->last = -1; } } return flecs_cmd_alloc(stage); } static void flecs_stages_merge( ecs_world_t *world, bool force_merge) { bool is_stage = ecs_poly_is(world, ecs_stage_t); ecs_stage_t *stage = flecs_stage_from_world(&world); bool measure_frame_time = ECS_BIT_IS_SET(world->flags, EcsWorldMeasureFrameTime); ecs_time_t t_start = {0}; if (measure_frame_time) { ecs_os_get_time(&t_start); } ecs_dbg_3("#[magenta]merge"); ecs_log_push_3(); if (is_stage) { /* Check for consistency if force_merge is enabled. In practice this * function will never get called with force_merge disabled for just * a single stage. */ if (force_merge || stage->auto_merge) { ecs_assert(stage->defer == 1, ECS_INVALID_OPERATION, "mismatching defer_begin/defer_end detected"); flecs_defer_end(world, stage); } } else { /* Merge stages. Only merge if the stage has auto_merging turned on, or * if this is a forced merge (like when ecs_merge is called) */ int32_t i, count = ecs_get_stage_count(world); for (i = 0; i < count; i ++) { ecs_stage_t *s = (ecs_stage_t*)ecs_get_stage(world, i); ecs_poly_assert(s, ecs_stage_t); if (force_merge || s->auto_merge) { flecs_defer_end(world, s); } } } flecs_eval_component_monitors(world); if (measure_frame_time) { world->info.merge_time_total += (ecs_ftime_t)ecs_time_measure(&t_start); } world->info.merge_count_total ++; /* If stage is asynchronous, deferring is always enabled */ if (stage->async) { flecs_defer_begin(world, stage); } ecs_log_pop_3(); } static void flecs_stage_auto_merge( ecs_world_t *world) { flecs_stages_merge(world, false); } static void flecs_stage_manual_merge( ecs_world_t *world) { flecs_stages_merge(world, true); } bool flecs_defer_begin( ecs_world_t *world, ecs_stage_t *stage) { ecs_poly_assert(world, ecs_world_t); ecs_poly_assert(stage, ecs_stage_t); (void)world; if (stage->defer < 0) return false; return (++ stage->defer) == 1; } bool flecs_defer_cmd( ecs_stage_t *stage) { if (stage->defer) { return (stage->defer > 0); } stage->defer ++; return false; } bool flecs_defer_modified( ecs_stage_t *stage, ecs_entity_t entity, ecs_id_t id) { if (flecs_defer_cmd(stage)) { ecs_cmd_t *cmd = flecs_cmd_new(stage, entity, false, true); if (cmd) { cmd->kind = EcsOpModified; cmd->id = id; cmd->entity = entity; } return true; } return false; } bool flecs_defer_clone( ecs_stage_t *stage, ecs_entity_t entity, ecs_entity_t src, bool clone_value) { if (flecs_defer_cmd(stage)) { ecs_cmd_t *cmd = flecs_cmd_new(stage, entity, false, false); if (cmd) { cmd->kind = EcsOpClone; cmd->id = src; cmd->entity = entity; cmd->is._1.clone_value = clone_value; } return true; } return false; } bool flecs_defer_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, entity, false, false); if (cmd) { cmd->kind = EcsOpPath; 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, entity, true, false); if (cmd) { cmd->kind = EcsOpDelete; cmd->entity = entity; } return true; } return false; } bool flecs_defer_clear( ecs_stage_t *stage, ecs_entity_t entity) { if (flecs_defer_cmd(stage)) { ecs_cmd_t *cmd = flecs_cmd_new(stage, entity, false, true); if (cmd) { cmd->kind = EcsOpClear; cmd->entity = entity; } return true; } return false; } bool flecs_defer_on_delete_action( ecs_stage_t *stage, ecs_id_t id, ecs_entity_t action) { if (flecs_defer_cmd(stage)) { ecs_cmd_t *cmd = flecs_cmd_alloc(stage); cmd->kind = EcsOpOnDeleteAction; cmd->id = id; cmd->entity = action; return true; } return false; } bool flecs_defer_enable( ecs_stage_t *stage, ecs_entity_t entity, ecs_id_t id, bool enable) { if (flecs_defer_cmd(stage)) { ecs_cmd_t *cmd = flecs_cmd_new(stage, entity, false, false); if (cmd) { cmd->kind = enable ? EcsOpEnable : EcsOpDisable; cmd->entity = entity; cmd->id = id; } return true; } return false; } bool flecs_defer_bulk_new( ecs_world_t *world, ecs_stage_t *stage, int32_t count, ecs_id_t id, const ecs_entity_t **ids_out) { if (flecs_defer_cmd(stage)) { ecs_entity_t *ids = ecs_os_malloc(count * ECS_SIZEOF(ecs_entity_t)); /* Use ecs_new_id as this is thread safe */ int i; for (i = 0; i < count; i ++) { ids[i] = ecs_new_id(world); } *ids_out = ids; /* Store data in op */ ecs_cmd_t *cmd = flecs_cmd_alloc(stage); if (cmd) { cmd->kind = EcsOpBulkNew; cmd->id = id; cmd->is._n.entities = ids; cmd->is._n.count = count; } return true; } return false; } bool flecs_defer_add( ecs_stage_t *stage, ecs_entity_t entity, ecs_id_t id) { if (flecs_defer_cmd(stage)) { ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); ecs_cmd_t *cmd = flecs_cmd_new(stage, entity, false, true); if (cmd) { cmd->kind = EcsOpAdd; cmd->id = id; cmd->entity = entity; } return true; } return false; } bool flecs_defer_remove( ecs_stage_t *stage, ecs_entity_t entity, ecs_id_t id) { if (flecs_defer_cmd(stage)) { ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL); ecs_cmd_t *cmd = flecs_cmd_new(stage, entity, false, true); if (cmd) { cmd->kind = EcsOpRemove; cmd->id = id; cmd->entity = entity; } return true; } return false; } void* flecs_defer_set( ecs_world_t *world, ecs_stage_t *stage, ecs_cmd_kind_t cmd_kind, ecs_entity_t entity, ecs_id_t id, ecs_size_t size, void *value, bool need_value) { ecs_cmd_t *cmd = flecs_cmd_new(stage, entity, false, true); if (!cmd) { if (need_value) { /* Entity is deleted by a previous command, but we still need to * return a temporary storage to the application. */ cmd_kind = EcsOpSkip; } else { /* No value needs to be returned, we can drop the command */ return NULL; } } /* Find type info for id */ const ecs_type_info_t *ti = NULL; ecs_id_record_t *idr = flecs_id_record_get(world, id); if (!idr) { /* If idr doesn't exist yet, create it but only if the * application is not multithreaded. */ if (stage->async || (world->flags & EcsWorldMultiThreaded)) { ti = ecs_get_type_info(world, id); ecs_assert(ti != NULL, ECS_INVALID_PARAMETER, NULL); } else { /* When not in multi threaded mode, it's safe to find or * create the id record. */ idr = flecs_id_record_ensure(world, id); ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); /* Get type_info from id record. We could have called * ecs_get_type_info directly, but since this function can be * expensive for pairs, creating the id record ensures we can * find the type_info quickly for subsequent operations. */ ti = idr->type_info; } } else { ti = idr->type_info; } /* If the id isn't associated with a type, we can't set anything */ ecs_check(ti != NULL, ECS_INVALID_PARAMETER, NULL); /* Make sure the size of the value equals the type size */ ecs_assert(!size || size == ti->size, ECS_INVALID_PARAMETER, NULL); size = ti->size; /* Find existing component. Make sure it's owned, so that we won't use the * component of a prefab. */ void *existing = NULL; ecs_table_t *table = NULL, *storage_table; if (idr) { /* Entity can only have existing component if id record exists */ ecs_record_t *r = flecs_entities_get(world, entity); table = r->table; if (r && table && (storage_table = table->storage_table)) { const ecs_table_record_t *tr = flecs_id_record_get_table( idr, storage_table); if (tr) { /* Entity has the component */ ecs_vec_t *column = &table->data.columns[tr->column]; existing = ecs_vec_get(column, size, ECS_RECORD_TO_ROW(r->row)); } } } /* Get existing value from storage */ void *cmd_value = existing; bool emplace = cmd_kind == EcsOpEmplace; /* If the component does not yet exist, create a temporary value. This is * necessary so we can store a component value in the deferred command, * without adding the component to the entity which is not allowed in * deferred mode. */ if (!existing) { ecs_stack_t *stack = &stage->defer_stack; cmd_value = flecs_stack_alloc(stack, size, ti->alignment); /* If the component doesn't yet exist, construct it and move the * provided value into the component, if provided. Don't construct if * this is an emplace operation, in which case the application is * responsible for constructing. */ if (value) { if (emplace) { ecs_move_t move = ti->hooks.move_ctor; if (move) { move(cmd_value, value, 1, ti); } else { ecs_os_memcpy(cmd_value, value, size); } } else { ecs_copy_t copy = ti->hooks.copy_ctor; if (copy) { copy(cmd_value, value, 1, ti); } else { ecs_os_memcpy(cmd_value, value, size); } } } else if (!emplace) { /* If the command is not an emplace, construct the temp storage */ /* Check if entity inherits component */ void *base = NULL; if (table && (table->flags & EcsTableHasIsA)) { base = flecs_get_base_component(world, table, id, idr, 0); } if (!base) { /* Normal ctor */ ecs_xtor_t ctor = ti->hooks.ctor; if (ctor) { ctor(cmd_value, 1, ti); } } else { /* Override */ ecs_copy_t copy = ti->hooks.copy_ctor; if (copy) { copy(cmd_value, base, 1, ti); } else { ecs_os_memcpy(cmd_value, base, size); } } } } else if (value) { /* If component exists and value is provided, copy */ ecs_copy_t copy = ti->hooks.copy; if (copy) { copy(existing, value, 1, ti); } else { ecs_os_memcpy(existing, value, size); } } if (!cmd) { /* If cmd is NULL, entity was already deleted. Check if we need to * insert a command into the queue. */ if (!ti->hooks.dtor) { /* If temporary memory does not need to be destructed, it'll get * freed when the stack allocator is reset. This prevents us * from having to insert a command when the entity was * already deleted. */ return cmd_value; } cmd = flecs_cmd_alloc(stage); } if (!existing) { /* If component didn't exist yet, insert command that will create it */ cmd->kind = cmd_kind; cmd->id = id; cmd->idr = idr; cmd->entity = entity; cmd->is._1.size = size; cmd->is._1.value = cmd_value; } else { /* If component already exists, still insert an Add command to ensure * that any preceding remove commands won't remove the component. If the * operation is a set, also insert a Modified command. */ if (cmd_kind == EcsOpSet) { cmd->kind = EcsOpAddModified; } else { cmd->kind = EcsOpAdd; } cmd->id = id; cmd->entity = entity; } return cmd_value; error: return NULL; } void flecs_stage_merge_post_frame( ecs_world_t *world, ecs_stage_t *stage) { /* Execute post frame actions */ 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_stage_init( ecs_world_t *world, ecs_stage_t *stage) { ecs_poly_assert(world, ecs_world_t); ecs_poly_init(stage, ecs_stage_t); stage->world = world; stage->thread_ctx = world; stage->auto_merge = true; stage->async = false; flecs_stack_init(&stage->defer_stack); flecs_stack_init(&stage->allocators.iter_stack); flecs_stack_init(&stage->allocators.deser_stack); flecs_allocator_init(&stage->allocator); flecs_ballocator_init_n(&stage->allocators.cmd_entry_chunk, ecs_cmd_entry_t, FLECS_SPARSE_CHUNK_SIZE); ecs_allocator_t *a = &stage->allocator; ecs_vec_init_t(a, &stage->commands, ecs_cmd_t, 0); ecs_vec_init_t(a, &stage->post_frame_actions, ecs_action_elem_t, 0); flecs_sparse_init_t(&stage->cmd_entries, &stage->allocator, &stage->allocators.cmd_entry_chunk, ecs_cmd_entry_t); } void flecs_stage_fini( ecs_world_t *world, ecs_stage_t *stage) { (void)world; ecs_poly_assert(world, ecs_world_t); ecs_poly_assert(stage, ecs_stage_t); /* Make sure stage has no unmerged data */ ecs_assert(ecs_vec_count(&stage->commands) == 0, ECS_INTERNAL_ERROR, NULL); ecs_poly_fini(stage, ecs_stage_t); flecs_sparse_fini(&stage->cmd_entries); ecs_allocator_t *a = &stage->allocator; ecs_vec_fini_t(a, &stage->commands, ecs_cmd_t); 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); flecs_stack_fini(&stage->defer_stack); flecs_stack_fini(&stage->allocators.iter_stack); flecs_stack_fini(&stage->allocators.deser_stack); flecs_ballocator_fini(&stage->allocators.cmd_entry_chunk); flecs_allocator_fini(&stage->allocator); } void ecs_set_stage_count( ecs_world_t *world, int32_t stage_count) { ecs_poly_assert(world, ecs_world_t); /* World must have at least one default stage */ ecs_assert(stage_count >= 1 || (world->flags & EcsWorldFini), ECS_INTERNAL_ERROR, NULL); bool auto_merge = true; ecs_entity_t *lookup_path = NULL; ecs_entity_t scope = 0; ecs_entity_t with = 0; if (world->stage_count >= 1) { auto_merge = world->stages[0].auto_merge; lookup_path = world->stages[0].lookup_path; scope = world->stages[0].scope; with = world->stages[0].with; } int32_t i, count = world->stage_count; if (count && count != stage_count) { ecs_stage_t *stages = world->stages; for (i = 0; i < count; i ++) { /* If stage contains a thread handle, ecs_set_threads was used to * create the stages. ecs_set_threads and ecs_set_stage_count should not * be mixed. */ ecs_poly_assert(&stages[i], ecs_stage_t); ecs_check(stages[i].thread == 0, ECS_INVALID_OPERATION, NULL); flecs_stage_fini(world, &stages[i]); } ecs_os_free(world->stages); } if (stage_count) { world->stages = ecs_os_malloc_n(ecs_stage_t, stage_count); for (i = 0; i < stage_count; i ++) { ecs_stage_t *stage = &world->stages[i]; flecs_stage_init(world, stage); stage->id = i; /* Set thread_ctx to stage, as this stage might be used in a * multithreaded context */ stage->thread_ctx = (ecs_world_t*)stage; } } else { /* Set to NULL to prevent double frees */ world->stages = NULL; } /* Regardless of whether the stage was just initialized or not, when the * ecs_set_stage_count function is called, all stages inherit the auto_merge * property from the world */ for (i = 0; i < stage_count; i ++) { world->stages[i].auto_merge = auto_merge; world->stages[i].lookup_path = lookup_path; world->stages[0].scope = scope; world->stages[0].with = with; } world->stage_count = stage_count; error: return; } int32_t ecs_get_stage_count( const ecs_world_t *world) { world = ecs_get_world(world); return world->stage_count; } int32_t ecs_get_stage_id( const ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); if (ecs_poly_is(world, ecs_stage_t)) { ecs_stage_t *stage = (ecs_stage_t*)world; /* Index 0 is reserved for main stage */ return stage->id; } else if (ecs_poly_is(world, ecs_world_t)) { return 0; } else { ecs_throw(ECS_INTERNAL_ERROR, NULL); } error: return 0; } ecs_world_t* ecs_get_stage( const ecs_world_t *world, int32_t stage_id) { ecs_poly_assert(world, ecs_world_t); ecs_check(world->stage_count > stage_id, ECS_INVALID_PARAMETER, NULL); return (ecs_world_t*)&world->stages[stage_id]; error: return NULL; } bool ecs_readonly_begin( ecs_world_t *world) { ecs_poly_assert(world, ecs_world_t); flecs_process_pending_tables(world); ecs_dbg_3("#[bold]readonly"); ecs_log_push_3(); int32_t i, count = ecs_get_stage_count(world); for (i = 0; i < count; i ++) { ecs_stage_t *stage = &world->stages[i]; stage->lookup_path = world->stages[0].lookup_path; ecs_assert(stage->defer == 0, ECS_INVALID_OPERATION, "deferred mode cannot be enabled when entering readonly mode"); flecs_defer_begin(world, stage); } bool is_readonly = ECS_BIT_IS_SET(world->flags, EcsWorldReadonly); /* From this point on, the world is "locked" for mutations, and it is only * allowed to enqueue commands from stages */ ECS_BIT_SET(world->flags, EcsWorldReadonly); /* If world has more than one stage, signal we might be running on multiple * threads. This is a stricter version of readonly mode: while some * mutations like implicit component registration are still allowed in plain * readonly mode, no mutations are allowed when multithreaded. */ if (world->worker_cond) { ECS_BIT_SET(world->flags, EcsWorldMultiThreaded); } return is_readonly; } void ecs_readonly_end( ecs_world_t *world) { ecs_poly_assert(world, ecs_world_t); ecs_check(world->flags & EcsWorldReadonly, ECS_INVALID_OPERATION, NULL); /* After this it is safe again to mutate the world directly */ ECS_BIT_CLEAR(world->flags, EcsWorldReadonly); ECS_BIT_CLEAR(world->flags, EcsWorldMultiThreaded); ecs_log_pop_3(); flecs_stage_auto_merge(world); error: return; } void ecs_merge( ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(ecs_poly_is(world, ecs_world_t) || ecs_poly_is(world, ecs_stage_t), ECS_INVALID_PARAMETER, NULL); flecs_stage_manual_merge(world); error: return; } void ecs_set_automerge( ecs_world_t *world, bool auto_merge) { /* If a world is provided, set auto_merge globally for the world. This * doesn't actually do anything (the main stage never merges) but it serves * as the default for when stages are created. */ if (ecs_poly_is(world, ecs_world_t)) { world->stages[0].auto_merge = auto_merge; /* Propagate change to all stages */ int i, stage_count = ecs_get_stage_count(world); for (i = 0; i < stage_count; i ++) { ecs_stage_t *stage = (ecs_stage_t*)ecs_get_stage(world, i); stage->auto_merge = auto_merge; } /* If a stage is provided, override the auto_merge value for the individual * stage. This allows an application to control per-stage which stage should * be automatically merged and which one shouldn't */ } else { ecs_poly_assert(world, ecs_stage_t); ecs_stage_t *stage = (ecs_stage_t*)world; stage->auto_merge = auto_merge; } } bool ecs_stage_is_readonly( const ecs_world_t *stage) { const ecs_world_t *world = ecs_get_world(stage); if (ecs_poly_is(stage, ecs_stage_t)) { if (((ecs_stage_t*)stage)->async) { return false; } } if (world->flags & EcsWorldReadonly) { if (ecs_poly_is(stage, ecs_world_t)) { return true; } } else { if (ecs_poly_is(stage, ecs_stage_t)) { return true; } } return false; } ecs_world_t* ecs_async_stage_new( ecs_world_t *world) { ecs_stage_t *stage = ecs_os_calloc(sizeof(ecs_stage_t)); flecs_stage_init(world, stage); stage->id = -1; stage->auto_merge = false; stage->async = true; flecs_defer_begin(world, stage); return (ecs_world_t*)stage; } void ecs_async_stage_free( ecs_world_t *world) { ecs_poly_assert(world, ecs_stage_t); ecs_stage_t *stage = (ecs_stage_t*)world; ecs_check(stage->async == true, ECS_INVALID_PARAMETER, NULL); flecs_stage_fini(stage->world, stage); ecs_os_free(stage); error: return; } bool ecs_stage_is_async( ecs_world_t *stage) { if (!stage) { return false; } if (!ecs_poly_is(stage, ecs_stage_t)) { return false; } return ((ecs_stage_t*)stage)->async; } bool ecs_is_deferred( const ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); const ecs_stage_t *stage = flecs_stage_from_readonly_world(world); return stage->defer > 0; error: return false; } /** * @file datastructures/allocator.c * @brief Allocator for any size. * * Allocators create a block allocator for each requested size. */ static ecs_size_t flecs_allocator_size( ecs_size_t size) { return ECS_ALIGN(size, 16); } static ecs_size_t flecs_allocator_size_hash( ecs_size_t size) { return size >> 4; } void flecs_allocator_init( ecs_allocator_t *a) { flecs_ballocator_init_n(&a->chunks, ecs_block_allocator_t, FLECS_SPARSE_CHUNK_SIZE); flecs_sparse_init_t(&a->sizes, NULL, &a->chunks, ecs_block_allocator_t); } void flecs_allocator_fini( ecs_allocator_t *a) { int32_t i = 0, count = flecs_sparse_count(&a->sizes); for (i = 0; i < count; i ++) { ecs_block_allocator_t *ba = flecs_sparse_get_dense_t( &a->sizes, ecs_block_allocator_t, i); flecs_ballocator_fini(ba); } flecs_sparse_fini(&a->sizes); flecs_ballocator_fini(&a->chunks); } ecs_block_allocator_t* flecs_allocator_get( ecs_allocator_t *a, ecs_size_t size) { ecs_assert(size >= 0, ECS_INTERNAL_ERROR, NULL); if (!size) { return NULL; } ecs_assert(a != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(size <= flecs_allocator_size(size), ECS_INTERNAL_ERROR, NULL); size = flecs_allocator_size(size); ecs_size_t hash = flecs_allocator_size_hash(size); ecs_block_allocator_t *result = flecs_sparse_get_any_t(&a->sizes, ecs_block_allocator_t, (uint32_t)hash); if (!result) { result = flecs_sparse_ensure_fast_t(&a->sizes, ecs_block_allocator_t, (uint32_t)hash); flecs_ballocator_init(result, size); } ecs_assert(result->data_size == size, ECS_INTERNAL_ERROR, NULL); return result; } char* flecs_strdup( ecs_allocator_t *a, const char* str) { ecs_size_t len = ecs_os_strlen(str); char *result = flecs_alloc_n(a, char, len + 1); ecs_os_memcpy(result, str, len + 1); return result; } void flecs_strfree( ecs_allocator_t *a, char* str) { ecs_size_t len = ecs_os_strlen(str); flecs_free_n(a, char, len + 1, str); } void* flecs_dup( ecs_allocator_t *a, ecs_size_t size, const void *src) { ecs_block_allocator_t *ba = flecs_allocator_get(a, size); if (ba) { void *dst = flecs_balloc(ba); ecs_os_memcpy(dst, src, size); return dst; } else { return NULL; } } /** * @file datastructures/sparse.c * @brief Sparse set data structure. * * The sparse set data structure allows for fast lookups by 64bit key with * variable payload size. Lookup operations are guaranteed to be O(1), in * contrast to ecs_map_t which has O(1) lookup time on average. At a high level * the sparse set works with two arrays: * * dense [ - ][ 3 ][ 1 ][ 4 ] * sparse [ ][ 2 ][ ][ 1 ][ 3 ] * * Indices in the dense array point to the sparse array, and vice versa. The * dense array is guaranteed to contain no holes. By iterating the dense array, * all populated elements in the sparse set can be found without having to skip * empty elements. * * The sparse array is paged, which means that it is split up in memory blocks * (pages, not to be confused with OS pages) of equal size. The page size is * set to 4096 elements. Paging prevents the sparse array from having to grow * to N elements, where N is the largest key used in the set. It also ensures * that payload pointers are stable. * * The sparse set recycles deleted keys. It does this by moving not alive keys * to the end of the dense array, and using a count member that indicates the * last alive member in the dense array. This approach makes it possible to * recycle keys in bulk, by increasing the alive count. * * When a key is deleted, the sparse set increases its generation count. This * generation count is used to test whether a key passed to the sparse set is * still valid, or whether it has been deleted. * * The sparse set is used in a number of places, like for retrieving entity * administration, tables and allocators. */ /** Compute the page index from an id by stripping the first 12 bits */ #define PAGE(index) ((int32_t)((uint32_t)index >> 12)) /** This computes the offset of an index inside a page */ #define OFFSET(index) ((int32_t)index & 0xFFF) /* Utility to get a pointer to the payload */ #define DATA(array, size, offset) (ECS_OFFSET(array, size * offset)) typedef struct ecs_page_t { int32_t *sparse; /* Sparse array with indices to dense array */ void *data; /* Store data in sparse array to reduce * indirection and provide stable pointers. */ } ecs_page_t; static ecs_page_t* flecs_sparse_page_new( ecs_sparse_t *sparse, int32_t page_index) { ecs_allocator_t *a = sparse->allocator; ecs_block_allocator_t *ca = sparse->page_allocator; int32_t count = ecs_vec_count(&sparse->pages); ecs_page_t *pages; if (count <= page_index) { ecs_vec_set_count_t(a, &sparse->pages, ecs_page_t, page_index + 1); pages = ecs_vec_first_t(&sparse->pages, ecs_page_t); ecs_os_memset_n(&pages[count], 0, ecs_page_t, (1 + page_index - count)); } else { pages = ecs_vec_first_t(&sparse->pages, ecs_page_t); } ecs_assert(pages != NULL, ECS_INTERNAL_ERROR, NULL); ecs_page_t *result = &pages[page_index]; ecs_assert(result->sparse == NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(result->data == NULL, ECS_INTERNAL_ERROR, NULL); /* Initialize sparse array with zero's, as zero is used to indicate that the * sparse element has not been paired with a dense element. Use zero * as this means we can take advantage of calloc having a possibly better * performance than malloc + memset. */ result->sparse = ca ? flecs_bcalloc(ca) : ecs_os_calloc_n(int32_t, FLECS_SPARSE_CHUNK_SIZE); /* Initialize the data array with zero's to guarantee that data is * always initialized. When an entry is removed, data is reset back to * zero. Initialize now, as this can take advantage of calloc. */ result->data = a ? flecs_calloc(a, sparse->size * FLECS_SPARSE_CHUNK_SIZE) : ecs_os_calloc(sparse->size * FLECS_SPARSE_CHUNK_SIZE); ecs_assert(result->sparse != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(result->data != NULL, ECS_INTERNAL_ERROR, NULL); return result; } static void flecs_sparse_page_free( ecs_sparse_t *sparse, ecs_page_t *page) { ecs_allocator_t *a = sparse->allocator; ecs_block_allocator_t *ca = sparse->page_allocator; if (ca) { flecs_bfree(ca, page->sparse); } else { ecs_os_free(page->sparse); } if (a) { flecs_free(a, sparse->size * FLECS_SPARSE_CHUNK_SIZE, page->data); } else { ecs_os_free(page->data); } } static ecs_page_t* flecs_sparse_get_page( const ecs_sparse_t *sparse, int32_t page_index) { ecs_assert(page_index >= 0, ECS_INVALID_PARAMETER, NULL); if (page_index >= ecs_vec_count(&sparse->pages)) { return NULL; } return ecs_vec_get_t(&sparse->pages, ecs_page_t, page_index);; } static ecs_page_t* flecs_sparse_get_or_create_page( ecs_sparse_t *sparse, int32_t page_index) { ecs_page_t *page = flecs_sparse_get_page(sparse, page_index); if (page && page->sparse) { return page; } return flecs_sparse_page_new(sparse, page_index); } static void flecs_sparse_grow_dense( ecs_sparse_t *sparse) { ecs_vec_append_t(sparse->allocator, &sparse->dense, uint64_t); } static uint64_t flecs_sparse_strip_generation( uint64_t *index_out) { uint64_t index = *index_out; uint64_t gen = index & ECS_GENERATION_MASK; /* Make sure there's no junk in the id */ ecs_assert(gen == (index & (0xFFFFFFFFull << 32)), ECS_INVALID_PARAMETER, NULL); *index_out -= gen; return gen; } static void flecs_sparse_assign_index( ecs_page_t * page, uint64_t * dense_array, uint64_t index, int32_t dense) { /* Initialize sparse-dense pair. This assigns the dense index to the sparse * array, and the sparse index to the dense array .*/ page->sparse[OFFSET(index)] = dense; dense_array[dense] = index; } static uint64_t flecs_sparse_inc_gen( uint64_t index) { /* When an index is deleted, its generation is increased so that we can do * liveliness checking while recycling ids */ return ECS_GENERATION_INC(index); } static uint64_t flecs_sparse_inc_id( ecs_sparse_t *sparse) { /* Generate a new id. The last issued id could be stored in an external * variable, such as is the case with the last issued entity id, which is * stored on the world. */ return ++ (sparse->max_id[0]); } static uint64_t flecs_sparse_get_id( const ecs_sparse_t *sparse) { ecs_assert(sparse != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(sparse->max_id != NULL, ECS_INTERNAL_ERROR, NULL); return sparse->max_id[0]; } static void flecs_sparse_set_id( ecs_sparse_t *sparse, uint64_t value) { /* Sometimes the max id needs to be assigned directly, which typically * happens when the API calls get_or_create for an id that hasn't been * issued before. */ sparse->max_id[0] = value; } /* Pair dense id with new sparse id */ static uint64_t flecs_sparse_create_id( ecs_sparse_t *sparse, int32_t dense) { uint64_t index = flecs_sparse_inc_id(sparse); flecs_sparse_grow_dense(sparse); ecs_page_t *page = flecs_sparse_get_or_create_page(sparse, PAGE(index)); ecs_assert(page->sparse[OFFSET(index)] == 0, ECS_INTERNAL_ERROR, NULL); uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); flecs_sparse_assign_index(page, dense_array, index, dense); return index; } /* Create new id */ static uint64_t flecs_sparse_new_index( ecs_sparse_t *sparse) { int32_t dense_count = ecs_vec_count(&sparse->dense); int32_t count = sparse->count ++; ecs_assert(count <= dense_count, ECS_INTERNAL_ERROR, NULL); if (count < dense_count) { /* If there are unused elements in the dense array, return first */ uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); return dense_array[count]; } else { return flecs_sparse_create_id(sparse, count); } } /* Get value from sparse set when it is guaranteed that the value exists. This * function is used when values are obtained using a dense index */ static void* flecs_sparse_get_sparse( const ecs_sparse_t *sparse, int32_t dense, uint64_t index) { flecs_sparse_strip_generation(&index); ecs_page_t *page = flecs_sparse_get_page(sparse, PAGE(index)); if (!page || !page->sparse) { return NULL; } int32_t offset = OFFSET(index); ecs_assert(page != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(dense == page->sparse[offset], ECS_INTERNAL_ERROR, NULL); (void)dense; return DATA(page->data, sparse->size, offset); } /* Swap dense elements. A swap occurs when an element is removed, or when a * removed element is recycled. */ static void flecs_sparse_swap_dense( ecs_sparse_t * sparse, ecs_page_t * page_a, int32_t a, int32_t b) { ecs_assert(a != b, ECS_INTERNAL_ERROR, NULL); uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); uint64_t index_a = dense_array[a]; uint64_t index_b = dense_array[b]; ecs_page_t *page_b = flecs_sparse_get_or_create_page(sparse, PAGE(index_b)); flecs_sparse_assign_index(page_a, dense_array, index_a, b); flecs_sparse_assign_index(page_b, dense_array, index_b, a); } void flecs_sparse_init( ecs_sparse_t *result, struct ecs_allocator_t *allocator, ecs_block_allocator_t *page_allocator, ecs_size_t size) { ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL); result->size = size; result->max_id_local = UINT64_MAX; result->max_id = &result->max_id_local; result->allocator = allocator; result->page_allocator = page_allocator; ecs_vec_init_t(allocator, &result->pages, ecs_page_t, 0); ecs_vec_init_t(allocator, &result->dense, uint64_t, 1); result->dense.count = 1; /* Consume first value in dense array as 0 is used in the sparse array to * indicate that a sparse element hasn't been paired yet. */ ecs_vec_first_t(&result->dense, uint64_t)[0] = 0; result->count = 1; } void flecs_sparse_set_id_source( ecs_sparse_t * sparse, uint64_t * id_source) { ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); sparse->max_id = id_source; } void flecs_sparse_clear( ecs_sparse_t *sparse) { ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); int32_t i, count = ecs_vec_count(&sparse->pages); ecs_page_t *pages = ecs_vec_first_t(&sparse->pages, ecs_page_t); for (i = 0; i < count; i ++) { int32_t *indices = pages[i].sparse; if (indices) { ecs_os_memset_n(indices, 0, int32_t, FLECS_SPARSE_CHUNK_SIZE); } } ecs_vec_set_count_t(sparse->allocator, &sparse->dense, uint64_t, 1); sparse->count = 1; sparse->max_id_local = 0; } void flecs_sparse_fini( ecs_sparse_t *sparse) { ecs_assert(sparse != NULL, ECS_INTERNAL_ERROR, NULL); int32_t i, count = ecs_vec_count(&sparse->pages); ecs_page_t *pages = ecs_vec_first_t(&sparse->pages, ecs_page_t); for (i = 0; i < count; i ++) { flecs_sparse_page_free(sparse, &pages[i]); } ecs_vec_fini_t(sparse->allocator, &sparse->pages, ecs_page_t); ecs_vec_fini_t(sparse->allocator, &sparse->dense, uint64_t); } uint64_t flecs_sparse_new_id( ecs_sparse_t *sparse) { ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); return flecs_sparse_new_index(sparse); } const uint64_t* flecs_sparse_new_ids( ecs_sparse_t *sparse, int32_t new_count) { ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); int32_t dense_count = ecs_vec_count(&sparse->dense); int32_t count = sparse->count; int32_t recyclable = dense_count - count; int32_t i, to_create = new_count - recyclable; if (to_create > 0) { flecs_sparse_set_size(sparse, dense_count + to_create); for (i = 0; i < to_create; i ++) { flecs_sparse_create_id(sparse, count + recyclable + i); } } sparse->count += new_count; return ecs_vec_get_t(&sparse->dense, uint64_t, count); } void* flecs_sparse_add( ecs_sparse_t *sparse, ecs_size_t size) { ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); uint64_t index = flecs_sparse_new_index(sparse); ecs_page_t *page = flecs_sparse_get_page(sparse, PAGE(index)); ecs_assert(page != NULL, ECS_INTERNAL_ERROR, NULL); return DATA(page->data, size, OFFSET(index)); } uint64_t flecs_sparse_last_id( const ecs_sparse_t *sparse) { ecs_assert(sparse != NULL, ECS_INTERNAL_ERROR, NULL); uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); return dense_array[sparse->count - 1]; } void* flecs_sparse_ensure( ecs_sparse_t *sparse, ecs_size_t size, uint64_t index) { ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); ecs_assert(ecs_vec_count(&sparse->dense) > 0, ECS_INTERNAL_ERROR, NULL); (void)size; uint64_t gen = flecs_sparse_strip_generation(&index); ecs_page_t *page = flecs_sparse_get_or_create_page(sparse, PAGE(index)); int32_t offset = OFFSET(index); int32_t dense = page->sparse[offset]; if (dense) { /* Check if element is alive. If element is not alive, update indices so * that the first unused dense element points to the sparse element. */ int32_t count = sparse->count; if (dense == count) { /* If dense is the next unused element in the array, simply increase * the count to make it part of the alive set. */ sparse->count ++; } else if (dense > count) { /* If dense is not alive, swap it with the first unused element. */ flecs_sparse_swap_dense(sparse, page, dense, count); dense = count; /* First unused element is now last used element */ sparse->count ++; } else { /* Dense is already alive, nothing to be done */ } /* Ensure provided generation matches current. Only allow mismatching * generations if the provided generation count is 0. This allows for * using the ensure function in combination with ids that have their * generation stripped. */ #ifdef FLECS_DEBUG uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); ecs_assert(!gen || dense_array[dense] == (index | gen), ECS_INTERNAL_ERROR, NULL); #endif } else { /* Element is not paired yet. Must add a new element to dense array */ flecs_sparse_grow_dense(sparse); uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); int32_t dense_count = ecs_vec_count(&sparse->dense) - 1; int32_t count = sparse->count ++; /* If index is larger than max id, update max id */ if (index >= flecs_sparse_get_id(sparse)) { flecs_sparse_set_id(sparse, index); } if (count < dense_count) { /* If there are unused elements in the list, move the first unused * element to the end of the list */ uint64_t unused = dense_array[count]; ecs_page_t *unused_page = flecs_sparse_get_or_create_page(sparse, PAGE(unused)); flecs_sparse_assign_index(unused_page, dense_array, unused, dense_count); } flecs_sparse_assign_index(page, dense_array, index, count); dense_array[count] |= gen; } return DATA(page->data, sparse->size, offset); } void* flecs_sparse_ensure_fast( ecs_sparse_t *sparse, ecs_size_t size, uint64_t index_long) { ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); ecs_assert(ecs_vec_count(&sparse->dense) > 0, ECS_INTERNAL_ERROR, NULL); (void)size; uint32_t index = (uint32_t)index_long; ecs_page_t *page = flecs_sparse_get_or_create_page(sparse, PAGE(index)); int32_t offset = OFFSET(index); int32_t dense = page->sparse[offset]; int32_t count = sparse->count; if (!dense) { /* Element is not paired yet. Must add a new element to dense array */ sparse->count = count + 1; if (count == ecs_vec_count(&sparse->dense)) { flecs_sparse_grow_dense(sparse); } uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); flecs_sparse_assign_index(page, dense_array, index, count); } return DATA(page->data, sparse->size, offset); } void flecs_sparse_remove( ecs_sparse_t *sparse, ecs_size_t size, uint64_t index) { ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); (void)size; ecs_page_t *page = flecs_sparse_get_page(sparse, PAGE(index)); if (!page || !page->sparse) { return; } uint64_t gen = flecs_sparse_strip_generation(&index); int32_t offset = OFFSET(index); int32_t dense = page->sparse[offset]; if (dense) { uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); uint64_t cur_gen = dense_array[dense] & ECS_GENERATION_MASK; if (gen != cur_gen) { /* Generation doesn't match which means that the provided entity is * already not alive. */ return; } /* Increase generation */ dense_array[dense] = index | flecs_sparse_inc_gen(cur_gen); int32_t count = sparse->count; if (dense == (count - 1)) { /* If dense is the last used element, simply decrease count */ sparse->count --; } else if (dense < count) { /* If element is alive, move it to unused elements */ flecs_sparse_swap_dense(sparse, page, dense, count - 1); sparse->count --; } else { /* Element is not alive, nothing to be done */ return; } /* Reset memory to zero on remove */ void *ptr = DATA(page->data, sparse->size, offset); ecs_os_memset(ptr, 0, size); } else { /* Element is not paired and thus not alive, nothing to be done */ return; } } void flecs_sparse_set_generation( ecs_sparse_t *sparse, uint64_t index) { ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); ecs_page_t *page = flecs_sparse_get_or_create_page(sparse, PAGE(index)); uint64_t index_w_gen = index; flecs_sparse_strip_generation(&index); int32_t offset = OFFSET(index); int32_t dense = page->sparse[offset]; if (dense) { /* Increase generation */ ecs_vec_get_t(&sparse->dense, uint64_t, dense)[0] = index_w_gen; } else { /* Element is not paired and thus not alive, nothing to be done */ } } bool flecs_sparse_exists( const ecs_sparse_t *sparse, uint64_t index) { ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); ecs_page_t *page = flecs_sparse_get_page(sparse, PAGE(index)); if (!page || !page->sparse) { return false; } flecs_sparse_strip_generation(&index); int32_t offset = OFFSET(index); int32_t dense = page->sparse[offset]; return dense != 0; } bool flecs_sparse_is_valid( const ecs_sparse_t *sparse, uint64_t index) { ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); ecs_page_t *page = flecs_sparse_get_page(sparse, PAGE(index)); if (!page || !page->sparse) { return true; /* Doesn't exist yet, so is valid */ } flecs_sparse_strip_generation(&index); int32_t offset = OFFSET(index); int32_t dense = page->sparse[offset]; if (!dense) { return true; /* Doesn't exist yet, so is valid */ } /* If the id exists, it must be alive */ return dense < sparse->count; } void* flecs_sparse_get_dense( const ecs_sparse_t *sparse, ecs_size_t size, int32_t dense_index) { ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); ecs_assert(dense_index < sparse->count, ECS_INVALID_PARAMETER, NULL); (void)size; dense_index ++; uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); return flecs_sparse_get_sparse(sparse, dense_index, dense_array[dense_index]); } bool flecs_sparse_is_alive( const ecs_sparse_t *sparse, uint64_t index) { ecs_page_t *page = flecs_sparse_get_page(sparse, PAGE(index)); if (!page || !page->sparse) { return false; } int32_t offset = OFFSET(index); int32_t dense = page->sparse[offset]; if (!dense || (dense >= sparse->count)) { return false; } uint64_t gen = flecs_sparse_strip_generation(&index); uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); uint64_t cur_gen = dense_array[dense] & ECS_GENERATION_MASK; if (cur_gen != gen) { return false; } ecs_assert(dense == page->sparse[offset], ECS_INTERNAL_ERROR, NULL); return true; } uint64_t flecs_sparse_get_current( const ecs_sparse_t *sparse, uint64_t index) { ecs_page_t *page = flecs_sparse_get_page(sparse, PAGE(index)); if (!page || !page->sparse) { return 0; } int32_t offset = OFFSET(index); int32_t dense = page->sparse[offset]; uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); /* If dense is 0 (tombstone) this will return 0 */ return dense_array[dense]; } void* flecs_sparse_try( const ecs_sparse_t *sparse, ecs_size_t size, uint64_t index) { ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); (void)size; ecs_page_t *page = flecs_sparse_get_page(sparse, PAGE(index)); if (!page || !page->sparse) { return NULL; } int32_t offset = OFFSET(index); int32_t dense = page->sparse[offset]; if (!dense || (dense >= sparse->count)) { return NULL; } uint64_t gen = flecs_sparse_strip_generation(&index); uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); uint64_t cur_gen = dense_array[dense] & ECS_GENERATION_MASK; if (cur_gen != gen) { return NULL; } ecs_assert(dense == page->sparse[offset], ECS_INTERNAL_ERROR, NULL); return DATA(page->data, sparse->size, offset); } void* flecs_sparse_get( const ecs_sparse_t *sparse, ecs_size_t size, uint64_t index) { ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); (void)size; ecs_page_t *page = ecs_vec_get_t(&sparse->pages, ecs_page_t, PAGE(index)); int32_t offset = OFFSET(index); int32_t dense = page->sparse[offset]; ecs_assert(dense != 0, ECS_INTERNAL_ERROR, NULL); uint64_t gen = flecs_sparse_strip_generation(&index); uint64_t *dense_array = ecs_vec_first_t(&sparse->dense, uint64_t); uint64_t cur_gen = dense_array[dense] & ECS_GENERATION_MASK; (void)cur_gen; (void)gen; ecs_assert(cur_gen == gen, ECS_INVALID_PARAMETER, NULL); ecs_assert(dense == page->sparse[offset], ECS_INTERNAL_ERROR, NULL); ecs_assert(dense < sparse->count, ECS_INTERNAL_ERROR, NULL); return DATA(page->data, sparse->size, offset); } void* flecs_sparse_get_any( const ecs_sparse_t *sparse, ecs_size_t size, uint64_t index) { ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); (void)size; flecs_sparse_strip_generation(&index); ecs_page_t *page = flecs_sparse_get_page(sparse, PAGE(index)); if (!page || !page->sparse) { return NULL; } int32_t offset = OFFSET(index); int32_t dense = page->sparse[offset]; bool in_use = dense && (dense < sparse->count); if (!in_use) { return NULL; } ecs_assert(dense == page->sparse[offset], ECS_INTERNAL_ERROR, NULL); return DATA(page->data, sparse->size, offset); } int32_t flecs_sparse_count( const ecs_sparse_t *sparse) { if (!sparse || !sparse->count) { return 0; } return sparse->count - 1; } int32_t flecs_sparse_not_alive_count( const ecs_sparse_t *sparse) { if (!sparse) { return 0; } return ecs_vec_count(&sparse->dense) - sparse->count; } int32_t flecs_sparse_size( const ecs_sparse_t *sparse) { if (!sparse) { return 0; } return ecs_vec_count(&sparse->dense) - 1; } const uint64_t* flecs_sparse_ids( const ecs_sparse_t *sparse) { ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); if (sparse->dense.array) { return &(ecs_vec_first_t(&sparse->dense, uint64_t)[1]); } else { return NULL; } } void flecs_sparse_set_size( ecs_sparse_t *sparse, int32_t elem_count) { ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); ecs_vec_set_size_t(sparse->allocator, &sparse->dense, uint64_t, elem_count); } static void flecs_sparse_copy_intern( ecs_sparse_t * dst, const ecs_sparse_t * src) { flecs_sparse_set_size(dst, flecs_sparse_size(src)); const uint64_t *indices = flecs_sparse_ids(src); ecs_size_t size = src->size; int32_t i, count = src->count; for (i = 0; i < count - 1; i ++) { uint64_t index = indices[i]; void *src_ptr = flecs_sparse_get(src, size, index); void *dst_ptr = flecs_sparse_ensure(dst, size, index); flecs_sparse_set_generation(dst, index); ecs_os_memcpy(dst_ptr, src_ptr, size); } flecs_sparse_set_id(dst, flecs_sparse_get_id(src)); ecs_assert(src->count == dst->count, ECS_INTERNAL_ERROR, NULL); } void flecs_sparse_copy( ecs_sparse_t *dst, const ecs_sparse_t *src) { if (!src) { return; } flecs_sparse_init(dst, src->allocator, src->page_allocator, src->size); flecs_sparse_copy_intern(dst, src); } void flecs_sparse_restore( ecs_sparse_t * dst, const ecs_sparse_t * src) { ecs_assert(dst != NULL, ECS_INVALID_PARAMETER, NULL); dst->count = 1; if (src) { flecs_sparse_copy_intern(dst, src); } } void ecs_sparse_init( ecs_sparse_t *sparse, ecs_size_t elem_size) { flecs_sparse_init(sparse, NULL, NULL, elem_size); } void* ecs_sparse_add( ecs_sparse_t *sparse, ecs_size_t elem_size) { return flecs_sparse_add(sparse, elem_size); } uint64_t ecs_sparse_last_id( const ecs_sparse_t *sparse) { return flecs_sparse_last_id(sparse); } int32_t ecs_sparse_count( const ecs_sparse_t *sparse) { return flecs_sparse_count(sparse); } void* ecs_sparse_get_dense( const ecs_sparse_t *sparse, ecs_size_t elem_size, int32_t index) { return flecs_sparse_get_dense(sparse, elem_size, index); } void* ecs_sparse_get( const ecs_sparse_t *sparse, ecs_size_t elem_size, uint64_t id) { return flecs_sparse_get(sparse, elem_size, id); } /** * @file datastructures/switch_list.c * @brief Interleaved linked list for storing mutually exclusive values. * * Datastructure that stores N interleaved linked lists in an array. * This allows for efficient storage of elements with mutually exclusive values. * Each linked list has a header element which points to the index in the array * that stores the first node of the list. Each list node points to the next * array element. * * The datastructure allows for efficient storage and retrieval for values with * mutually exclusive values, such as enumeration values. The linked list allows * an application to obtain all elements for a given (enumeration) value without * having to search. * * While the list accepts 64 bit values, it only uses the lower 32bits of the * value for selecting the correct linked list. * * The switch list is used to store union relationships. */ #ifdef FLECS_SANITIZE static void flecs_switch_verify_nodes( ecs_switch_header_t *hdr, ecs_switch_node_t *nodes) { if (!hdr) { return; } int32_t prev = -1, elem = hdr->element, count = 0; while (elem != -1) { ecs_assert(prev == nodes[elem].prev, ECS_INTERNAL_ERROR, NULL); prev = elem; elem = nodes[elem].next; count ++; } ecs_assert(count == hdr->count, ECS_INTERNAL_ERROR, NULL); } #else #define flecs_switch_verify_nodes(hdr, nodes) #endif static ecs_switch_header_t* flecs_switch_get_header( const ecs_switch_t *sw, uint64_t value) { if (value == 0) { return NULL; } return (ecs_switch_header_t*)ecs_map_get(&sw->hdrs, value); } static ecs_switch_header_t *flecs_switch_ensure_header( ecs_switch_t *sw, uint64_t value) { ecs_switch_header_t *node = flecs_switch_get_header(sw, value); if (!node && (value != 0)) { node = (ecs_switch_header_t*)ecs_map_ensure(&sw->hdrs, value); node->count = 0; node->element = -1; } return node; } static void flecs_switch_remove_node( ecs_switch_header_t *hdr, ecs_switch_node_t *nodes, ecs_switch_node_t *node, int32_t element) { ecs_assert(&nodes[element] == node, ECS_INTERNAL_ERROR, NULL); /* Update previous node/header */ if (hdr->element == element) { ecs_assert(node->prev == -1, ECS_INVALID_PARAMETER, NULL); /* If this is the first node, update the header */ hdr->element = node->next; } else { /* If this is not the first node, update the previous node to the * removed node's next ptr */ ecs_assert(node->prev != -1, ECS_INVALID_PARAMETER, NULL); ecs_switch_node_t *prev_node = &nodes[node->prev]; prev_node->next = node->next; } /* Update next node */ int32_t next = node->next; if (next != -1) { ecs_assert(next >= 0, ECS_INVALID_PARAMETER, NULL); /* If this is not the last node, update the next node to point to the * removed node's prev ptr */ ecs_switch_node_t *next_node = &nodes[next]; next_node->prev = node->prev; } /* Decrease count of current header */ hdr->count --; ecs_assert(hdr->count >= 0, ECS_INTERNAL_ERROR, NULL); } void flecs_switch_init( ecs_switch_t *sw, ecs_allocator_t *allocator, int32_t elements) { ecs_map_init(&sw->hdrs, allocator); ecs_vec_init_t(allocator, &sw->nodes, ecs_switch_node_t, elements); ecs_vec_init_t(allocator, &sw->values, uint64_t, elements); ecs_switch_node_t *nodes = ecs_vec_first(&sw->nodes); uint64_t *values = ecs_vec_first(&sw->values); int i; for (i = 0; i < elements; i ++) { nodes[i].prev = -1; nodes[i].next = -1; values[i] = 0; } } void flecs_switch_clear( ecs_switch_t *sw) { ecs_map_clear(&sw->hdrs); ecs_vec_fini_t(sw->hdrs.allocator, &sw->nodes, ecs_switch_node_t); ecs_vec_fini_t(sw->hdrs.allocator, &sw->values, uint64_t); } void flecs_switch_fini( ecs_switch_t *sw) { ecs_map_fini(&sw->hdrs); ecs_vec_fini_t(sw->hdrs.allocator, &sw->nodes, ecs_switch_node_t); ecs_vec_fini_t(sw->hdrs.allocator, &sw->values, uint64_t); } void flecs_switch_add( ecs_switch_t *sw) { ecs_switch_node_t *node = ecs_vec_append_t(sw->hdrs.allocator, &sw->nodes, ecs_switch_node_t); uint64_t *value = ecs_vec_append_t(sw->hdrs.allocator, &sw->values, uint64_t); node->prev = -1; node->next = -1; *value = 0; } void flecs_switch_set_count( ecs_switch_t *sw, int32_t count) { int32_t old_count = ecs_vec_count(&sw->nodes); if (old_count == count) { return; } ecs_vec_set_count_t(sw->hdrs.allocator, &sw->nodes, ecs_switch_node_t, count); ecs_vec_set_count_t(sw->hdrs.allocator, &sw->values, uint64_t, count); ecs_switch_node_t *nodes = ecs_vec_first(&sw->nodes); uint64_t *values = ecs_vec_first(&sw->values); int32_t i; for (i = old_count; i < count; i ++) { ecs_switch_node_t *node = &nodes[i]; node->prev = -1; node->next = -1; values[i] = 0; } } int32_t flecs_switch_count( ecs_switch_t *sw) { ecs_assert(ecs_vec_count(&sw->values) == ecs_vec_count(&sw->nodes), ECS_INTERNAL_ERROR, NULL); return ecs_vec_count(&sw->values); } void flecs_switch_ensure( ecs_switch_t *sw, int32_t count) { int32_t old_count = ecs_vec_count(&sw->nodes); if (old_count >= count) { return; } flecs_switch_set_count(sw, count); } void flecs_switch_addn( ecs_switch_t *sw, int32_t count) { int32_t old_count = ecs_vec_count(&sw->nodes); flecs_switch_set_count(sw, old_count + count); } void flecs_switch_set( ecs_switch_t *sw, int32_t element, uint64_t value) { ecs_assert(sw != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(element < ecs_vec_count(&sw->nodes), ECS_INVALID_PARAMETER, NULL); ecs_assert(element < ecs_vec_count(&sw->values), ECS_INVALID_PARAMETER, NULL); ecs_assert(element >= 0, ECS_INVALID_PARAMETER, NULL); uint64_t *values = ecs_vec_first(&sw->values); uint64_t cur_value = values[element]; /* If the node is already assigned to the value, nothing to be done */ if (cur_value == value) { return; } ecs_switch_node_t *nodes = ecs_vec_first(&sw->nodes); ecs_switch_node_t *node = &nodes[element]; ecs_switch_header_t *dst_hdr = flecs_switch_ensure_header(sw, value); ecs_switch_header_t *cur_hdr = flecs_switch_get_header(sw, cur_value); flecs_switch_verify_nodes(cur_hdr, nodes); flecs_switch_verify_nodes(dst_hdr, nodes); /* If value is not 0, and dst_hdr is NULL, then this is not a valid value * for this switch */ ecs_assert(dst_hdr != NULL || !value, ECS_INVALID_PARAMETER, NULL); if (cur_hdr) { flecs_switch_remove_node(cur_hdr, nodes, node, element); } /* Now update the node itself by adding it as the first node of dst */ node->prev = -1; values[element] = value; if (dst_hdr) { node->next = dst_hdr->element; /* Also update the dst header */ int32_t first = dst_hdr->element; if (first != -1) { ecs_assert(first >= 0, ECS_INTERNAL_ERROR, NULL); ecs_switch_node_t *first_node = &nodes[first]; first_node->prev = element; } dst_hdr->element = element; dst_hdr->count ++; } } void flecs_switch_remove( ecs_switch_t *sw, int32_t elem) { ecs_assert(sw != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(elem < ecs_vec_count(&sw->nodes), ECS_INVALID_PARAMETER, NULL); ecs_assert(elem >= 0, ECS_INVALID_PARAMETER, NULL); uint64_t *values = ecs_vec_first(&sw->values); uint64_t value = values[elem]; ecs_switch_node_t *nodes = ecs_vec_first(&sw->nodes); ecs_switch_node_t *node = &nodes[elem]; /* If node is currently assigned to a case, remove it from the list */ if (value != 0) { ecs_switch_header_t *hdr = flecs_switch_get_header(sw, value); ecs_assert(hdr != NULL, ECS_INTERNAL_ERROR, NULL); flecs_switch_verify_nodes(hdr, nodes); flecs_switch_remove_node(hdr, nodes, node, elem); } int32_t last_elem = ecs_vec_count(&sw->nodes) - 1; if (last_elem != elem) { ecs_switch_node_t *last = ecs_vec_last_t(&sw->nodes, ecs_switch_node_t); int32_t next = last->next, prev = last->prev; if (next != -1) { ecs_switch_node_t *n = &nodes[next]; n->prev = elem; } if (prev != -1) { ecs_switch_node_t *n = &nodes[prev]; n->next = elem; } else { ecs_switch_header_t *hdr = flecs_switch_get_header(sw, values[last_elem]); if (hdr && hdr->element != -1) { ecs_assert(hdr->element == last_elem, ECS_INTERNAL_ERROR, NULL); hdr->element = elem; } } } /* Remove element from arrays */ ecs_vec_remove_t(&sw->nodes, ecs_switch_node_t, elem); ecs_vec_remove_t(&sw->values, uint64_t, elem); } uint64_t flecs_switch_get( const ecs_switch_t *sw, int32_t element) { ecs_assert(sw != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(element < ecs_vec_count(&sw->nodes), ECS_INVALID_PARAMETER, NULL); ecs_assert(element < ecs_vec_count(&sw->values), ECS_INVALID_PARAMETER, NULL); ecs_assert(element >= 0, ECS_INVALID_PARAMETER, NULL); uint64_t *values = ecs_vec_first(&sw->values); return values[element]; } ecs_vec_t* flecs_switch_values( const ecs_switch_t *sw) { return (ecs_vec_t*)&sw->values; } int32_t flecs_switch_case_count( const ecs_switch_t *sw, uint64_t value) { ecs_switch_header_t *hdr = flecs_switch_get_header(sw, value); if (!hdr) { return 0; } return hdr->count; } void flecs_switch_swap( ecs_switch_t *sw, int32_t elem_1, int32_t elem_2) { uint64_t v1 = flecs_switch_get(sw, elem_1); uint64_t v2 = flecs_switch_get(sw, elem_2); flecs_switch_set(sw, elem_2, v1); flecs_switch_set(sw, elem_1, v2); } int32_t flecs_switch_first( const ecs_switch_t *sw, uint64_t value) { ecs_assert(sw != NULL, ECS_INVALID_PARAMETER, NULL); ecs_switch_header_t *hdr = flecs_switch_get_header(sw, value); if (!hdr) { return -1; } return hdr->element; } int32_t flecs_switch_next( const ecs_switch_t *sw, int32_t element) { ecs_assert(sw != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(element < ecs_vec_count(&sw->nodes), ECS_INVALID_PARAMETER, NULL); ecs_assert(element >= 0, ECS_INVALID_PARAMETER, NULL); ecs_switch_node_t *nodes = ecs_vec_first(&sw->nodes); return nodes[element].next; } /** * @file datastructures/name_index.c * @brief Data structure for resolving 64bit keys by string (name). */ static uint64_t flecs_name_index_hash( const void *ptr) { const ecs_hashed_string_t *str = ptr; ecs_assert(str->hash != 0, ECS_INTERNAL_ERROR, NULL); return str->hash; } static int flecs_name_index_compare( const void *ptr1, const void *ptr2) { const ecs_hashed_string_t *str1 = ptr1; const ecs_hashed_string_t *str2 = ptr2; ecs_size_t len1 = str1->length; ecs_size_t len2 = str2->length; if (len1 != len2) { return (len1 > len2) - (len1 < len2); } return ecs_os_memcmp(str1->value, str2->value, len1); } void flecs_name_index_init( ecs_hashmap_t *hm, ecs_allocator_t *allocator) { _flecs_hashmap_init(hm, ECS_SIZEOF(ecs_hashed_string_t), ECS_SIZEOF(uint64_t), flecs_name_index_hash, flecs_name_index_compare, allocator); } 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 = (char*)name, .length = length, .hash = hash }; } const uint64_t* flecs_name_index_find_ptr( const ecs_hashmap_t *map, const char *name, ecs_size_t length, uint64_t hash) { ecs_hashed_string_t hs = flecs_get_hashed_string(name, length, hash); ecs_hm_bucket_t *b = flecs_hashmap_get_bucket(map, hs.hash); if (!b) { return NULL; } ecs_hashed_string_t *keys = ecs_vec_first(&b->keys); int32_t i, count = ecs_vec_count(&b->keys); for (i = 0; i < count; i ++) { ecs_hashed_string_t *key = &keys[i]; ecs_assert(key->hash == hs.hash, ECS_INTERNAL_ERROR, NULL); if (hs.length != key->length) { continue; } if (!ecs_os_strcmp(name, key->value)) { uint64_t *e = ecs_vec_get_t(&b->values, uint64_t, i); ecs_assert(e != NULL, ECS_INTERNAL_ERROR, NULL); return e; } } return NULL; } uint64_t flecs_name_index_find( const ecs_hashmap_t *map, const char *name, ecs_size_t length, uint64_t hash) { const uint64_t *id = flecs_name_index_find_ptr(map, name, length, hash); if (id) { return id[0]; } return 0; } void flecs_name_index_remove( ecs_hashmap_t *map, uint64_t e, uint64_t hash) { ecs_hm_bucket_t *b = flecs_hashmap_get_bucket(map, hash); if (!b) { return; } uint64_t *ids = ecs_vec_first(&b->values); int32_t i, count = ecs_vec_count(&b->values); for (i = 0; i < count; i ++) { if (ids[i] == e) { flecs_hm_bucket_remove(map, b, hash, i); break; } } } void flecs_name_index_update_name( ecs_hashmap_t *map, uint64_t e, uint64_t hash, const char *name) { ecs_hm_bucket_t *b = flecs_hashmap_get_bucket(map, hash); if (!b) { return; } uint64_t *ids = ecs_vec_first(&b->values); int32_t i, count = ecs_vec_count(&b->values); for (i = 0; i < count; i ++) { if (ids[i] == e) { ecs_hashed_string_t *key = ecs_vec_get_t( &b->keys, ecs_hashed_string_t, i); key->value = (char*)name; ecs_assert(ecs_os_strlen(name) == key->length, ECS_INTERNAL_ERROR, NULL); ecs_assert(flecs_hash(name, key->length) == key->hash, ECS_INTERNAL_ERROR, NULL); return; } } /* Record must already have been in the index */ ecs_abort(ECS_INTERNAL_ERROR, NULL); } void flecs_name_index_ensure( ecs_hashmap_t *map, uint64_t id, const char *name, ecs_size_t length, uint64_t hash) { ecs_check(name != NULL, ECS_INVALID_PARAMETER, NULL); ecs_hashed_string_t key = flecs_get_hashed_string(name, length, hash); uint64_t existing = flecs_name_index_find( map, name, key.length, key.hash); if (existing) { if (existing != id) { ecs_abort(ECS_ALREADY_DEFINED, "conflicting id registered with name '%s'", name); } } flecs_hashmap_result_t hmr = flecs_hashmap_ensure( map, &key, uint64_t); *((uint64_t*)hmr.value) = id; error: return; } /** * @file datastructures/hash.c * @brief Functions for hasing byte arrays to 64bit value. */ #ifdef ECS_TARGET_GNU #pragma GCC diagnostic ignored "-Wimplicit-fallthrough" #endif /* See explanation below. The hashing function may read beyond the memory passed * into the hashing function, but only at word boundaries. This should be safe, * but trips up address sanitizers and valgrind. * This ensures clean valgrind logs in debug mode & the best perf in release */ #if !defined(FLECS_NDEBUG) || defined(ADDRESS_SANITIZER) #ifndef VALGRIND #define VALGRIND #endif #endif /* ------------------------------------------------------------------------------- lookup3.c, by Bob Jenkins, May 2006, Public Domain. http://burtleburtle.net/bob/c/lookup3.c ------------------------------------------------------------------------------- */ #ifdef ECS_TARGET_POSIX #include /* attempt to define endianness */ #endif #ifdef ECS_TARGET_LINUX #include /* attempt to define endianness */ #endif /* * My best guess at if you are big-endian or little-endian. This may * need adjustment. */ #if (defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN) && \ __BYTE_ORDER == __LITTLE_ENDIAN) || \ (defined(i386) || defined(__i386__) || defined(__i486__) || \ defined(__i586__) || defined(__i686__) || defined(vax) || defined(MIPSEL)) # define HASH_LITTLE_ENDIAN 1 #elif (defined(__BYTE_ORDER) && defined(__BIG_ENDIAN) && \ __BYTE_ORDER == __BIG_ENDIAN) || \ (defined(sparc) || defined(POWERPC) || defined(mc68000) || defined(sel)) # define HASH_LITTLE_ENDIAN 0 #else # define HASH_LITTLE_ENDIAN 0 #endif #define rot(x,k) (((x)<<(k)) | ((x)>>(32-(k)))) /* ------------------------------------------------------------------------------- mix -- mix 3 32-bit values reversibly. This is reversible, so any information in (a,b,c) before mix() is still in (a,b,c) after mix(). If four pairs of (a,b,c) inputs are run through mix(), or through mix() in reverse, there are at least 32 bits of the output that are sometimes the same for one pair and different for another pair. This was tested for: * pairs that differed by one bit, by two bits, in any combination of top bits of (a,b,c), or in any combination of bottom bits of (a,b,c). * "differ" is defined as +, -, ^, or ~^. For + and -, I transformed the output delta to a Gray code (a^(a>>1)) so a string of 1's (as is commonly produced by subtraction) look like a single 1-bit difference. * the base values were pseudorandom, all zero but one bit set, or all zero plus a counter that starts at zero. Some k values for my "a-=c; a^=rot(c,k); c+=b;" arrangement that satisfy this are 4 6 8 16 19 4 9 15 3 18 27 15 14 9 3 7 17 3 Well, "9 15 3 18 27 15" didn't quite get 32 bits diffing for "differ" defined as + with a one-bit base and a two-bit delta. I used http://burtleburtle.net/bob/hash/avalanche.html to choose the operations, constants, and arrangements of the variables. This does not achieve avalanche. There are input bits of (a,b,c) that fail to affect some output bits of (a,b,c), especially of a. The most thoroughly mixed value is c, but it doesn't really even achieve avalanche in c. This allows some parallelism. Read-after-writes are good at doubling the number of bits affected, so the goal of mixing pulls in the opposite direction as the goal of parallelism. I did what I could. Rotates seem to cost as much as shifts on every machine I could lay my hands on, and rotates are much kinder to the top and bottom bits, so I used rotates. ------------------------------------------------------------------------------- */ #define mix(a,b,c) \ { \ a -= c; a ^= rot(c, 4); c += b; \ b -= a; b ^= rot(a, 6); a += c; \ c -= b; c ^= rot(b, 8); b += a; \ a -= c; a ^= rot(c,16); c += b; \ b -= a; b ^= rot(a,19); a += c; \ c -= b; c ^= rot(b, 4); b += a; \ } /* ------------------------------------------------------------------------------- final -- final mixing of 3 32-bit values (a,b,c) into c Pairs of (a,b,c) values differing in only a few bits will usually produce values of c that look totally different. This was tested for * pairs that differed by one bit, by two bits, in any combination of top bits of (a,b,c), or in any combination of bottom bits of (a,b,c). * "differ" is defined as +, -, ^, or ~^. For + and -, I transformed the output delta to a Gray code (a^(a>>1)) so a string of 1's (as is commonly produced by subtraction) look like a single 1-bit difference. * the base values were pseudorandom, all zero but one bit set, or all zero plus a counter that starts at zero. These constants passed: 14 11 25 16 4 14 24 12 14 25 16 4 14 24 and these came close: 4 8 15 26 3 22 24 10 8 15 26 3 22 24 11 8 15 26 3 22 24 ------------------------------------------------------------------------------- */ #define final(a,b,c) \ { \ c ^= b; c -= rot(b,14); \ a ^= c; a -= rot(c,11); \ b ^= a; b -= rot(a,25); \ c ^= b; c -= rot(b,16); \ a ^= c; a -= rot(c,4); \ b ^= a; b -= rot(a,14); \ c ^= b; c -= rot(b,24); \ } /* * hashlittle2: return 2 32-bit hash values * * This is identical to hashlittle(), except it returns two 32-bit hash * values instead of just one. This is good enough for hash table * lookup with 2^^64 buckets, or if you want a second hash if you're not * happy with the first, or if you want a probably-unique 64-bit ID for * the key. *pc is better mixed than *pb, so use *pc first. If you want * a 64-bit value do something like "*pc + (((uint64_t)*pb)<<32)". */ static void hashlittle2( const void *key, /* the key to hash */ size_t length, /* length of the key */ uint32_t *pc, /* IN: primary initval, OUT: primary hash */ uint32_t *pb) /* IN: secondary initval, OUT: secondary hash */ { uint32_t a,b,c; /* internal state */ union { const void *ptr; size_t i; } u; /* needed for Mac Powerbook G4 */ /* Set up the internal state */ a = b = c = 0xdeadbeef + ((uint32_t)length) + *pc; c += *pb; u.ptr = key; if (HASH_LITTLE_ENDIAN && ((u.i & 0x3) == 0)) { const uint32_t *k = (const uint32_t *)key; /* read 32-bit chunks */ const uint8_t *k8; (void)k8; /*------ all but last block: aligned reads and affect 32 bits of (a,b,c) */ while (length > 12) { a += k[0]; b += k[1]; c += k[2]; mix(a,b,c); length -= 12; k += 3; } /*----------------------------- handle the last (probably partial) block */ /* * "k[2]&0xffffff" actually reads beyond the end of the string, but * then masks off the part it's not allowed to read. Because the * string is aligned, the masked-off tail is in the same word as the * rest of the string. Every machine with memory protection I've seen * does it on word boundaries, so is OK with this. But VALGRIND will * still catch it and complain. The masking trick does make the hash * noticably faster for short strings (like English words). */ #ifndef VALGRIND switch(length) { case 12: c+=k[2]; b+=k[1]; a+=k[0]; break; case 11: c+=k[2]&0xffffff; b+=k[1]; a+=k[0]; break; case 10: c+=k[2]&0xffff; b+=k[1]; a+=k[0]; break; case 9 : c+=k[2]&0xff; b+=k[1]; a+=k[0]; break; case 8 : b+=k[1]; a+=k[0]; break; case 7 : b+=k[1]&0xffffff; a+=k[0]; break; case 6 : b+=k[1]&0xffff; a+=k[0]; break; case 5 : b+=k[1]&0xff; a+=k[0]; break; case 4 : a+=k[0]; break; case 3 : a+=k[0]&0xffffff; break; case 2 : a+=k[0]&0xffff; break; case 1 : a+=k[0]&0xff; break; case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */ } #else /* make valgrind happy */ k8 = (const uint8_t *)k; switch(length) { case 12: c+=k[2]; b+=k[1]; a+=k[0]; break; case 11: c+=((uint32_t)k8[10])<<16; /* fall through */ case 10: c+=((uint32_t)k8[9])<<8; /* fall through */ case 9 : c+=k8[8]; /* fall through */ case 8 : b+=k[1]; a+=k[0]; break; case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */ case 6 : b+=((uint32_t)k8[5])<<8; /* fall through */ case 5 : b+=k8[4]; /* fall through */ case 4 : a+=k[0]; break; case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */ case 2 : a+=((uint32_t)k8[1])<<8; /* fall through */ case 1 : a+=k8[0]; break; case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */ } #endif /* !valgrind */ } else if (HASH_LITTLE_ENDIAN && ((u.i & 0x1) == 0)) { const uint16_t *k = (const uint16_t *)key; /* read 16-bit chunks */ const uint8_t *k8; /*--------------- all but last block: aligned reads and different mixing */ while (length > 12) { a += k[0] + (((uint32_t)k[1])<<16); b += k[2] + (((uint32_t)k[3])<<16); c += k[4] + (((uint32_t)k[5])<<16); mix(a,b,c); length -= 12; k += 6; } /*----------------------------- handle the last (probably partial) block */ k8 = (const uint8_t *)k; switch(length) { case 12: c+=k[4]+(((uint32_t)k[5])<<16); b+=k[2]+(((uint32_t)k[3])<<16); a+=k[0]+(((uint32_t)k[1])<<16); break; case 11: c+=((uint32_t)k8[10])<<16; /* fall through */ case 10: c+=k[4]; b+=k[2]+(((uint32_t)k[3])<<16); a+=k[0]+(((uint32_t)k[1])<<16); break; case 9 : c+=k8[8]; /* fall through */ case 8 : b+=k[2]+(((uint32_t)k[3])<<16); a+=k[0]+(((uint32_t)k[1])<<16); break; case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */ case 6 : b+=k[2]; a+=k[0]+(((uint32_t)k[1])<<16); break; case 5 : b+=k8[4]; /* fall through */ case 4 : a+=k[0]+(((uint32_t)k[1])<<16); break; case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */ case 2 : a+=k[0]; break; case 1 : a+=k8[0]; break; case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */ } } else { /* need to read the key one byte at a time */ const uint8_t *k = (const uint8_t *)key; /*--------------- all but the last block: affect some 32 bits of (a,b,c) */ while (length > 12) { a += k[0]; a += ((uint32_t)k[1])<<8; a += ((uint32_t)k[2])<<16; a += ((uint32_t)k[3])<<24; b += k[4]; b += ((uint32_t)k[5])<<8; b += ((uint32_t)k[6])<<16; b += ((uint32_t)k[7])<<24; c += k[8]; c += ((uint32_t)k[9])<<8; c += ((uint32_t)k[10])<<16; c += ((uint32_t)k[11])<<24; mix(a,b,c); length -= 12; k += 12; } /*-------------------------------- last block: affect all 32 bits of (c) */ switch(length) /* all the case statements fall through */ { case 12: c+=((uint32_t)k[11])<<24; case 11: c+=((uint32_t)k[10])<<16; case 10: c+=((uint32_t)k[9])<<8; case 9 : c+=k[8]; case 8 : b+=((uint32_t)k[7])<<24; case 7 : b+=((uint32_t)k[6])<<16; case 6 : b+=((uint32_t)k[5])<<8; case 5 : b+=k[4]; case 4 : a+=((uint32_t)k[3])<<24; case 3 : a+=((uint32_t)k[2])<<16; case 2 : a+=((uint32_t)k[1])<<8; case 1 : a+=k[0]; break; case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */ } } final(a,b,c); *pc=c; *pb=b; } uint64_t flecs_hash( const void *data, ecs_size_t length) { uint32_t h_1 = 0; uint32_t h_2 = 0; hashlittle2( data, flecs_ito(size_t, length), &h_1, &h_2); #ifndef __clang_analyzer__ uint64_t h_2_shift = (uint64_t)h_2 << 32; #else uint64_t h_2_shift = 0; #endif return h_1 | h_2_shift; } /** * @file datastructures/qsort.c * @brief Quicksort implementation. */ void ecs_qsort( void *base, ecs_size_t nitems, ecs_size_t size, int (*compar)(const void *, const void*)) { void *tmp = ecs_os_alloca(size); /* For swap */ #define LESS(i, j) \ compar(ECS_ELEM(base, size, i), ECS_ELEM(base, size, j)) < 0 #define SWAP(i, j) \ ecs_os_memcpy(tmp, ECS_ELEM(base, size, i), size),\ ecs_os_memcpy(ECS_ELEM(base, size, i), ECS_ELEM(base, size, j), size),\ ecs_os_memcpy(ECS_ELEM(base, size, j), tmp, size) QSORT(nitems, LESS, SWAP); } /** * @file datastructures/bitset.c * @brief Bitset data structure. * * Simple bitset implementation. The bitset allows for storage of arbitrary * numbers of bits. */ static void ensure( ecs_bitset_t *bs, ecs_size_t size) { if (!bs->size) { int32_t new_size = ((size - 1) / 64 + 1) * ECS_SIZEOF(uint64_t); bs->size = ((size - 1) / 64 + 1) * 64; bs->data = ecs_os_calloc(new_size); } else if (size > bs->size) { int32_t prev_size = ((bs->size - 1) / 64 + 1) * ECS_SIZEOF(uint64_t); bs->size = ((size - 1) / 64 + 1) * 64; int32_t new_size = ((size - 1) / 64 + 1) * ECS_SIZEOF(uint64_t); bs->data = ecs_os_realloc(bs->data, new_size); ecs_os_memset(ECS_OFFSET(bs->data, prev_size), 0, new_size - prev_size); } } void flecs_bitset_init( ecs_bitset_t* bs) { bs->size = 0; bs->count = 0; bs->data = NULL; } void flecs_bitset_ensure( ecs_bitset_t *bs, int32_t count) { if (count > bs->count) { bs->count = count; ensure(bs, count); } } void flecs_bitset_fini( ecs_bitset_t *bs) { ecs_os_free(bs->data); bs->data = NULL; bs->count = 0; } void flecs_bitset_addn( ecs_bitset_t *bs, int32_t count) { int32_t elem = bs->count += count; ensure(bs, elem); } void flecs_bitset_set( ecs_bitset_t *bs, int32_t elem, bool value) { ecs_check(elem < bs->count, ECS_INVALID_PARAMETER, NULL); uint32_t hi = ((uint32_t)elem) >> 6; uint32_t lo = ((uint32_t)elem) & 0x3F; uint64_t v = bs->data[hi]; bs->data[hi] = (v & ~((uint64_t)1 << lo)) | ((uint64_t)value << lo); error: return; } bool flecs_bitset_get( const ecs_bitset_t *bs, int32_t elem) { ecs_check(elem < bs->count, ECS_INVALID_PARAMETER, NULL); return !!(bs->data[elem >> 6] & ((uint64_t)1 << ((uint64_t)elem & 0x3F))); error: return false; } int32_t flecs_bitset_count( const ecs_bitset_t *bs) { return bs->count; } void flecs_bitset_remove( ecs_bitset_t *bs, int32_t elem) { ecs_check(elem < bs->count, ECS_INVALID_PARAMETER, NULL); int32_t last = bs->count - 1; bool last_value = flecs_bitset_get(bs, last); flecs_bitset_set(bs, elem, last_value); 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/strbuf.c * @brief Utility for constructing strings. * * A buffer builds up a list of elements which individually can be up to N bytes * large. While appending, data is added to these elements. More elements are * added on the fly when needed. When an application calls ecs_strbuf_get, all * elements are combined in one string and the element administration is freed. * * This approach prevents reallocs of large blocks of memory, and therefore * copying large blocks of memory when appending to a large buffer. A buffer * preallocates some memory for the element overhead so that for small strings * there is hardly any overhead, while for large strings the overhead is offset * by the reduced time spent on copying memory. * * The functionality provided by strbuf is similar to std::stringstream. */ #include /** * stm32tpl -- STM32 C++ Template Peripheral Library * Visit https://github.com/antongus/stm32tpl for new versions * * Copyright (c) 2011-2020 Anton B. Gusev aka AHTOXA */ #define MAX_PRECISION (10) #define EXP_THRESHOLD (3) #define INT64_MAX_F ((double)INT64_MAX) static const double rounders[MAX_PRECISION + 1] = { 0.5, // 0 0.05, // 1 0.005, // 2 0.0005, // 3 0.00005, // 4 0.000005, // 5 0.0000005, // 6 0.00000005, // 7 0.000000005, // 8 0.0000000005, // 9 0.00000000005 // 10 }; static char* flecs_strbuf_itoa( char *buf, int64_t v) { char *ptr = buf; char * p1; char c; if (!v) { *ptr++ = '0'; } else { if (v < 0) { ptr[0] = '-'; ptr ++; v *= -1; } char *p = ptr; while (v) { int64_t vdiv = v / 10; int64_t vmod = v - (vdiv * 10); p[0] = (char)('0' + vmod); p ++; v = vdiv; } p1 = p; while (p > ptr) { c = *--p; *p = *ptr; *ptr++ = c; } ptr = p1; } return ptr; } static int flecs_strbuf_ftoa( ecs_strbuf_t *out, double f, int precision, char nan_delim) { char buf[64]; char * ptr = buf; char c; int64_t intPart; int64_t exp = 0; if (isnan(f)) { if (nan_delim) { ecs_strbuf_appendch(out, nan_delim); ecs_strbuf_appendlit(out, "NaN"); return ecs_strbuf_appendch(out, nan_delim); } else { return ecs_strbuf_appendlit(out, "NaN"); } } if (isinf(f)) { if (nan_delim) { ecs_strbuf_appendch(out, nan_delim); ecs_strbuf_appendlit(out, "Inf"); return ecs_strbuf_appendch(out, nan_delim); } else { return ecs_strbuf_appendlit(out, "Inf"); } } if (precision > MAX_PRECISION) { precision = MAX_PRECISION; } if (f < 0) { f = -f; *ptr++ = '-'; } if (precision < 0) { if (f < 1.0) precision = 6; else if (f < 10.0) precision = 5; else if (f < 100.0) precision = 4; else if (f < 1000.0) precision = 3; else if (f < 10000.0) precision = 2; else if (f < 100000.0) precision = 1; else precision = 0; } if (precision) { f += rounders[precision]; } /* Make sure that number can be represented as 64bit int, increase exp */ while (f > INT64_MAX_F) { f /= 1000 * 1000 * 1000; exp += 9; } intPart = (int64_t)f; f -= (double)intPart; ptr = flecs_strbuf_itoa(ptr, intPart); if (precision) { *ptr++ = '.'; while (precision--) { f *= 10.0; c = (char)f; *ptr++ = (char)('0' + c); f -= c; } } *ptr = 0; /* Remove trailing 0s */ while ((&ptr[-1] != buf) && (ptr[-1] == '0')) { ptr[-1] = '\0'; ptr --; } if (ptr != buf && ptr[-1] == '.') { ptr[-1] = '\0'; ptr --; } /* If 0s before . exceed threshold, convert to exponent to save space * without losing precision. */ char *cur = ptr; while ((&cur[-1] != buf) && (cur[-1] == '0')) { cur --; } if (exp || ((ptr - cur) > EXP_THRESHOLD)) { cur[0] = '\0'; exp += (ptr - cur); ptr = cur; } if (exp) { char *p1 = &buf[1]; if (nan_delim) { ecs_os_memmove(buf + 1, buf, 1 + (ptr - buf)); buf[0] = nan_delim; p1 ++; } /* Make sure that exp starts after first character */ c = p1[0]; if (c) { p1[0] = '.'; do { char t = (++p1)[0]; p1[0] = c; c = t; exp ++; } while (c); ptr = p1 + 1; } else { ptr = p1; } ptr[0] = 'e'; ptr = flecs_strbuf_itoa(ptr + 1, exp); if (nan_delim) { ptr[0] = nan_delim; ptr ++; } ptr[0] = '\0'; } return ecs_strbuf_appendstrn(out, buf, (int32_t)(ptr - buf)); } /* Add an extra element to the buffer */ static void flecs_strbuf_grow( ecs_strbuf_t *b) { /* Allocate new element */ ecs_strbuf_element_embedded *e = ecs_os_malloc_t(ecs_strbuf_element_embedded); b->size += b->current->pos; b->current->next = (ecs_strbuf_element*)e; b->current = (ecs_strbuf_element*)e; b->elementCount ++; e->super.buffer_embedded = true; e->super.buf = e->buf; e->super.pos = 0; e->super.next = NULL; } /* Add an extra dynamic element */ static void flecs_strbuf_grow_str( ecs_strbuf_t *b, char *str, char *alloc_str, int32_t size) { /* Allocate new element */ ecs_strbuf_element_str *e = ecs_os_malloc_t(ecs_strbuf_element_str); b->size += b->current->pos; b->current->next = (ecs_strbuf_element*)e; b->current = (ecs_strbuf_element*)e; b->elementCount ++; e->super.buffer_embedded = false; e->super.pos = size ? size : (int32_t)ecs_os_strlen(str); e->super.next = NULL; e->super.buf = str; e->alloc_str = alloc_str; } static char* flecs_strbuf_ptr( ecs_strbuf_t *b) { if (b->buf) { return &b->buf[b->current->pos]; } else { return &b->current->buf[b->current->pos]; } } /* Compute the amount of space left in the current element */ static int32_t flecs_strbuf_memLeftInCurrentElement( ecs_strbuf_t *b) { if (b->current->buffer_embedded) { return ECS_STRBUF_ELEMENT_SIZE - b->current->pos; } else { return 0; } } /* Compute the amount of space left */ static int32_t flecs_strbuf_memLeft( ecs_strbuf_t *b) { if (b->max) { return b->max - b->size - b->current->pos; } else { return INT_MAX; } } static void flecs_strbuf_init( ecs_strbuf_t *b) { /* Initialize buffer structure only once */ if (!b->elementCount) { b->size = 0; b->firstElement.super.next = NULL; b->firstElement.super.pos = 0; b->firstElement.super.buffer_embedded = true; b->firstElement.super.buf = b->firstElement.buf; b->elementCount ++; b->current = (ecs_strbuf_element*)&b->firstElement; } } /* Append a format string to a buffer */ static bool flecs_strbuf_vappend( ecs_strbuf_t *b, const char* str, va_list args) { bool result = true; va_list arg_cpy; if (!str) { return result; } flecs_strbuf_init(b); int32_t memLeftInElement = flecs_strbuf_memLeftInCurrentElement(b); int32_t memLeft = flecs_strbuf_memLeft(b); if (!memLeft) { return false; } /* Compute the memory required to add the string to the buffer. If user * provided buffer, use space left in buffer, otherwise use space left in * current element. */ int32_t max_copy = b->buf ? memLeft : memLeftInElement; int32_t memRequired; va_copy(arg_cpy, args); memRequired = vsnprintf( flecs_strbuf_ptr(b), (size_t)(max_copy + 1), str, args); ecs_assert(memRequired != -1, ECS_INTERNAL_ERROR, NULL); if (memRequired <= memLeftInElement) { /* Element was large enough to fit string */ b->current->pos += memRequired; } else if ((memRequired - memLeftInElement) < memLeft) { /* If string is a format string, a new buffer of size memRequired is * needed to re-evaluate the format string and only use the part that * wasn't already copied to the previous element */ if (memRequired <= ECS_STRBUF_ELEMENT_SIZE) { /* Resulting string fits in standard-size buffer. Note that the * entire string needs to fit, not just the remainder, as the * format string cannot be partially evaluated */ flecs_strbuf_grow(b); /* Copy entire string to new buffer */ ecs_os_vsprintf(flecs_strbuf_ptr(b), str, arg_cpy); /* Ignore the part of the string that was copied into the * previous buffer. The string copied into the new buffer could * be memmoved so that only the remainder is left, but that is * most likely more expensive than just keeping the entire * string. */ /* Update position in buffer */ b->current->pos += memRequired; } else { /* Resulting string does not fit in standard-size buffer. * Allocate a new buffer that can hold the entire string. */ char *dst = ecs_os_malloc(memRequired + 1); ecs_os_vsprintf(dst, str, arg_cpy); flecs_strbuf_grow_str(b, dst, dst, memRequired); } } va_end(arg_cpy); return flecs_strbuf_memLeft(b) > 0; } static bool flecs_strbuf_appendstr( ecs_strbuf_t *b, const char* str, int n) { flecs_strbuf_init(b); int32_t memLeftInElement = flecs_strbuf_memLeftInCurrentElement(b); int32_t memLeft = flecs_strbuf_memLeft(b); if (memLeft <= 0) { return false; } /* Never write more than what the buffer can store */ if (n > memLeft) { n = memLeft; } if (n <= memLeftInElement) { /* Element was large enough to fit string */ ecs_os_strncpy(flecs_strbuf_ptr(b), str, n); b->current->pos += n; } else if ((n - memLeftInElement) < memLeft) { ecs_os_strncpy(flecs_strbuf_ptr(b), str, memLeftInElement); /* Element was not large enough, but buffer still has space */ b->current->pos += memLeftInElement; n -= memLeftInElement; /* Current element was too small, copy remainder into new element */ if (n < ECS_STRBUF_ELEMENT_SIZE) { /* A standard-size buffer is large enough for the new string */ flecs_strbuf_grow(b); /* Copy the remainder to the new buffer */ if (n) { /* If a max number of characters to write is set, only a * subset of the string should be copied to the buffer */ ecs_os_strncpy( flecs_strbuf_ptr(b), str + memLeftInElement, (size_t)n); } else { ecs_os_strcpy(flecs_strbuf_ptr(b), str + memLeftInElement); } /* Update to number of characters copied to new buffer */ b->current->pos += n; } else { /* String doesn't fit in a single element, strdup */ char *remainder = ecs_os_strdup(str + memLeftInElement); flecs_strbuf_grow_str(b, remainder, remainder, n); } } else { /* Buffer max has been reached */ return false; } return flecs_strbuf_memLeft(b) > 0; } static bool flecs_strbuf_appendch( ecs_strbuf_t *b, char ch) { flecs_strbuf_init(b); int32_t memLeftInElement = flecs_strbuf_memLeftInCurrentElement(b); int32_t memLeft = flecs_strbuf_memLeft(b); if (memLeft <= 0) { return false; } if (memLeftInElement) { /* Element was large enough to fit string */ flecs_strbuf_ptr(b)[0] = ch; b->current->pos ++; } else { flecs_strbuf_grow(b); flecs_strbuf_ptr(b)[0] = ch; b->current->pos ++; } return flecs_strbuf_memLeft(b) > 0; } bool ecs_strbuf_vappend( ecs_strbuf_t *b, const char* fmt, va_list args) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(fmt != NULL, ECS_INVALID_PARAMETER, NULL); return flecs_strbuf_vappend(b, fmt, args); } bool ecs_strbuf_append( ecs_strbuf_t *b, const char* fmt, ...) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(fmt != NULL, ECS_INVALID_PARAMETER, NULL); va_list args; va_start(args, fmt); bool result = flecs_strbuf_vappend(b, fmt, args); va_end(args); return result; } bool ecs_strbuf_appendstrn( ecs_strbuf_t *b, const char* str, int32_t len) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); return flecs_strbuf_appendstr(b, str, len); } bool ecs_strbuf_appendch( ecs_strbuf_t *b, char ch) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); return flecs_strbuf_appendch(b, ch); } bool ecs_strbuf_appendint( ecs_strbuf_t *b, int64_t v) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); char numbuf[32]; char *ptr = flecs_strbuf_itoa(numbuf, v); return ecs_strbuf_appendstrn(b, numbuf, flecs_ito(int32_t, ptr - numbuf)); } bool ecs_strbuf_appendflt( ecs_strbuf_t *b, double flt, char nan_delim) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); return flecs_strbuf_ftoa(b, flt, 10, nan_delim); } bool ecs_strbuf_appendstr_zerocpy( ecs_strbuf_t *b, char* str) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); flecs_strbuf_init(b); flecs_strbuf_grow_str(b, str, str, 0); return true; } bool ecs_strbuf_appendstr_zerocpyn( ecs_strbuf_t *b, char *str, int32_t n) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); flecs_strbuf_init(b); flecs_strbuf_grow_str(b, str, str, n); return true; } bool ecs_strbuf_appendstr_zerocpy_const( ecs_strbuf_t *b, const char* str) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); /* Removes const modifier, but logic prevents changing / delete string */ flecs_strbuf_init(b); flecs_strbuf_grow_str(b, (char*)str, NULL, 0); return true; } bool ecs_strbuf_appendstr_zerocpyn_const( ecs_strbuf_t *b, const char *str, int32_t n) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); /* Removes const modifier, but logic prevents changing / delete string */ flecs_strbuf_init(b); flecs_strbuf_grow_str(b, (char*)str, NULL, n); return true; } bool ecs_strbuf_appendstr( ecs_strbuf_t *b, const char* str) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); return flecs_strbuf_appendstr(b, str, ecs_os_strlen(str)); } bool ecs_strbuf_mergebuff( ecs_strbuf_t *dst_buffer, ecs_strbuf_t *src_buffer) { if (src_buffer->elementCount) { if (src_buffer->buf) { return ecs_strbuf_appendstrn( dst_buffer, src_buffer->buf, src_buffer->length); } else { ecs_strbuf_element *e = (ecs_strbuf_element*)&src_buffer->firstElement; /* Copy first element as it is inlined in the src buffer */ ecs_strbuf_appendstrn(dst_buffer, e->buf, e->pos); while ((e = e->next)) { dst_buffer->current->next = ecs_os_malloc(sizeof(ecs_strbuf_element)); *dst_buffer->current->next = *e; } } *src_buffer = ECS_STRBUF_INIT; } return true; } char* ecs_strbuf_get( ecs_strbuf_t *b) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); char* result = NULL; if (b->elementCount) { if (b->buf) { b->buf[b->current->pos] = '\0'; result = ecs_os_strdup(b->buf); } else { void *next = NULL; int32_t len = b->size + b->current->pos + 1; ecs_strbuf_element *e = (ecs_strbuf_element*)&b->firstElement; result = ecs_os_malloc(len); char* ptr = result; do { ecs_os_memcpy(ptr, e->buf, e->pos); ptr += e->pos; next = e->next; if (e != &b->firstElement.super) { if (!e->buffer_embedded) { ecs_os_free(((ecs_strbuf_element_str*)e)->alloc_str); } ecs_os_free(e); } } while ((e = next)); result[len - 1] = '\0'; b->length = len; } } else { result = NULL; } b->elementCount = 0; b->content = result; return result; } char *ecs_strbuf_get_small( ecs_strbuf_t *b) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); int32_t written = ecs_strbuf_written(b); ecs_assert(written <= ECS_STRBUF_ELEMENT_SIZE, ECS_INVALID_OPERATION, NULL); char *buf = b->firstElement.buf; buf[written] = '\0'; return buf; } void ecs_strbuf_reset( ecs_strbuf_t *b) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); if (b->elementCount && !b->buf) { void *next = NULL; ecs_strbuf_element *e = (ecs_strbuf_element*)&b->firstElement; do { next = e->next; if (e != (ecs_strbuf_element*)&b->firstElement) { ecs_os_free(e); } } while ((e = next)); } *b = ECS_STRBUF_INIT; } void ecs_strbuf_list_push( ecs_strbuf_t *b, const char *list_open, const char *separator) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(list_open != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(separator != NULL, ECS_INVALID_PARAMETER, NULL); b->list_sp ++; b->list_stack[b->list_sp].count = 0; b->list_stack[b->list_sp].separator = separator; if (list_open) { char ch = list_open[0]; if (ch && !list_open[1]) { ecs_strbuf_appendch(b, ch); } else { ecs_strbuf_appendstr(b, list_open); } } } void ecs_strbuf_list_pop( ecs_strbuf_t *b, const char *list_close) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(list_close != NULL, ECS_INVALID_PARAMETER, NULL); b->list_sp --; if (list_close) { char ch = list_close[0]; if (ch && !list_close[1]) { ecs_strbuf_appendch(b, list_close[0]); } else { ecs_strbuf_appendstr(b, list_close); } } } void ecs_strbuf_list_next( ecs_strbuf_t *b) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); int32_t list_sp = b->list_sp; if (b->list_stack[list_sp].count != 0) { const char *sep = b->list_stack[list_sp].separator; if (sep && !sep[1]) { ecs_strbuf_appendch(b, sep[0]); } else { ecs_strbuf_appendstr(b, sep); } } b->list_stack[list_sp].count ++; } bool ecs_strbuf_list_appendch( ecs_strbuf_t *b, char ch) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); ecs_strbuf_list_next(b); return flecs_strbuf_appendch(b, ch); } bool ecs_strbuf_list_append( ecs_strbuf_t *b, const char *fmt, ...) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(fmt != NULL, ECS_INVALID_PARAMETER, NULL); ecs_strbuf_list_next(b); va_list args; va_start(args, fmt); bool result = flecs_strbuf_vappend(b, fmt, args); va_end(args); return result; } bool ecs_strbuf_list_appendstr( ecs_strbuf_t *b, const char *str) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); ecs_strbuf_list_next(b); return ecs_strbuf_appendstr(b, str); } bool ecs_strbuf_list_appendstrn( ecs_strbuf_t *b, const char *str, int32_t n) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(str != NULL, ECS_INVALID_PARAMETER, NULL); ecs_strbuf_list_next(b); return ecs_strbuf_appendstrn(b, str, n); } int32_t ecs_strbuf_written( const ecs_strbuf_t *b) { ecs_assert(b != NULL, ECS_INVALID_PARAMETER, NULL); if (b->current) { return b->size + b->current->pos; } else { return 0; } } /** * @file datastructures/vec.c * @brief Vector with allocator support. */ ecs_vec_t* ecs_vec_init( ecs_allocator_t *allocator, ecs_vec_t *v, ecs_size_t size, int32_t elem_count) { ecs_assert(size != 0, ECS_INVALID_PARAMETER, NULL); v->array = NULL; v->count = 0; if (elem_count) { if (allocator) { v->array = flecs_alloc(allocator, size * elem_count); } else { v->array = ecs_os_malloc(size * elem_count); } } v->size = elem_count; #ifdef FLECS_DEBUG v->elem_size = size; #endif return v; } void ecs_vec_init_if( ecs_vec_t *vec, ecs_size_t size) { ecs_dbg_assert(!vec->elem_size || vec->elem_size == size, ECS_INVALID_PARAMETER, NULL); (void)vec; (void)size; #ifdef FLECS_DEBUG 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_dbg_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_dbg_assert(size == v->elem_size, ECS_INTERNAL_ERROR, NULL); ecs_vec_clear(v); } return v; } void ecs_vec_clear( ecs_vec_t *vec) { vec->count = 0; } ecs_vec_t ecs_vec_copy( ecs_allocator_t *allocator, ecs_vec_t *v, ecs_size_t size) { ecs_dbg_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); void *array; if (allocator) { array = flecs_dup(allocator, size * v->size, v->array); } else { array = ecs_os_memdup(v->array, size * v->size); } return (ecs_vec_t) { .count = v->count, .size = v->size, .array = array #ifdef FLECS_DEBUG , .elem_size = size #endif }; } void ecs_vec_reclaim( ecs_allocator_t *allocator, ecs_vec_t *v, ecs_size_t size) { ecs_dbg_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); int32_t count = v->count; if (count < v->size) { if (count) { if (allocator) { v->array = flecs_realloc( allocator, size * count, size * v->size, v->array); } else { v->array = ecs_os_realloc(v->array, size * count); } v->size = count; } else { ecs_vec_fini(allocator, v, size); } } } void ecs_vec_set_size( ecs_allocator_t *allocator, ecs_vec_t *v, ecs_size_t size, int32_t elem_count) { ecs_dbg_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); if (v->size != elem_count) { if (elem_count < v->count) { elem_count = v->count; } elem_count = flecs_next_pow_of_2(elem_count); if (elem_count < 2) { elem_count = 2; } if (elem_count != v->size) { if (allocator) { v->array = flecs_realloc( allocator, size * elem_count, size * v->size, v->array); } else { v->array = ecs_os_realloc(v->array, size * elem_count); } v->size = elem_count; } } } void ecs_vec_set_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_count( ecs_allocator_t *allocator, ecs_vec_t *v, ecs_size_t size, int32_t elem_count) { ecs_dbg_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); if (v->count != elem_count) { if (v->size < elem_count) { ecs_vec_set_size(allocator, v, size, elem_count); } v->count = elem_count; } } void* ecs_vec_grow( ecs_allocator_t *allocator, ecs_vec_t *v, ecs_size_t size, int32_t elem_count) { ecs_dbg_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); ecs_assert(elem_count > 0, ECS_INTERNAL_ERROR, NULL); int32_t count = v->count; ecs_vec_set_count(allocator, v, size, count + elem_count); return ECS_ELEM(v->array, size, count); } void* ecs_vec_append( ecs_allocator_t *allocator, ecs_vec_t *v, ecs_size_t size) { ecs_dbg_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); int32_t count = v->count; if (v->size == count) { ecs_vec_set_size(allocator, v, size, count + 1); } v->count = count + 1; return ECS_ELEM(v->array, size, count); } void ecs_vec_remove( ecs_vec_t *v, ecs_size_t size, int32_t index) { ecs_dbg_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); ecs_assert(index < v->count, ECS_OUT_OF_RANGE, NULL); if (index == --v->count) { return; } ecs_os_memcpy( ECS_ELEM(v->array, size, index), ECS_ELEM(v->array, size, v->count), size); } void ecs_vec_remove_last( ecs_vec_t *v) { v->count --; } int32_t ecs_vec_count( const ecs_vec_t *v) { return v->count; } int32_t ecs_vec_size( const ecs_vec_t *v) { return v->size; } void* ecs_vec_get( const ecs_vec_t *v, ecs_size_t size, int32_t index) { ecs_dbg_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); ecs_assert(index < v->count, ECS_OUT_OF_RANGE, NULL); return ECS_ELEM(v->array, size, index); } void* ecs_vec_last( const ecs_vec_t *v, ecs_size_t size) { ecs_dbg_assert(!v->elem_size || size == v->elem_size, ECS_INVALID_PARAMETER, NULL); return ECS_ELEM(v->array, size, v->count - 1); } void* ecs_vec_first( const ecs_vec_t *v) { return v->array; } /** * @file datastructures/map.c * @brief Map data structure. * * Map data structure for 64bit keys and dynamic payload size. */ /* The ratio used to determine whether the map should flecs_map_rehash. If * (element_count * ECS_LOAD_FACTOR) > bucket_count, bucket count is increased. */ #define ECS_LOAD_FACTOR (12) #define ECS_BUCKET_END(b, c) ECS_ELEM_T(b, ecs_bucket_t, c) static uint8_t flecs_log2(uint32_t v) { static const uint8_t log2table[32] = {0, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, 22, 25, 3, 30, 8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31}; v |= v >> 1; v |= v >> 2; v |= v >> 4; v |= v >> 8; v |= v >> 16; return log2table[(uint32_t)(v * 0x07C4ACDDU) >> 27]; } /* Get bucket count for number of elements */ static int32_t flecs_map_get_bucket_count( int32_t count) { return flecs_next_pow_of_2((int32_t)(count * ECS_LOAD_FACTOR * 0.1)); } /* Get bucket shift amount for a given bucket count */ static uint8_t flecs_map_get_bucket_shift ( int32_t bucket_count) { return (uint8_t)(64u - flecs_log2((uint32_t)bucket_count)); } /* Get bucket index for provided map key */ static int32_t flecs_map_get_bucket_index( uint16_t bucket_shift, ecs_map_key_t key) { ecs_assert(bucket_shift != 0, ECS_INTERNAL_ERROR, NULL); return (int32_t)((11400714819323198485ull * key) >> bucket_shift); } /* Get bucket for key */ static ecs_bucket_t* flecs_map_get_bucket( const ecs_map_t *map, ecs_map_key_t key) { ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL); int32_t bucket_id = flecs_map_get_bucket_index(map->bucket_shift, key); ecs_assert(bucket_id < map->bucket_count, ECS_INTERNAL_ERROR, NULL); return &map->buckets[bucket_id]; } /* Add element to bucket */ static ecs_map_val_t* flecs_map_bucket_add( ecs_block_allocator_t *allocator, ecs_bucket_t *bucket, ecs_map_key_t key) { ecs_bucket_entry_t *new_entry = flecs_balloc(allocator); new_entry->key = key; new_entry->next = bucket->first; bucket->first = new_entry; return &new_entry->value; } /* Remove element from bucket */ static ecs_map_val_t flecs_map_bucket_remove( ecs_map_t *map, ecs_bucket_t *bucket, ecs_map_key_t key) { ecs_bucket_entry_t *entry; for (entry = bucket->first; entry; entry = entry->next) { if (entry->key == key) { ecs_map_val_t value = entry->value; ecs_bucket_entry_t **next_holder = &bucket->first; while(*next_holder != entry) { next_holder = &(*next_holder)->next; } *next_holder = entry->next; flecs_bfree(map->entry_allocator, entry); map->count --; return value; } } return 0; } /* Free contents of bucket */ static void flecs_map_bucket_clear( ecs_block_allocator_t *allocator, ecs_bucket_t *bucket) { ecs_bucket_entry_t *entry = bucket->first; while(entry) { ecs_bucket_entry_t *next = entry->next; flecs_bfree(allocator, entry); entry = next; } } /* Get payload pointer for key from bucket */ static ecs_map_val_t* flecs_map_bucket_get( ecs_bucket_t *bucket, ecs_map_key_t key) { ecs_bucket_entry_t *entry; for (entry = bucket->first; entry; entry = entry->next) { if (entry->key == key) { return &entry->value; } } return NULL; } /* Grow number of buckets */ static void flecs_map_rehash( ecs_map_t *map, int32_t count) { count = flecs_next_pow_of_2(count); if (count < 2) { count = 2; } ecs_assert(count > map->bucket_count, ECS_INTERNAL_ERROR, NULL); int32_t old_count = map->bucket_count; ecs_bucket_t *buckets = map->buckets, *b, *end = ECS_BUCKET_END(buckets, old_count); if (map->allocator) { map->buckets = flecs_calloc_n(map->allocator, ecs_bucket_t, count); } else { map->buckets = ecs_os_calloc_n(ecs_bucket_t, count); } map->bucket_count = count; map->bucket_shift = flecs_map_get_bucket_shift(count); /* Remap old bucket entries to new buckets */ for (b = buckets; b < end; b++) { ecs_bucket_entry_t* entry; for (entry = b->first; entry;) { ecs_bucket_entry_t* next = entry->next; int32_t bucket_index = flecs_map_get_bucket_index( map->bucket_shift, entry->key); ecs_bucket_t *bucket = &map->buckets[bucket_index]; entry->next = bucket->first; bucket->first = entry; entry = next; } } if (map->allocator) { flecs_free_n(map->allocator, ecs_bucket_t, old_count, buckets); } else { ecs_os_free(buckets); } } void ecs_map_params_init( ecs_map_params_t *params, ecs_allocator_t *allocator) { params->allocator = allocator; flecs_ballocator_init_t(¶ms->entry_allocator, ecs_bucket_entry_t); } void ecs_map_params_fini( ecs_map_params_t *params) { flecs_ballocator_fini(¶ms->entry_allocator); } void ecs_map_init_w_params( ecs_map_t *result, ecs_map_params_t *params) { ecs_os_zeromem(result); result->allocator = params->allocator; if (params->entry_allocator.chunk_size) { result->entry_allocator = ¶ms->entry_allocator; result->shared_allocator = true; } else { result->entry_allocator = flecs_ballocator_new_t(ecs_bucket_entry_t); } flecs_map_rehash(result, 0); } void ecs_map_init_w_params_if( ecs_map_t *result, ecs_map_params_t *params) { if (!ecs_map_is_init(result)) { ecs_map_init_w_params(result, params); } } void ecs_map_init( ecs_map_t *result, ecs_allocator_t *allocator) { ecs_map_init_w_params(result, &(ecs_map_params_t) { .allocator = allocator }); } void ecs_map_init_if( ecs_map_t *result, ecs_allocator_t *allocator) { if (!ecs_map_is_init(result)) { ecs_map_init(result, allocator); } } void ecs_map_fini( ecs_map_t *map) { if (!ecs_map_is_init(map)) { return; } bool sanitize = false; #ifdef FLECS_SANITIZE sanitize = true; #endif /* Free buckets in sanitized mode, so we can replace the allocator with * regular malloc/free and use asan/valgrind to find memory errors. */ ecs_allocator_t *a = map->allocator; ecs_block_allocator_t *ea = map->entry_allocator; if (map->shared_allocator || sanitize) { ecs_bucket_t *bucket = map->buckets, *end = &bucket[map->bucket_count]; while (bucket != end) { flecs_map_bucket_clear(ea, bucket); bucket ++; } } if (ea && !map->shared_allocator) { flecs_ballocator_free(ea); map->entry_allocator = NULL; } if (a) { flecs_free_n(a, ecs_bucket_t, map->bucket_count, map->buckets); } else { ecs_os_free(map->buckets); } map->bucket_shift = 0; } ecs_map_val_t* ecs_map_get( const ecs_map_t *map, ecs_map_key_t key) { return flecs_map_bucket_get(flecs_map_get_bucket(map, key), key); } void* _ecs_map_get_deref( const ecs_map_t *map, ecs_map_key_t key) { ecs_map_val_t* ptr = flecs_map_bucket_get( flecs_map_get_bucket(map, key), key); if (ptr) { return (void*)ptr[0]; } return NULL; } void ecs_map_insert( ecs_map_t *map, ecs_map_key_t key, ecs_map_val_t value) { ecs_assert(ecs_map_get(map, key) == NULL, ECS_INVALID_PARAMETER, NULL); int32_t map_count = ++map->count; int32_t tgt_bucket_count = flecs_map_get_bucket_count(map_count); int32_t bucket_count = map->bucket_count; if (tgt_bucket_count > bucket_count) { flecs_map_rehash(map, tgt_bucket_count); } ecs_bucket_t *bucket = flecs_map_get_bucket(map, key); flecs_map_bucket_add(map->entry_allocator, bucket, key)[0] = value; } void* ecs_map_insert_alloc( ecs_map_t *map, ecs_size_t elem_size, ecs_map_key_t key) { void *elem = ecs_os_calloc(elem_size); ecs_map_insert_ptr(map, key, elem); return elem; } ecs_map_val_t* ecs_map_ensure( ecs_map_t *map, ecs_map_key_t key) { ecs_bucket_t *bucket = flecs_map_get_bucket(map, key); ecs_map_val_t *result = flecs_map_bucket_get(bucket, key); if (result) { return result; } int32_t map_count = ++map->count; int32_t tgt_bucket_count = flecs_map_get_bucket_count(map_count); int32_t bucket_count = map->bucket_count; if (tgt_bucket_count > bucket_count) { flecs_map_rehash(map, tgt_bucket_count); bucket = flecs_map_get_bucket(map, key); } ecs_map_val_t* v = flecs_map_bucket_add(map->entry_allocator, bucket, key); *v = 0; return v; } void* ecs_map_ensure_alloc( ecs_map_t *map, ecs_size_t elem_size, ecs_map_key_t key) { ecs_map_val_t *val = ecs_map_ensure(map, key); if (!*val) { void *elem = ecs_os_calloc(elem_size); *val = (ecs_map_val_t)elem; return elem; } else { return (void*)*val; } } ecs_map_val_t ecs_map_remove( ecs_map_t *map, ecs_map_key_t key) { return flecs_map_bucket_remove(map, flecs_map_get_bucket(map, key), key); } void ecs_map_remove_free( ecs_map_t *map, ecs_map_key_t key) { ecs_map_val_t val = ecs_map_remove(map, key); if (val) { ecs_os_free((void*)val); } } void ecs_map_clear( ecs_map_t *map) { ecs_assert(map != NULL, ECS_INVALID_PARAMETER, NULL); int32_t i, count = map->bucket_count; for (i = 0; i < count; i ++) { flecs_map_bucket_clear(map->entry_allocator, &map->buckets[i]); } if (map->allocator) { flecs_free_n(map->allocator, ecs_bucket_t, count, map->buckets); } else { ecs_os_free(map->buckets); } map->buckets = NULL; map->bucket_count = 0; map->count = 0; flecs_map_rehash(map, 2); } ecs_map_iter_t ecs_map_iter( const ecs_map_t *map) { if (ecs_map_is_init(map)) { return (ecs_map_iter_t){ .map = map, .bucket = NULL, .entry = NULL }; } else { return (ecs_map_iter_t){ 0 }; } } bool ecs_map_next( ecs_map_iter_t *iter) { const ecs_map_t *map = iter->map; ecs_bucket_t *end; if (!map || (iter->bucket == (end = &map->buckets[map->bucket_count]))) { return false; } ecs_bucket_entry_t *entry = NULL; if (!iter->bucket) { for (iter->bucket = map->buckets; iter->bucket != end; ++iter->bucket) { if (iter->bucket->first) { entry = iter->bucket->first; break; } } if (iter->bucket == end) { return false; } } else if ((entry = iter->entry) == NULL) { do { ++iter->bucket; if (iter->bucket == end) { return false; } } while(!iter->bucket->first); entry = iter->bucket->first; } ecs_assert(entry != NULL, ECS_INTERNAL_ERROR, NULL); iter->entry = entry->next; iter->res = &entry->key; return true; } void ecs_map_copy( ecs_map_t *dst, const ecs_map_t *src) { if (ecs_map_is_init(dst)) { ecs_assert(ecs_map_count(dst) == 0, ECS_INVALID_PARAMETER, NULL); ecs_map_fini(dst); } if (!ecs_map_is_init(src)) { return; } ecs_map_init(dst, src->allocator); ecs_map_iter_t it = ecs_map_iter(src); while (ecs_map_next(&it)) { ecs_map_insert(dst, ecs_map_key(&it), ecs_map_value(&it)); } } /** * @file datastructures/block_allocator.c * @brief Block allocator. * * A block allocator is an allocator for a fixed size that allocates blocks of * memory with N elements of the requested size. */ // #ifdef FLECS_SANITIZE // #define FLECS_USE_OS_ALLOC // #define FLECS_MEMSET_UNINITIALIZED // #endif int64_t ecs_block_allocator_alloc_count = 0; int64_t ecs_block_allocator_free_count = 0; static ecs_block_allocator_chunk_header_t* flecs_balloc_block( ecs_block_allocator_t *allocator) { if (!allocator->chunk_size) { return NULL; } ecs_block_allocator_block_t *block = ecs_os_malloc(ECS_SIZEOF(ecs_block_allocator_block_t) + allocator->block_size); ecs_block_allocator_chunk_header_t *first_chunk = ECS_OFFSET(block, ECS_SIZEOF(ecs_block_allocator_block_t)); block->memory = first_chunk; if (!allocator->block_tail) { ecs_assert(!allocator->block_head, ECS_INTERNAL_ERROR, 0); block->next = NULL; allocator->block_head = block; allocator->block_tail = block; } else { block->next = NULL; allocator->block_tail->next = block; allocator->block_tail = block; } ecs_block_allocator_chunk_header_t *chunk = first_chunk; int32_t i, end; for (i = 0, end = allocator->chunks_per_block - 1; i < end; ++i) { chunk->next = ECS_OFFSET(chunk, allocator->chunk_size); chunk = chunk->next; } ecs_os_linc(&ecs_block_allocator_alloc_count); chunk->next = NULL; return first_chunk; } void flecs_ballocator_init( ecs_block_allocator_t *ba, ecs_size_t size) { ecs_assert(ba != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); ba->data_size = size; #ifdef FLECS_SANITIZE size += ECS_SIZEOF(int64_t); #endif ba->chunk_size = ECS_ALIGN(size, 16); ba->chunks_per_block = ECS_MAX(4096 / ba->chunk_size, 1); ba->block_size = ba->chunks_per_block * ba->chunk_size; ba->head = NULL; ba->block_head = NULL; ba->block_tail = NULL; } ecs_block_allocator_t* flecs_ballocator_new( ecs_size_t size) { ecs_block_allocator_t *result = ecs_os_calloc_t(ecs_block_allocator_t); flecs_ballocator_init(result, size); return result; } void flecs_ballocator_fini( ecs_block_allocator_t *ba) { ecs_assert(ba != NULL, ECS_INTERNAL_ERROR, NULL); #ifdef FLECS_SANITIZE ecs_assert(ba->alloc_count == 0, ECS_LEAK_DETECTED, NULL); #endif ecs_block_allocator_block_t *block; for (block = ba->block_head; block;) { ecs_block_allocator_block_t *next = block->next; ecs_os_free(block); ecs_os_linc(&ecs_block_allocator_free_count); block = next; } ba->block_head = NULL; } void flecs_ballocator_free( ecs_block_allocator_t *ba) { flecs_ballocator_fini(ba); ecs_os_free(ba); } void* flecs_balloc( ecs_block_allocator_t *ba) { void *result; #ifdef FLECS_USE_OS_ALLOC result = ecs_os_malloc(ba->data_size); #else if (!ba) return NULL; if (!ba->head) { ba->head = flecs_balloc_block(ba); } result = ba->head; ba->head = ba->head->next; #ifdef FLECS_SANITIZE ecs_assert(ba->alloc_count >= 0, ECS_INTERNAL_ERROR, "corrupted allocator"); ba->alloc_count ++; *(int64_t*)result = ba->chunk_size; result = ECS_OFFSET(result, ECS_SIZEOF(int64_t)); #endif #endif #ifdef FLECS_MEMSET_UNINITIALIZED ecs_os_memset(result, 0xAA, ba->data_size); #endif return result; } void* flecs_bcalloc( ecs_block_allocator_t *ba) { #ifdef FLECS_USE_OS_ALLOC return ecs_os_calloc(ba->data_size); #endif if (!ba) return NULL; void *result = flecs_balloc(ba); ecs_os_memset(result, 0, ba->data_size); return result; } void flecs_bfree( ecs_block_allocator_t *ba, void *memory) { #ifdef FLECS_USE_OS_ALLOC ecs_os_free(memory); return; #endif if (!ba) { ecs_assert(memory == NULL, ECS_INTERNAL_ERROR, NULL); return; } if (memory == NULL) { return; } #ifdef FLECS_SANITIZE memory = ECS_OFFSET(memory, -ECS_SIZEOF(int64_t)); if (*(int64_t*)memory != ba->chunk_size) { ecs_err("chunk %p returned to wrong allocator " "(chunk = %ub, allocator = %ub)", memory, *(int64_t*)memory, ba->chunk_size); ecs_abort(ECS_INTERNAL_ERROR, NULL); } ba->alloc_count --; #endif ecs_block_allocator_chunk_header_t *chunk = memory; chunk->next = ba->head; ba->head = chunk; ecs_assert(ba->alloc_count >= 0, ECS_INTERNAL_ERROR, "corrupted allocator"); } void* flecs_brealloc( ecs_block_allocator_t *dst, ecs_block_allocator_t *src, void *memory) { void *result; #ifdef FLECS_USE_OS_ALLOC result = ecs_os_realloc(memory, dst->data_size); #else if (dst == src) { return memory; } result = flecs_balloc(dst); if (result && src) { ecs_size_t size = src->data_size; if (dst->data_size < size) { size = dst->data_size; } ecs_os_memcpy(result, memory, size); } flecs_bfree(src, memory); #endif #ifdef FLECS_MEMSET_UNINITIALIZED if (dst && src && (dst->data_size > src->data_size)) { ecs_os_memset(ECS_OFFSET(result, src->data_size), 0xAA, dst->data_size - src->data_size); } else if (dst && !src) { ecs_os_memset(result, 0xAA, dst->data_size); } #endif return result; } void* flecs_bdup( ecs_block_allocator_t *ba, void *memory) { #ifdef FLECS_USE_OS_ALLOC if (memory && ba->chunk_size) { return ecs_os_memdup(memory, ba->data_size); } else { return NULL; } #endif void *result = flecs_balloc(ba); if (result) { ecs_os_memcpy(result, memory, ba->data_size); } return result; } /** * @file datastructures/hashmap.c * @brief Hashmap data structure. * * The hashmap data structure is built on top of the map data structure. Where * the map data structure can only work with 64bit key values, the hashmap can * hash keys of any size, and handles collisions between hashes. */ static int32_t flecs_hashmap_find_key( const ecs_hashmap_t *map, ecs_vec_t *keys, ecs_size_t key_size, const void *key) { int32_t i, count = ecs_vec_count(keys); void *key_array = ecs_vec_first(keys); for (i = 0; i < count; i ++) { void *key_ptr = ECS_OFFSET(key_array, key_size * i); if (map->compare(key_ptr, key) == 0) { return i; } } return -1; } void _flecs_hashmap_init( ecs_hashmap_t *map, ecs_size_t key_size, ecs_size_t value_size, ecs_hash_value_action_t hash, ecs_compare_action_t compare, ecs_allocator_t *allocator) { map->key_size = key_size; map->value_size = value_size; map->hash = hash; map->compare = compare; flecs_ballocator_init_t(&map->bucket_allocator, ecs_hm_bucket_t); ecs_map_init(&map->impl, allocator); } void flecs_hashmap_fini( ecs_hashmap_t *map) { ecs_allocator_t *a = map->impl.allocator; ecs_map_iter_t it = ecs_map_iter(&map->impl); while (ecs_map_next(&it)) { ecs_hm_bucket_t *bucket = ecs_map_ptr(&it); ecs_vec_fini(a, &bucket->keys, map->key_size); ecs_vec_fini(a, &bucket->values, map->value_size); #ifdef FLECS_SANITIZE flecs_bfree(&map->bucket_allocator, bucket); #endif } flecs_ballocator_fini(&map->bucket_allocator); ecs_map_fini(&map->impl); } void flecs_hashmap_copy( ecs_hashmap_t *dst, const ecs_hashmap_t *src) { ecs_assert(dst != src, ECS_INVALID_PARAMETER, NULL); _flecs_hashmap_init(dst, src->key_size, src->value_size, src->hash, src->compare, src->impl.allocator); ecs_map_copy(&dst->impl, &src->impl); ecs_allocator_t *a = dst->impl.allocator; ecs_map_iter_t it = ecs_map_iter(&dst->impl); while (ecs_map_next(&it)) { ecs_hm_bucket_t **bucket_ptr = ecs_map_ref(&it, ecs_hm_bucket_t); ecs_hm_bucket_t *src_bucket = bucket_ptr[0]; ecs_hm_bucket_t *dst_bucket = flecs_balloc(&dst->bucket_allocator); bucket_ptr[0] = dst_bucket; dst_bucket->keys = ecs_vec_copy(a, &src_bucket->keys, dst->key_size); dst_bucket->values = ecs_vec_copy(a, &src_bucket->values, dst->value_size); } } void* _flecs_hashmap_get( const ecs_hashmap_t *map, ecs_size_t key_size, const void *key, ecs_size_t value_size) { ecs_assert(map->key_size == key_size, ECS_INVALID_PARAMETER, NULL); ecs_assert(map->value_size == value_size, ECS_INVALID_PARAMETER, NULL); uint64_t hash = map->hash(key); ecs_hm_bucket_t *bucket = ecs_map_get_deref(&map->impl, ecs_hm_bucket_t, hash); if (!bucket) { return NULL; } int32_t index = flecs_hashmap_find_key(map, &bucket->keys, key_size, key); if (index == -1) { return NULL; } return ecs_vec_get(&bucket->values, value_size, index); } flecs_hashmap_result_t _flecs_hashmap_ensure( ecs_hashmap_t *map, ecs_size_t key_size, const void *key, ecs_size_t value_size) { ecs_assert(map->key_size == key_size, ECS_INVALID_PARAMETER, NULL); ecs_assert(map->value_size == value_size, ECS_INVALID_PARAMETER, NULL); uint64_t hash = map->hash(key); ecs_hm_bucket_t **r = ecs_map_ensure_ref(&map->impl, ecs_hm_bucket_t, hash); ecs_hm_bucket_t *bucket = r[0]; if (!bucket) { bucket = r[0] = flecs_bcalloc(&map->bucket_allocator); } ecs_allocator_t *a = map->impl.allocator; void *value_ptr, *key_ptr; ecs_vec_t *keys = &bucket->keys; ecs_vec_t *values = &bucket->values; if (!keys->array) { keys = ecs_vec_init(a, &bucket->keys, key_size, 1); values = ecs_vec_init(a, &bucket->values, value_size, 1); key_ptr = ecs_vec_append(a, keys, key_size); value_ptr = ecs_vec_append(a, values, value_size); ecs_os_memcpy(key_ptr, key, key_size); ecs_os_memset(value_ptr, 0, value_size); } else { int32_t index = flecs_hashmap_find_key(map, keys, key_size, key); if (index == -1) { key_ptr = ecs_vec_append(a, keys, key_size); value_ptr = ecs_vec_append(a, values, value_size); ecs_os_memcpy(key_ptr, key, key_size); ecs_os_memset(value_ptr, 0, value_size); } else { key_ptr = ecs_vec_get(keys, key_size, index); value_ptr = ecs_vec_get(values, value_size, index); } } return (flecs_hashmap_result_t){ .key = key_ptr, .value = value_ptr, .hash = hash }; } void _flecs_hashmap_set( ecs_hashmap_t *map, ecs_size_t key_size, void *key, ecs_size_t value_size, const void *value) { void *value_ptr = _flecs_hashmap_ensure(map, key_size, key, value_size).value; ecs_assert(value_ptr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_os_memcpy(value_ptr, value, value_size); } ecs_hm_bucket_t* flecs_hashmap_get_bucket( const ecs_hashmap_t *map, uint64_t hash) { ecs_assert(map != NULL, ECS_INTERNAL_ERROR, NULL); return ecs_map_get_deref(&map->impl, ecs_hm_bucket_t, hash); } void flecs_hm_bucket_remove( ecs_hashmap_t *map, ecs_hm_bucket_t *bucket, uint64_t hash, int32_t index) { ecs_vec_remove(&bucket->keys, map->key_size, index); ecs_vec_remove(&bucket->values, map->value_size, index); if (!ecs_vec_count(&bucket->keys)) { ecs_allocator_t *a = map->impl.allocator; ecs_vec_fini(a, &bucket->keys, map->key_size); ecs_vec_fini(a, &bucket->values, map->value_size); ecs_hm_bucket_t *b = ecs_map_remove_ptr(&map->impl, hash); ecs_assert(bucket == b, ECS_INTERNAL_ERROR, NULL); (void)b; flecs_bfree(&map->bucket_allocator, bucket); } } void _flecs_hashmap_remove_w_hash( ecs_hashmap_t *map, ecs_size_t key_size, const void *key, ecs_size_t value_size, uint64_t hash) { ecs_assert(map->key_size == key_size, ECS_INVALID_PARAMETER, NULL); ecs_assert(map->value_size == value_size, ECS_INVALID_PARAMETER, NULL); (void)value_size; ecs_hm_bucket_t *bucket = ecs_map_get_deref(&map->impl, ecs_hm_bucket_t, hash); if (!bucket) { return; } int32_t index = flecs_hashmap_find_key(map, &bucket->keys, key_size, key); if (index == -1) { return; } flecs_hm_bucket_remove(map, bucket, hash, index); } void _flecs_hashmap_remove( ecs_hashmap_t *map, ecs_size_t key_size, const void *key, ecs_size_t value_size) { ecs_assert(map->key_size == key_size, ECS_INVALID_PARAMETER, NULL); ecs_assert(map->value_size == value_size, ECS_INVALID_PARAMETER, NULL); uint64_t hash = map->hash(key); _flecs_hashmap_remove_w_hash(map, key_size, key, value_size, hash); } flecs_hashmap_iter_t flecs_hashmap_iter( ecs_hashmap_t *map) { return (flecs_hashmap_iter_t){ .it = ecs_map_iter(&map->impl) }; } void* _flecs_hashmap_next( flecs_hashmap_iter_t *it, ecs_size_t key_size, void *key_out, ecs_size_t value_size) { int32_t index = ++ it->index; ecs_hm_bucket_t *bucket = it->bucket; while (!bucket || it->index >= ecs_vec_count(&bucket->keys)) { ecs_map_next(&it->it); bucket = it->bucket = ecs_map_ptr(&it->it); if (!bucket) { return NULL; } index = it->index = 0; } if (key_out) { *(void**)key_out = ecs_vec_get(&bucket->keys, key_size, index); } return ecs_vec_get(&bucket->values, value_size, index); } /** * @file datastructures/stack_allocator.c * @brief Stack allocator. * * The stack allocator enables pushing and popping values to a stack, and has * a lower overhead when compared to block allocators. A stack allocator is a * good fit for small temporary allocations. * * The stack allocator allocates memory in pages. If the requested size of an * allocation exceeds the page size, a regular allocator is used instead. */ #define FLECS_STACK_PAGE_OFFSET ECS_ALIGN(ECS_SIZEOF(ecs_stack_page_t), 16) int64_t ecs_stack_allocator_alloc_count = 0; int64_t ecs_stack_allocator_free_count = 0; static ecs_stack_page_t* flecs_stack_page_new(uint32_t page_id) { ecs_stack_page_t *result = ecs_os_malloc( FLECS_STACK_PAGE_OFFSET + ECS_STACK_PAGE_SIZE); result->data = ECS_OFFSET(result, FLECS_STACK_PAGE_OFFSET); result->next = NULL; result->id = page_id + 1; ecs_os_linc(&ecs_stack_allocator_alloc_count); return result; } void* flecs_stack_alloc( ecs_stack_t *stack, ecs_size_t size, ecs_size_t align) { ecs_stack_page_t *page = stack->cur; if (page == &stack->first && !page->data) { page->data = ecs_os_malloc(ECS_STACK_PAGE_SIZE); ecs_os_linc(&ecs_stack_allocator_alloc_count); } int16_t sp = flecs_ito(int16_t, ECS_ALIGN(page->sp, align)); int16_t next_sp = flecs_ito(int16_t, sp + size); void *result = NULL; if (next_sp > ECS_STACK_PAGE_SIZE) { if (size > ECS_STACK_PAGE_SIZE) { result = ecs_os_malloc(size); /* Too large for page */ goto done; } if (page->next) { page = page->next; } else { page = page->next = flecs_stack_page_new(page->id); } sp = 0; next_sp = flecs_ito(int16_t, size); stack->cur = page; } page->sp = next_sp; result = ECS_OFFSET(page->data, sp); done: #ifdef FLECS_SANITIZE ecs_os_memset(result, 0xAA, size); #endif return result; } void* flecs_stack_calloc( ecs_stack_t *stack, ecs_size_t size, ecs_size_t align) { void *ptr = flecs_stack_alloc(stack, size, align); ecs_os_memset(ptr, 0, size); return ptr; } void flecs_stack_free( void *ptr, ecs_size_t size) { if (size > ECS_STACK_PAGE_SIZE) { ecs_os_free(ptr); } } ecs_stack_cursor_t flecs_stack_get_cursor( ecs_stack_t *stack) { return (ecs_stack_cursor_t){ .cur = stack->cur, .sp = stack->cur->sp }; } void flecs_stack_restore_cursor( ecs_stack_t *stack, const ecs_stack_cursor_t *cursor) { ecs_stack_page_t *cur = cursor->cur; if (!cur) { return; } if (cur == stack->cur) { if (cursor->sp > stack->cur->sp) { return; } } else if (cur->id > stack->cur->id) { return; } stack->cur = cursor->cur; stack->cur->sp = cursor->sp; } void flecs_stack_reset( ecs_stack_t *stack) { stack->cur = &stack->first; stack->first.sp = 0; } void flecs_stack_init( ecs_stack_t *stack) { ecs_os_zeromem(stack); stack->cur = &stack->first; stack->first.data = NULL; } void flecs_stack_fini( ecs_stack_t *stack) { ecs_stack_page_t *next, *cur = &stack->first; ecs_assert(stack->cur == &stack->first, ECS_LEAK_DETECTED, NULL); ecs_assert(stack->cur->sp == 0, ECS_LEAK_DETECTED, NULL); do { next = cur->next; if (cur == &stack->first) { if (cur->data) { ecs_os_linc(&ecs_stack_allocator_free_count); } ecs_os_free(cur->data); } else { ecs_os_linc(&ecs_stack_allocator_free_count); ecs_os_free(cur); } } while ((cur = next)); } /** * @file entity_filter.c * @brief Filters that are applied to entities in a table. * * After a table has been matched by a query, additional filters may have to * be applied before returning entities to the application. The two scenarios * under which this happens are queries for union relationship pairs (entities * for multiple targets are stored in the same table) and toggles (components * that are enabled/disabled with a bitset). */ static int flecs_entity_filter_find_smallest_term( ecs_table_t *table, ecs_entity_filter_iter_t *iter) { flecs_switch_term_t *sw_terms = ecs_vec_first(&iter->entity_filter->sw_terms); int32_t i, count = ecs_vec_count(&iter->entity_filter->sw_terms); int32_t min = INT_MAX, index = 0; for (i = 0; i < count; i ++) { /* The array with sparse queries for the matched table */ flecs_switch_term_t *sparse_column = &sw_terms[i]; /* Pointer to the switch column struct of the table */ ecs_switch_t *sw = sparse_column->sw_column; /* If the sparse column pointer hadn't been retrieved yet, do it now */ if (!sw) { /* Get the table column index from the signature column index */ int32_t table_column_index = iter->columns[ sparse_column->signature_column_index]; /* Translate the table column index to switch column index */ table_column_index -= table->sw_offset; ecs_assert(table_column_index >= 1, ECS_INTERNAL_ERROR, NULL); /* Get the sparse column */ ecs_data_t *data = &table->data; sw = sparse_column->sw_column = &data->sw_columns[table_column_index - 1]; } /* Find the smallest column */ int32_t case_count = flecs_switch_case_count(sw, sparse_column->sw_case); if (case_count < min) { min = case_count; index = i + 1; } } return index; } static int flecs_entity_filter_switch_next( ecs_table_t *table, ecs_entity_filter_iter_t *iter, bool filter) { bool first_iteration = false; int32_t switch_smallest; if (!(switch_smallest = iter->sw_smallest)) { switch_smallest = iter->sw_smallest = flecs_entity_filter_find_smallest_term(table, iter); first_iteration = true; } switch_smallest -= 1; flecs_switch_term_t *columns = ecs_vec_first(&iter->entity_filter->sw_terms); flecs_switch_term_t *column = &columns[switch_smallest]; ecs_switch_t *sw, *sw_smallest = column->sw_column; ecs_entity_t case_smallest = column->sw_case; /* Find next entity to iterate in sparse column */ int32_t first, sparse_first = iter->sw_offset; if (!filter) { if (first_iteration) { first = flecs_switch_first(sw_smallest, case_smallest); } else { first = flecs_switch_next(sw_smallest, sparse_first); } } else { int32_t cur_first = iter->range.offset, cur_count = iter->range.count; first = cur_first; while (flecs_switch_get(sw_smallest, first) != case_smallest) { first ++; if (first >= (cur_first + cur_count)) { first = -1; break; } } } if (first == -1) { goto done; } /* Check if entity matches with other sparse columns, if any */ int32_t i, count = ecs_vec_count(&iter->entity_filter->sw_terms); do { for (i = 0; i < count; i ++) { if (i == switch_smallest) { /* Already validated this one */ continue; } column = &columns[i]; sw = column->sw_column; if (flecs_switch_get(sw, first) != column->sw_case) { first = flecs_switch_next(sw_smallest, first); if (first == -1) { goto done; } } } } while (i != count); iter->range.offset = iter->sw_offset = first; iter->range.count = 1; return 0; done: /* Iterated all elements in the sparse list, we should move to the * next matched table. */ iter->sw_smallest = 0; iter->sw_offset = 0; return -1; } #define BS_MAX ((uint64_t)0xFFFFFFFFFFFFFFFF) static int flecs_entity_filter_bitset_next( ecs_table_t *table, ecs_entity_filter_iter_t *iter) { /* Precomputed single-bit test */ static const uint64_t bitmask[64] = { (uint64_t)1 << 0, (uint64_t)1 << 1, (uint64_t)1 << 2, (uint64_t)1 << 3, (uint64_t)1 << 4, (uint64_t)1 << 5, (uint64_t)1 << 6, (uint64_t)1 << 7, (uint64_t)1 << 8, (uint64_t)1 << 9, (uint64_t)1 << 10, (uint64_t)1 << 11, (uint64_t)1 << 12, (uint64_t)1 << 13, (uint64_t)1 << 14, (uint64_t)1 << 15, (uint64_t)1 << 16, (uint64_t)1 << 17, (uint64_t)1 << 18, (uint64_t)1 << 19, (uint64_t)1 << 20, (uint64_t)1 << 21, (uint64_t)1 << 22, (uint64_t)1 << 23, (uint64_t)1 << 24, (uint64_t)1 << 25, (uint64_t)1 << 26, (uint64_t)1 << 27, (uint64_t)1 << 28, (uint64_t)1 << 29, (uint64_t)1 << 30, (uint64_t)1 << 31, (uint64_t)1 << 32, (uint64_t)1 << 33, (uint64_t)1 << 34, (uint64_t)1 << 35, (uint64_t)1 << 36, (uint64_t)1 << 37, (uint64_t)1 << 38, (uint64_t)1 << 39, (uint64_t)1 << 40, (uint64_t)1 << 41, (uint64_t)1 << 42, (uint64_t)1 << 43, (uint64_t)1 << 44, (uint64_t)1 << 45, (uint64_t)1 << 46, (uint64_t)1 << 47, (uint64_t)1 << 48, (uint64_t)1 << 49, (uint64_t)1 << 50, (uint64_t)1 << 51, (uint64_t)1 << 52, (uint64_t)1 << 53, (uint64_t)1 << 54, (uint64_t)1 << 55, (uint64_t)1 << 56, (uint64_t)1 << 57, (uint64_t)1 << 58, (uint64_t)1 << 59, (uint64_t)1 << 60, (uint64_t)1 << 61, (uint64_t)1 << 62, (uint64_t)1 << 63 }; /* Precomputed test to verify if remainder of block is set (or not) */ static const uint64_t bitmask_remain[64] = { BS_MAX, BS_MAX - (BS_MAX >> 63), BS_MAX - (BS_MAX >> 62), BS_MAX - (BS_MAX >> 61), BS_MAX - (BS_MAX >> 60), BS_MAX - (BS_MAX >> 59), BS_MAX - (BS_MAX >> 58), BS_MAX - (BS_MAX >> 57), BS_MAX - (BS_MAX >> 56), BS_MAX - (BS_MAX >> 55), BS_MAX - (BS_MAX >> 54), BS_MAX - (BS_MAX >> 53), BS_MAX - (BS_MAX >> 52), BS_MAX - (BS_MAX >> 51), BS_MAX - (BS_MAX >> 50), BS_MAX - (BS_MAX >> 49), BS_MAX - (BS_MAX >> 48), BS_MAX - (BS_MAX >> 47), BS_MAX - (BS_MAX >> 46), BS_MAX - (BS_MAX >> 45), BS_MAX - (BS_MAX >> 44), BS_MAX - (BS_MAX >> 43), BS_MAX - (BS_MAX >> 42), BS_MAX - (BS_MAX >> 41), BS_MAX - (BS_MAX >> 40), BS_MAX - (BS_MAX >> 39), BS_MAX - (BS_MAX >> 38), BS_MAX - (BS_MAX >> 37), BS_MAX - (BS_MAX >> 36), BS_MAX - (BS_MAX >> 35), BS_MAX - (BS_MAX >> 34), BS_MAX - (BS_MAX >> 33), BS_MAX - (BS_MAX >> 32), BS_MAX - (BS_MAX >> 31), BS_MAX - (BS_MAX >> 30), BS_MAX - (BS_MAX >> 29), BS_MAX - (BS_MAX >> 28), BS_MAX - (BS_MAX >> 27), BS_MAX - (BS_MAX >> 26), BS_MAX - (BS_MAX >> 25), BS_MAX - (BS_MAX >> 24), BS_MAX - (BS_MAX >> 23), BS_MAX - (BS_MAX >> 22), BS_MAX - (BS_MAX >> 21), BS_MAX - (BS_MAX >> 20), BS_MAX - (BS_MAX >> 19), BS_MAX - (BS_MAX >> 18), BS_MAX - (BS_MAX >> 17), BS_MAX - (BS_MAX >> 16), BS_MAX - (BS_MAX >> 15), BS_MAX - (BS_MAX >> 14), BS_MAX - (BS_MAX >> 13), BS_MAX - (BS_MAX >> 12), BS_MAX - (BS_MAX >> 11), BS_MAX - (BS_MAX >> 10), BS_MAX - (BS_MAX >> 9), BS_MAX - (BS_MAX >> 8), BS_MAX - (BS_MAX >> 7), BS_MAX - (BS_MAX >> 6), BS_MAX - (BS_MAX >> 5), BS_MAX - (BS_MAX >> 4), BS_MAX - (BS_MAX >> 3), BS_MAX - (BS_MAX >> 2), BS_MAX - (BS_MAX >> 1) }; int32_t i, count = ecs_vec_count(&iter->entity_filter->bs_terms); flecs_bitset_term_t *terms = ecs_vec_first(&iter->entity_filter->bs_terms); int32_t bs_offset = table->bs_offset; int32_t first = iter->bs_offset; int32_t last = 0; for (i = 0; i < count; i ++) { flecs_bitset_term_t *column = &terms[i]; ecs_bitset_t *bs = terms[i].bs_column; if (!bs) { int32_t index = column->column_index; ecs_assert((index - bs_offset >= 0), ECS_INTERNAL_ERROR, NULL); bs = &table->data.bs_columns[index - bs_offset]; terms[i].bs_column = bs; } int32_t bs_elem_count = bs->count; int32_t bs_block = first >> 6; int32_t bs_block_count = ((bs_elem_count - 1) >> 6) + 1; if (bs_block >= bs_block_count) { goto done; } uint64_t *data = bs->data; int32_t bs_start = first & 0x3F; /* Step 1: find the first non-empty block */ uint64_t v = data[bs_block]; uint64_t remain = bitmask_remain[bs_start]; while (!(v & remain)) { /* If no elements are remaining, move to next block */ if ((++bs_block) >= bs_block_count) { /* No non-empty blocks left */ goto done; } bs_start = 0; remain = BS_MAX; /* Test the full block */ v = data[bs_block]; } /* Step 2: find the first non-empty element in the block */ while (!(v & bitmask[bs_start])) { bs_start ++; /* Block was not empty, so bs_start must be smaller than 64 */ ecs_assert(bs_start < 64, ECS_INTERNAL_ERROR, NULL); } /* Step 3: Find number of contiguous enabled elements after start */ int32_t bs_end = bs_start, bs_block_end = bs_block; remain = bitmask_remain[bs_end]; while ((v & remain) == remain) { bs_end = 0; bs_block_end ++; if (bs_block_end == bs_block_count) { break; } v = data[bs_block_end]; remain = BS_MAX; /* Test the full block */ } /* Step 4: find remainder of enabled elements in current block */ if (bs_block_end != bs_block_count) { while ((v & bitmask[bs_end])) { bs_end ++; } } /* Block was not 100% occupied, so bs_start must be smaller than 64 */ ecs_assert(bs_end < 64, ECS_INTERNAL_ERROR, NULL); /* Step 5: translate to element start/end and make sure that each column * range is a subset of the previous one. */ first = bs_block * 64 + bs_start; int32_t cur_last = bs_block_end * 64 + bs_end; /* No enabled elements found in table */ if (first == cur_last) { goto done; } /* If multiple bitsets are evaluated, make sure each subsequent range * is equal or a subset of the previous range */ if (i) { /* If the first element of a subsequent bitset is larger than the * previous last value, start over. */ if (first >= last) { i = -1; continue; } /* Make sure the last element of the range doesn't exceed the last * element of the previous range. */ if (cur_last > last) { cur_last = last; } } last = cur_last; int32_t elem_count = last - first; /* Make sure last element doesn't exceed total number of elements in * the table */ if (elem_count > (bs_elem_count - first)) { elem_count = (bs_elem_count - first); if (!elem_count) { iter->bs_offset = 0; goto done; } } iter->range.offset = first; iter->range.count = elem_count; iter->bs_offset = first; } /* Keep track of last processed element for iteration */ iter->bs_offset = last; return 0; done: iter->sw_smallest = 0; iter->sw_offset = 0; return -1; } #undef BS_MAX void flecs_entity_filter_init( ecs_world_t *world, ecs_entity_filter_t *entity_filter, const ecs_filter_t *filter, const ecs_table_t *table, ecs_id_t *ids, int32_t *columns) { ecs_poly_assert(world, ecs_world_t); ecs_assert(entity_filter != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(filter != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(ids != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(columns != NULL, ECS_INTERNAL_ERROR, NULL); ecs_allocator_t *a = &world->allocator; ecs_vec_t *sw_terms = &entity_filter->sw_terms; ecs_vec_t *bs_terms = &entity_filter->bs_terms; ecs_vec_reset_t(a, sw_terms, flecs_switch_term_t); ecs_vec_reset_t(a, bs_terms, flecs_bitset_term_t); ecs_term_t *terms = filter->terms; int32_t i, term_count = filter->term_count; entity_filter->has_filter = false; /* Look for union fields */ if (table->flags & EcsTableHasUnion) { for (i = 0; i < term_count; i ++) { if (ecs_term_match_0(&terms[i])) { continue; } ecs_id_t id = terms[i].id; if (ECS_HAS_ID_FLAG(id, PAIR) && ECS_PAIR_SECOND(id) == EcsWildcard) { continue; } int32_t field = terms[i].field_index; int32_t column = columns[field]; if (column <= 0) { continue; } ecs_id_t table_id = table->type.array[column - 1]; if (ECS_PAIR_FIRST(table_id) != EcsUnion) { continue; } flecs_switch_term_t *el = ecs_vec_append_t(a, sw_terms, flecs_switch_term_t); el->signature_column_index = field; el->sw_case = ECS_PAIR_SECOND(id); el->sw_column = NULL; ids[field] = id; entity_filter->has_filter = true; } } /* Look for disabled fields */ if (table->flags & EcsTableHasToggle) { for (i = 0; i < term_count; i ++) { if (ecs_term_match_0(&terms[i])) { continue; } int32_t field = terms[i].field_index; ecs_id_t id = ids[field]; ecs_id_t bs_id = ECS_TOGGLE | id; int32_t bs_index = ecs_search(world, table, bs_id, 0); if (bs_index != -1) { flecs_bitset_term_t *bc = ecs_vec_append_t(a, bs_terms, flecs_bitset_term_t); bc->column_index = bs_index; bc->bs_column = NULL; entity_filter->has_filter = true; } } } } void flecs_entity_filter_fini( ecs_world_t *world, ecs_entity_filter_t *entity_filter) { ecs_allocator_t *a = &world->allocator; ecs_vec_fini_t(a, &entity_filter->sw_terms, flecs_switch_term_t); ecs_vec_fini_t(a, &entity_filter->bs_terms, flecs_bitset_term_t); } int flecs_entity_filter_next( ecs_entity_filter_iter_t *it) { ecs_table_t *table = it->range.table; flecs_switch_term_t *sw_terms = ecs_vec_first(&it->entity_filter->sw_terms); flecs_bitset_term_t *bs_terms = ecs_vec_first(&it->entity_filter->bs_terms); ecs_table_range_t *range = &it->range; bool found = false, next_table = true; do { found = false; if (bs_terms) { if (flecs_entity_filter_bitset_next(table, it) == -1) { /* No more enabled components for table */ it->bs_offset = 0; break; } else { found = true; next_table = false; } } if (sw_terms) { if (flecs_entity_filter_switch_next(table, it, found) == -1) { /* No more elements in sparse column */ if (found) { /* Try again */ next_table = true; found = false; } else { /* Nothing found */ it->bs_offset = 0; break; } } else { found = true; next_table = false; it->bs_offset = range->offset + range->count; } } } while (!found); if (!found) { return 1; } else if (next_table) { return 0; } else { return -1; } } /** * @file addons/log.c * @brief Log addon. */ #ifdef FLECS_LOG #include void flecs_colorize_buf( char *msg, bool enable_colors, ecs_strbuf_t *buf) { ecs_assert(msg != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(buf != NULL, ECS_INTERNAL_ERROR, NULL); char *ptr, ch, prev = '\0'; bool isNum = false; char isStr = '\0'; bool isVar = false; bool overrideColor = false; bool autoColor = true; bool dontAppend = false; for (ptr = msg; (ch = *ptr); ptr++) { dontAppend = false; if (!overrideColor) { if (isNum && !isdigit(ch) && !isalpha(ch) && (ch != '.') && (ch != '%')) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); isNum = false; } if (isStr && (isStr == ch) && prev != '\\') { isStr = '\0'; } else if (((ch == '\'') || (ch == '"')) && !isStr && !isalpha(prev) && (prev != '\\')) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_CYAN); isStr = ch; } if ((isdigit(ch) || (ch == '%' && isdigit(prev)) || (ch == '-' && isdigit(ptr[1]))) && !isNum && !isStr && !isVar && !isalpha(prev) && !isdigit(prev) && (prev != '_') && (prev != '.')) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_GREEN); isNum = true; } if (isVar && !isalpha(ch) && !isdigit(ch) && ch != '_') { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); isVar = false; } if (!isStr && !isVar && ch == '$' && isalpha(ptr[1])) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_CYAN); isVar = true; } } if (!isVar && !isStr && !isNum && ch == '#' && ptr[1] == '[') { bool isColor = true; overrideColor = true; /* Custom colors */ if (!ecs_os_strncmp(&ptr[2], "]", ecs_os_strlen("]"))) { autoColor = false; } else if (!ecs_os_strncmp(&ptr[2], "green]", ecs_os_strlen("green]"))) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_GREEN); } else if (!ecs_os_strncmp(&ptr[2], "red]", ecs_os_strlen("red]"))) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_RED); } else if (!ecs_os_strncmp(&ptr[2], "blue]", ecs_os_strlen("red]"))) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_BLUE); } else if (!ecs_os_strncmp(&ptr[2], "magenta]", ecs_os_strlen("magenta]"))) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_MAGENTA); } else if (!ecs_os_strncmp(&ptr[2], "cyan]", ecs_os_strlen("cyan]"))) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_CYAN); } else if (!ecs_os_strncmp(&ptr[2], "yellow]", ecs_os_strlen("yellow]"))) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_YELLOW); } else if (!ecs_os_strncmp(&ptr[2], "grey]", ecs_os_strlen("grey]"))) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_GREY); } else if (!ecs_os_strncmp(&ptr[2], "white]", ecs_os_strlen("white]"))) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); } else if (!ecs_os_strncmp(&ptr[2], "bold]", ecs_os_strlen("bold]"))) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_BOLD); } else if (!ecs_os_strncmp(&ptr[2], "normal]", ecs_os_strlen("normal]"))) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); } else if (!ecs_os_strncmp(&ptr[2], "reset]", ecs_os_strlen("reset]"))) { overrideColor = false; if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); } else { isColor = false; overrideColor = false; } if (isColor) { ptr += 2; while ((ch = *ptr) != ']') ptr ++; dontAppend = true; } if (!autoColor) { overrideColor = true; } } if (ch == '\n') { if (isNum || isStr || isVar || overrideColor) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); overrideColor = false; isNum = false; isStr = false; isVar = false; } } if (!dontAppend) { ecs_strbuf_appendstrn(buf, ptr, 1); } if (!overrideColor) { if (((ch == '\'') || (ch == '"')) && !isStr) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); } } prev = ch; } if (isNum || isStr || isVar || overrideColor) { if (enable_colors) ecs_strbuf_appendlit(buf, ECS_NORMAL); } } void _ecs_printv( int level, const char *file, int32_t line, const char *fmt, va_list args) { (void)level; (void)line; ecs_strbuf_t msg_buf = ECS_STRBUF_INIT; /* Apply color. Even if we don't want color, we still need to call the * colorize function to get rid of the color tags (e.g. #[green]) */ char *msg_nocolor = ecs_vasprintf(fmt, args); flecs_colorize_buf(msg_nocolor, ecs_os_api.flags_ & EcsOsApiLogWithColors, &msg_buf); ecs_os_free(msg_nocolor); char *msg = ecs_strbuf_get(&msg_buf); if (msg) { ecs_os_api.log_(level, file, line, msg); ecs_os_free(msg); } else { ecs_os_api.log_(level, file, line, ""); } } void _ecs_print( int level, const char *file, int32_t line, const char *fmt, ...) { va_list args; va_start(args, fmt); _ecs_printv(level, file, line, fmt, args); va_end(args); } void _ecs_logv( int level, const char *file, int32_t line, const char *fmt, va_list args) { if (level > ecs_os_api.log_level_) { return; } _ecs_printv(level, file, line, fmt, args); } void _ecs_log( int level, const char *file, int32_t line, const char *fmt, ...) { if (level > ecs_os_api.log_level_) { return; } va_list args; va_start(args, fmt); _ecs_printv(level, file, line, fmt, args); va_end(args); } void _ecs_log_push( int32_t level) { if (level <= ecs_os_api.log_level_) { ecs_os_api.log_indent_ ++; } } void _ecs_log_pop( int32_t level) { if (level <= ecs_os_api.log_level_) { ecs_os_api.log_indent_ --; ecs_assert(ecs_os_api.log_indent_ >= 0, ECS_INTERNAL_ERROR, NULL); } } void _ecs_parser_errorv( const char *name, const char *expr, int64_t column_arg, const char *fmt, va_list args) { if (column_arg > 65536) { /* Limit column size, which prevents the code from throwing up when the * function is called with (expr - ptr), and expr is NULL. */ column_arg = 0; } int32_t column = flecs_itoi32(column_arg); if (ecs_os_api.log_level_ >= -2) { ecs_strbuf_t msg_buf = ECS_STRBUF_INIT; ecs_strbuf_vappend(&msg_buf, fmt, args); if (expr) { ecs_strbuf_appendch(&msg_buf, '\n'); /* Find start of line by taking column and looking for the * last occurring newline */ if (column != -1) { const char *ptr = &expr[column]; while (ptr[0] != '\n' && ptr > expr) { ptr --; } if (ptr == expr) { /* ptr is already at start of line */ } else { column -= (int32_t)(ptr - expr + 1); expr = ptr + 1; } } /* Strip newlines from current statement, if any */ char *newline_ptr = strchr(expr, '\n'); if (newline_ptr) { /* Strip newline from expr */ ecs_strbuf_appendstrn(&msg_buf, expr, (int32_t)(newline_ptr - expr)); } else { ecs_strbuf_appendstr(&msg_buf, expr); } ecs_strbuf_appendch(&msg_buf, '\n'); if (column != -1) { int32_t c; for (c = 0; c < column; c ++) { ecs_strbuf_appendch(&msg_buf, ' '); } ecs_strbuf_appendch(&msg_buf, '^'); } } char *msg = ecs_strbuf_get(&msg_buf); ecs_os_err(name, 0, msg); ecs_os_free(msg); } } void _ecs_parser_error( const char *name, const char *expr, int64_t column, const char *fmt, ...) { if (ecs_os_api.log_level_ >= -2) { va_list args; va_start(args, fmt); _ecs_parser_errorv(name, expr, column, fmt, args); va_end(args); } } void _ecs_abort( int32_t err, const char *file, int32_t line, const char *fmt, ...) { if (fmt) { va_list args; va_start(args, fmt); char *msg = ecs_vasprintf(fmt, args); va_end(args); _ecs_fatal(file, line, "%s (%s)", msg, ecs_strerror(err)); ecs_os_free(msg); } else { _ecs_fatal(file, line, "%s", ecs_strerror(err)); } ecs_os_api.log_last_error_ = err; } bool _ecs_assert( bool condition, int32_t err, const char *cond_str, const char *file, int32_t line, const char *fmt, ...) { if (!condition) { if (fmt) { va_list args; va_start(args, fmt); char *msg = ecs_vasprintf(fmt, args); va_end(args); _ecs_fatal(file, line, "assert: %s %s (%s)", cond_str, msg, ecs_strerror(err)); ecs_os_free(msg); } else { _ecs_fatal(file, line, "assert: %s %s", cond_str, ecs_strerror(err)); } ecs_os_api.log_last_error_ = err; } return condition; } void _ecs_deprecated( const char *file, int32_t line, const char *msg) { _ecs_err(file, line, "%s", msg); } bool ecs_should_log(int32_t level) { # if !defined(FLECS_LOG_3) if (level == 3) { return false; } # endif # if !defined(FLECS_LOG_2) if (level == 2) { return false; } # endif # if !defined(FLECS_LOG_1) if (level == 1) { return false; } # endif return level <= ecs_os_api.log_level_; } #define ECS_ERR_STR(code) case code: return &(#code[4]) const char* ecs_strerror( int32_t error_code) { switch (error_code) { ECS_ERR_STR(ECS_INVALID_PARAMETER); ECS_ERR_STR(ECS_NOT_A_COMPONENT); ECS_ERR_STR(ECS_INTERNAL_ERROR); ECS_ERR_STR(ECS_ALREADY_DEFINED); ECS_ERR_STR(ECS_INVALID_COMPONENT_SIZE); ECS_ERR_STR(ECS_INVALID_COMPONENT_ALIGNMENT); ECS_ERR_STR(ECS_NAME_IN_USE); ECS_ERR_STR(ECS_OUT_OF_MEMORY); ECS_ERR_STR(ECS_OPERATION_FAILED); ECS_ERR_STR(ECS_INVALID_CONVERSION); ECS_ERR_STR(ECS_MODULE_UNDEFINED); ECS_ERR_STR(ECS_MISSING_SYMBOL); ECS_ERR_STR(ECS_ALREADY_IN_USE); ECS_ERR_STR(ECS_CYCLE_DETECTED); ECS_ERR_STR(ECS_LEAK_DETECTED); ECS_ERR_STR(ECS_COLUMN_INDEX_OUT_OF_RANGE); ECS_ERR_STR(ECS_COLUMN_IS_NOT_SHARED); ECS_ERR_STR(ECS_COLUMN_IS_SHARED); ECS_ERR_STR(ECS_COLUMN_TYPE_MISMATCH); ECS_ERR_STR(ECS_INVALID_WHILE_READONLY); ECS_ERR_STR(ECS_INVALID_FROM_WORKER); ECS_ERR_STR(ECS_OUT_OF_RANGE); ECS_ERR_STR(ECS_MISSING_OS_API); ECS_ERR_STR(ECS_UNSUPPORTED); ECS_ERR_STR(ECS_ACCESS_VIOLATION); ECS_ERR_STR(ECS_COMPONENT_NOT_REGISTERED); ECS_ERR_STR(ECS_INCONSISTENT_COMPONENT_ID); ECS_ERR_STR(ECS_INCONSISTENT_NAME); ECS_ERR_STR(ECS_INCONSISTENT_COMPONENT_ACTION); ECS_ERR_STR(ECS_INVALID_OPERATION); ECS_ERR_STR(ECS_CONSTRAINT_VIOLATED); ECS_ERR_STR(ECS_LOCKED_STORAGE); ECS_ERR_STR(ECS_ID_IN_USE); } return "unknown error code"; } #else /* Empty bodies for when logging is disabled */ void _ecs_log( int32_t level, const char *file, int32_t line, const char *fmt, ...) { (void)level; (void)file; (void)line; (void)fmt; } void _ecs_parser_error( const char *name, const char *expr, int64_t column, const char *fmt, ...) { (void)name; (void)expr; (void)column; (void)fmt; } void _ecs_parser_errorv( const char *name, const char *expr, int64_t column, const char *fmt, va_list args) { (void)name; (void)expr; (void)column; (void)fmt; (void)args; } void _ecs_abort( int32_t error_code, const char *file, int32_t line, const char *fmt, ...) { (void)error_code; (void)file; (void)line; (void)fmt; } bool _ecs_assert( bool condition, int32_t error_code, const char *condition_str, const char *file, int32_t line, const char *fmt, ...) { (void)condition; (void)error_code; (void)condition_str; (void)file; (void)line; (void)fmt; return true; } #endif int ecs_log_get_level(void) { return ecs_os_api.log_level_; } int ecs_log_set_level( int level) { int prev = level; ecs_os_api.log_level_ = level; return prev; } bool ecs_log_enable_colors( bool enabled) { bool prev = ecs_os_api.flags_ & EcsOsApiLogWithColors; ECS_BIT_COND(ecs_os_api.flags_, EcsOsApiLogWithColors, enabled); return prev; } bool ecs_log_enable_timestamp( bool enabled) { bool prev = ecs_os_api.flags_ & EcsOsApiLogWithTimeStamp; ECS_BIT_COND(ecs_os_api.flags_, EcsOsApiLogWithTimeStamp, enabled); return prev; } bool ecs_log_enable_timedelta( bool enabled) { bool prev = ecs_os_api.flags_ & EcsOsApiLogWithTimeDelta; ECS_BIT_COND(ecs_os_api.flags_, EcsOsApiLogWithTimeDelta, enabled); return prev; } int ecs_log_last_error(void) { int result = ecs_os_api.log_last_error_; ecs_os_api.log_last_error_ = 0; return result; } /** * @file addons/pipeline/worker.c * @brief Functions for running pipelines on one or more threads. */ /** * @file addons/system/system.c * @brief Internal types and functions for system addon. */ #ifndef FLECS_SYSTEM_PRIVATE_H #define FLECS_SYSTEM_PRIVATE_H #ifdef FLECS_SYSTEM #define ecs_system_t_magic (0x65637383) #define ecs_system_t_tag EcsSystem extern ecs_mixins_t ecs_system_t_mixins; typedef struct ecs_system_t { ecs_header_t hdr; ecs_run_action_t run; /* See ecs_system_desc_t */ ecs_iter_action_t action; /* See ecs_system_desc_t */ ecs_query_t *query; /* System query */ ecs_entity_t query_entity; /* Entity associated with query */ ecs_entity_t tick_source; /* Tick source associated with system */ /* Schedule parameters */ bool multi_threaded; bool no_readonly; int64_t invoke_count; /* Number of times system is invoked */ ecs_ftime_t time_spent; /* Time spent on running system */ ecs_ftime_t time_passed; /* Time passed since last invocation */ int64_t last_frame; /* Last frame for which the system was considered */ void *ctx; /* Userdata for system */ void *binding_ctx; /* Optional language binding context */ ecs_ctx_free_t ctx_free; ecs_ctx_free_t binding_ctx_free; /* Mixins */ ecs_world_t *world; ecs_entity_t entity; ecs_poly_dtor_t dtor; } ecs_system_t; /* Invoked when system becomes active / inactive */ void ecs_system_activate( ecs_world_t *world, ecs_entity_t system, bool activate, const ecs_system_t *system_data); /* Internal function to run a system */ ecs_entity_t ecs_run_intern( ecs_world_t *world, ecs_stage_t *stage, ecs_entity_t system, ecs_system_t *system_data, int32_t stage_current, int32_t stage_count, ecs_ftime_t delta_time, int32_t offset, int32_t limit, void *param); #endif #endif #ifdef FLECS_PIPELINE /** * @file addons/pipeline/pipeline.h * @brief Internal functions/types for pipeline addon. */ #ifndef FLECS_PIPELINE_PRIVATE_H #define FLECS_PIPELINE_PRIVATE_H /** Instruction data for pipeline. * This type is the element type in the "ops" vector of a pipeline. */ typedef struct ecs_pipeline_op_t { int32_t offset; /* Offset in systems vector */ int32_t count; /* Number of systems to run before next op */ bool multi_threaded; /* Whether systems can be ran multi threaded */ bool no_readonly; /* Whether systems are staged or not */ } ecs_pipeline_op_t; typedef struct ecs_pipeline_state_t { ecs_query_t *query; /* Pipeline query */ ecs_vec_t ops; /* Pipeline schedule */ ecs_vec_t systems; /* Vector with system ids */ ecs_entity_t last_system; /* Last system ran by pipeline */ ecs_id_record_t *idr_inactive; /* Cached record for quick inactive test */ int32_t match_count; /* Used to track of rebuild is necessary */ int32_t rebuild_count; /* Number of pipeline rebuilds */ ecs_iter_t *iters; /* Iterator for worker(s) */ int32_t iter_count; /* Members for continuing pipeline iteration after pipeline rebuild */ ecs_pipeline_op_t *cur_op; /* Current pipeline op */ int32_t cur_i; /* Index in current result */ int32_t ran_since_merge; /* Index in current op */ bool no_readonly; /* Is pipeline in readonly mode */ } ecs_pipeline_state_t; typedef struct EcsPipeline { /* Stable ptr so threads can safely access while entity/components move */ ecs_pipeline_state_t *state; } EcsPipeline; //////////////////////////////////////////////////////////////////////////////// //// Pipeline API //////////////////////////////////////////////////////////////////////////////// bool flecs_pipeline_update( ecs_world_t *world, ecs_pipeline_state_t *pq, bool start_of_frame); void flecs_run_pipeline( ecs_world_t *world, ecs_pipeline_state_t *pq, ecs_ftime_t delta_time); //////////////////////////////////////////////////////////////////////////////// //// Worker API //////////////////////////////////////////////////////////////////////////////// bool flecs_worker_begin( ecs_world_t *world, ecs_stage_t *stage, ecs_pipeline_state_t *pq, bool start_of_frame); void flecs_worker_end( ecs_world_t *world, ecs_stage_t *stage); bool flecs_worker_sync( ecs_world_t *world, ecs_stage_t *stage, ecs_pipeline_state_t *pq, ecs_pipeline_op_t **cur_op, int32_t *cur_i); void flecs_workers_progress( ecs_world_t *world, ecs_pipeline_state_t *pq, ecs_ftime_t delta_time); #endif typedef struct ecs_worker_state_t { ecs_stage_t *stage; ecs_pipeline_state_t *pq; } ecs_worker_state_t; /* Worker thread */ static void* flecs_worker(void *arg) { ecs_worker_state_t *state = arg; ecs_stage_t *stage = state->stage; ecs_pipeline_state_t *pq = state->pq; ecs_world_t *world = stage->world; ecs_poly_assert(world, ecs_world_t); ecs_poly_assert(stage, ecs_stage_t); ecs_dbg_2("worker %d: start", stage->id); /* Start worker, increase counter so main thread knows how many * workers are ready */ ecs_os_mutex_lock(world->sync_mutex); world->workers_running ++; if (!(world->flags & EcsWorldQuitWorkers)) { ecs_os_cond_wait(world->worker_cond, world->sync_mutex); } ecs_os_mutex_unlock(world->sync_mutex); while (!(world->flags & EcsWorldQuitWorkers)) { ecs_entity_t old_scope = ecs_set_scope((ecs_world_t*)stage, 0); ecs_dbg_3("worker %d: run", stage->id); flecs_run_pipeline((ecs_world_t*)stage, pq, world->info.delta_time); ecs_set_scope((ecs_world_t*)stage, old_scope); } ecs_dbg_2("worker %d: finalizing", stage->id); ecs_os_mutex_lock(world->sync_mutex); world->workers_running --; ecs_os_mutex_unlock(world->sync_mutex); ecs_dbg_2("worker %d: stop", stage->id); ecs_os_free(state); return NULL; } static bool flecs_is_multithreaded( ecs_world_t *world) { ecs_poly_assert(world, ecs_world_t); return ecs_get_stage_count(world) > 1; } static bool flecs_is_main_thread( ecs_stage_t *stage) { return !stage->id; } /* Start threads */ static void flecs_start_workers( ecs_world_t *world, int32_t threads) { ecs_set_stage_count(world, threads); ecs_assert(ecs_get_stage_count(world) == threads, ECS_INTERNAL_ERROR, NULL); int32_t i; for (i = 0; i < threads - 1; i ++) { ecs_stage_t *stage = (ecs_stage_t*)ecs_get_stage(world, i + 1); ecs_assert(stage != NULL, ECS_INTERNAL_ERROR, NULL); ecs_poly_assert(stage, ecs_stage_t); ecs_entity_t pipeline = world->pipeline; ecs_assert(pipeline != 0, ECS_INVALID_OPERATION, NULL); const EcsPipeline *pqc = ecs_get(world, pipeline, EcsPipeline); ecs_assert(pqc != NULL, ECS_INVALID_OPERATION, NULL); ecs_pipeline_state_t *pq = pqc->state; ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); ecs_worker_state_t *state = ecs_os_calloc_t(ecs_worker_state_t); state->stage = stage; state->pq = pq; stage->thread = ecs_os_thread_new(flecs_worker, state); ecs_assert(stage->thread != 0, ECS_OPERATION_FAILED, NULL); } } /* Wait until all workers are running */ static void flecs_wait_for_workers( ecs_world_t *world) { ecs_poly_assert(world, ecs_world_t); int32_t stage_count = ecs_get_stage_count(world); if (stage_count <= 1) { return; } bool wait = true; do { ecs_os_mutex_lock(world->sync_mutex); if (world->workers_running == (stage_count - 1)) { wait = false; } ecs_os_mutex_unlock(world->sync_mutex); } while (wait); } /* Wait until all threads are waiting on sync point */ static void flecs_wait_for_sync( ecs_world_t *world) { int32_t stage_count = ecs_get_stage_count(world); if (stage_count <= 1) { return; } ecs_dbg_3("#[bold]pipeline: waiting for worker sync"); ecs_os_mutex_lock(world->sync_mutex); if (world->workers_waiting != (stage_count - 1)) { ecs_os_cond_wait(world->sync_cond, world->sync_mutex); } /* We shouldn't have been signalled unless all workers are waiting on sync */ ecs_assert(world->workers_waiting == (stage_count - 1), ECS_INTERNAL_ERROR, NULL); world->workers_waiting = 0; ecs_os_mutex_unlock(world->sync_mutex); ecs_dbg_3("#[bold]pipeline: workers synced"); } /* Synchronize workers */ static void flecs_sync_worker( ecs_world_t *world) { int32_t stage_count = ecs_get_stage_count(world); if (stage_count <= 1) { return; } /* Signal that thread is waiting */ ecs_os_mutex_lock(world->sync_mutex); if (++ world->workers_waiting == (stage_count - 1)) { /* Only signal main thread when all threads are waiting */ ecs_os_cond_signal(world->sync_cond); } /* Wait until main thread signals that thread can continue */ ecs_os_cond_wait(world->worker_cond, world->sync_mutex); ecs_os_mutex_unlock(world->sync_mutex); } /* Signal workers that they can start/resume work */ static void flecs_signal_workers( ecs_world_t *world) { int32_t stage_count = ecs_get_stage_count(world); if (stage_count <= 1) { return; } ecs_dbg_3("#[bold]pipeline: signal workers"); ecs_os_mutex_lock(world->sync_mutex); ecs_os_cond_broadcast(world->worker_cond); ecs_os_mutex_unlock(world->sync_mutex); } /** Stop workers */ static bool ecs_stop_threads( ecs_world_t *world) { bool threads_active = false; /* Test if threads are created. Cannot use workers_running, since this is * a potential race if threads haven't spun up yet. */ ecs_stage_t *stages = world->stages; int i, count = world->stage_count; for (i = 1; i < count; i ++) { ecs_stage_t *stage = &stages[i]; if (stage->thread) { threads_active = true; break; } stage->thread = 0; }; /* If no threads are active, just return */ if (!threads_active) { return false; } /* Make sure all threads are running, to ensure they catch the signal */ flecs_wait_for_workers(world); /* Signal threads should quit */ world->flags |= EcsWorldQuitWorkers; flecs_signal_workers(world); /* Join all threads with main */ for (i = 1; i < count; i ++) { ecs_os_thread_join(stages[i].thread); stages[i].thread = 0; } world->flags &= ~EcsWorldQuitWorkers; ecs_assert(world->workers_running == 0, ECS_INTERNAL_ERROR, NULL); /* Deinitialize stages */ ecs_set_stage_count(world, 1); return true; } /* -- Private functions -- */ bool flecs_worker_begin( ecs_world_t *world, ecs_stage_t *stage, ecs_pipeline_state_t *pq, bool start_of_frame) { ecs_poly_assert(world, ecs_world_t); ecs_poly_assert(stage, ecs_stage_t); bool main_thread = flecs_is_main_thread(stage); bool multi_threaded = flecs_is_multithreaded(world); if (main_thread) { if (ecs_stage_is_readonly(world)) { ecs_assert(!pq->no_readonly, ECS_INTERNAL_ERROR, NULL); ecs_readonly_end(world); pq->no_readonly = false; } flecs_pipeline_update(world, pq, start_of_frame); } ecs_pipeline_op_t *cur_op = pq->cur_op; if (main_thread && (cur_op != NULL)) { pq->no_readonly = cur_op->no_readonly; if (!cur_op->no_readonly) { ecs_readonly_begin(world); } ECS_BIT_COND(world->flags, EcsWorldMultiThreaded, cur_op->multi_threaded); ecs_assert(world->workers_waiting == 0, ECS_INTERNAL_ERROR, NULL); } if (main_thread && multi_threaded) { flecs_signal_workers(world); } return pq->cur_op != NULL; } void flecs_worker_end( ecs_world_t *world, ecs_stage_t *stage) { ecs_poly_assert(world, ecs_world_t); ecs_poly_assert(stage, ecs_stage_t); if (flecs_is_multithreaded(world)) { if (flecs_is_main_thread(stage)) { flecs_wait_for_sync(world); } else { flecs_sync_worker(world); } } if (flecs_is_main_thread(stage)) { if (ecs_stage_is_readonly(world)) { ecs_readonly_end(world); } } } bool flecs_worker_sync( ecs_world_t *world, ecs_stage_t *stage, ecs_pipeline_state_t *pq, ecs_pipeline_op_t **cur_op, int32_t *cur_i) { ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(pq->cur_op != NULL, ECS_INTERNAL_ERROR, NULL); bool main_thread = flecs_is_main_thread(stage); /* Synchronize workers */ flecs_worker_end(world, stage); /* Store the current state of the schedule after we synchronized the * threads, to avoid race conditions. */ if (main_thread) { pq->cur_op = *cur_op; pq->cur_i = *cur_i; } /* Prepare state for running the next part of the schedule */ bool result = flecs_worker_begin(world, stage, pq, false); *cur_op = pq->cur_op; *cur_i = pq->cur_i; return result; } void flecs_workers_progress( ecs_world_t *world, ecs_pipeline_state_t *pq, ecs_ftime_t delta_time) { ecs_poly_assert(world, ecs_world_t); ecs_assert(!ecs_is_deferred(world), ECS_INVALID_OPERATION, NULL); /* Make sure workers are running and ready */ flecs_wait_for_workers(world); /* Run pipeline on main thread */ ecs_world_t *stage = ecs_get_stage(world, 0); ecs_entity_t old_scope = ecs_set_scope((ecs_world_t*)stage, 0); flecs_run_pipeline(stage, pq, delta_time); ecs_set_scope((ecs_world_t*)stage, old_scope); } /* -- Public functions -- */ void ecs_set_threads( ecs_world_t *world, int32_t threads) { ecs_assert(threads <= 1 || ecs_os_has_threading(), ECS_MISSING_OS_API, NULL); int32_t stage_count = ecs_get_stage_count(world); if (stage_count != threads) { /* Stop existing threads */ if (stage_count > 1) { if (ecs_stop_threads(world)) { ecs_os_cond_free(world->worker_cond); ecs_os_cond_free(world->sync_cond); ecs_os_mutex_free(world->sync_mutex); } } /* Start threads if number of threads > 1 */ if (threads > 1) { world->worker_cond = ecs_os_cond_new(); world->sync_cond = ecs_os_cond_new(); world->sync_mutex = ecs_os_mutex_new(); flecs_start_workers(world, threads); } } } #endif /** * @file addons/ipeline/pipeline.c * @brief Functions for building and running pipelines. */ #ifdef FLECS_PIPELINE static void flecs_pipeline_free( ecs_pipeline_state_t *p) { if (p) { ecs_world_t *world = p->query->filter.world; ecs_allocator_t *a = &world->allocator; ecs_vec_fini_t(a, &p->ops, ecs_pipeline_op_t); ecs_vec_fini_t(a, &p->systems, ecs_entity_t); ecs_os_free(p->iters); ecs_query_fini(p->query); ecs_os_free(p); } } static ECS_MOVE(EcsPipeline, dst, src, { flecs_pipeline_free(dst->state); dst->state = src->state; src->state = NULL; }) static ECS_DTOR(EcsPipeline, ptr, { flecs_pipeline_free(ptr->state); }) typedef enum ecs_write_kind_t { WriteStateNone = 0, WriteStateToStage, } ecs_write_kind_t; typedef struct ecs_write_state_t { bool write_barrier; ecs_map_t ids; ecs_map_t wildcard_ids; } ecs_write_state_t; static ecs_write_kind_t flecs_pipeline_get_write_state( ecs_write_state_t *write_state, ecs_id_t id) { ecs_write_kind_t result = WriteStateNone; if (write_state->write_barrier) { /* Any component could have been written */ return WriteStateToStage; } if (id == EcsWildcard) { /* Using a wildcard for id indicates read barrier. Return true if any * components could have been staged */ if (ecs_map_count(&write_state->ids) || ecs_map_count(&write_state->wildcard_ids)) { return WriteStateToStage; } } if (!ecs_id_is_wildcard(id)) { if (ecs_map_get(&write_state->ids, id)) { result = WriteStateToStage; } } else { ecs_map_iter_t it = ecs_map_iter(&write_state->ids); while (ecs_map_next(&it)) { if (ecs_id_match(ecs_map_key(&it), id)) { return WriteStateToStage; } } } if (ecs_map_count(&write_state->wildcard_ids)) { ecs_map_iter_t it = ecs_map_iter(&write_state->wildcard_ids); while (ecs_map_next(&it)) { if (ecs_id_match(id, ecs_map_key(&it))) { return WriteStateToStage; } } } return result; } static void flecs_pipeline_set_write_state( ecs_write_state_t *write_state, ecs_id_t id) { if (id == EcsWildcard) { /* If writing to wildcard, flag all components as written */ write_state->write_barrier = true; return; } ecs_map_t *ids; if (ecs_id_is_wildcard(id)) { ids = &write_state->wildcard_ids; } else { ids = &write_state->ids; } ecs_map_ensure(ids, id)[0] = true; } static void flecs_pipeline_reset_write_state( ecs_write_state_t *write_state) { ecs_map_clear(&write_state->ids); ecs_map_clear(&write_state->wildcard_ids); write_state->write_barrier = false; } static bool flecs_pipeline_check_term( ecs_world_t *world, ecs_term_t *term, bool is_active, ecs_write_state_t *write_state) { (void)world; ecs_term_id_t *src = &term->src; if (src->flags & EcsInOutNone) { return false; } ecs_id_t id = term->id; ecs_oper_kind_t oper = term->oper; ecs_inout_kind_t inout = term->inout; bool from_any = ecs_term_match_0(term); bool from_this = ecs_term_match_this(term); bool is_shared = !from_any && (!from_this || !(src->flags & EcsSelf)); ecs_write_kind_t ws = flecs_pipeline_get_write_state(write_state, id); if (from_this && ws >= WriteStateToStage) { /* A staged write could have happened for an id that's matched on the * main storage. Even if the id isn't read, still insert a merge so that * a write to the main storage after the staged write doesn't get * overwritten. */ return true; } if (inout == EcsInOutDefault) { if (from_any) { /* If no inout kind is specified for terms without a source, this is * not interpreted as a read/write annotation but just a (component) * id that's passed to a system. */ return false; } else if (is_shared) { inout = EcsIn; } else { /* Default for owned terms is InOut */ inout = EcsInOut; } } if (oper == EcsNot && inout == EcsOut) { /* If a Not term is combined with Out, it signals that the system * intends to add a component that the entity doesn't yet have */ from_any = true; } if (from_any) { switch(inout) { case EcsOut: case EcsInOut: if (is_active) { /* Only flag component as written if system is active */ flecs_pipeline_set_write_state(write_state, id); } break; default: break; } switch(inout) { case EcsIn: case EcsInOut: if (ws == WriteStateToStage) { /* If a system does a get/get_mut, the component is fetched from * the main store so it must be merged first */ return true; } default: break; } } return false; } static bool flecs_pipeline_check_terms( ecs_world_t *world, ecs_filter_t *filter, bool is_active, ecs_write_state_t *ws) { bool needs_merge = false; ecs_term_t *terms = filter->terms; int32_t t, term_count = filter->term_count; /* Check This terms first. This way if a term indicating writing to a stage * was added before the term, it won't cause merging. */ for (t = 0; t < term_count; t ++) { ecs_term_t *term = &terms[t]; if (ecs_term_match_this(term)) { needs_merge |= flecs_pipeline_check_term(world, term, is_active, ws); } } /* Now check staged terms */ for (t = 0; t < term_count; t ++) { ecs_term_t *term = &terms[t]; if (!ecs_term_match_this(term)) { needs_merge |= flecs_pipeline_check_term(world, term, is_active, ws); } } return needs_merge; } static EcsPoly* flecs_pipeline_term_system( ecs_iter_t *it) { int32_t index = ecs_search(it->real_world, it->table, ecs_poly_id(EcsSystem), 0); ecs_assert(index != -1, ECS_INTERNAL_ERROR, NULL); EcsPoly *poly = ecs_table_get_column(it->table, index, it->offset); ecs_assert(poly != NULL, ECS_INTERNAL_ERROR, NULL); return poly; } static bool flecs_pipeline_build( ecs_world_t *world, ecs_pipeline_state_t *pq) { ecs_iter_t it = ecs_query_iter(world, pq->query); if (pq->match_count == pq->query->match_count) { /* No need to rebuild the pipeline */ ecs_iter_fini(&it); return false; } world->info.pipeline_build_count_total ++; pq->rebuild_count ++; ecs_allocator_t *a = &world->allocator; ecs_pipeline_op_t *op = NULL; ecs_write_state_t ws = {0}; ecs_map_init(&ws.ids, a); ecs_map_init(&ws.wildcard_ids, a); ecs_vec_reset_t(a, &pq->ops, ecs_pipeline_op_t); ecs_vec_reset_t(a, &pq->systems, ecs_entity_t); bool multi_threaded = false; bool no_readonly = false; bool first = true; /* Iterate systems in pipeline, add ops for running / merging */ while (ecs_query_next(&it)) { EcsPoly *poly = flecs_pipeline_term_system(&it); bool is_active = ecs_table_get_index(world, it.table, EcsEmpty) == -1; int32_t i; for (i = 0; i < it.count; i ++) { ecs_system_t *sys = ecs_poly(poly[i].poly, ecs_system_t); ecs_query_t *q = sys->query; bool needs_merge = false; needs_merge = flecs_pipeline_check_terms( world, &q->filter, is_active, &ws); if (is_active) { if (first) { multi_threaded = sys->multi_threaded; no_readonly = sys->no_readonly; first = false; } if (sys->multi_threaded != multi_threaded) { needs_merge = true; multi_threaded = sys->multi_threaded; } if (sys->no_readonly != no_readonly) { needs_merge = true; no_readonly = sys->no_readonly; } } if (no_readonly) { needs_merge = true; } if (needs_merge) { /* After merge all components will be merged, so reset state */ flecs_pipeline_reset_write_state(&ws); /* An inactive system can insert a merge if one of its * components got written, which could make the system * active. If this is the only system in the pipeline operation, * it results in an empty operation when we get here. If that's * the case, reuse the empty operation for the next op. */ if (op && op->count) { op = NULL; } /* Re-evaluate columns to set write flags if system is active. * If system is inactive, it can't write anything and so it * should not insert unnecessary merges. */ needs_merge = false; if (is_active) { needs_merge = flecs_pipeline_check_terms( world, &q->filter, true, &ws); } /* The component states were just reset, so if we conclude that * another merge is needed something is wrong. */ ecs_assert(needs_merge == false, ECS_INTERNAL_ERROR, NULL); } if (!op) { op = ecs_vec_append_t(a, &pq->ops, ecs_pipeline_op_t); op->offset = ecs_vec_count(&pq->systems); op->count = 0; op->multi_threaded = false; op->no_readonly = false; } /* Don't increase count for inactive systems, as they are ignored by * the query used to run the pipeline. */ if (is_active) { ecs_vec_append_t(a, &pq->systems, ecs_entity_t)[0] = it.entities[i]; if (!op->count) { op->multi_threaded = multi_threaded; op->no_readonly = no_readonly; } op->count ++; } } } if (op && !op->count && ecs_vec_count(&pq->ops) > 1) { ecs_vec_remove_last(&pq->ops); } ecs_map_fini(&ws.ids); ecs_map_fini(&ws.wildcard_ids); op = ecs_vec_first_t(&pq->ops, ecs_pipeline_op_t); if (!op) { ecs_dbg("#[green]pipeline#[reset] is empty"); return true; } else { /* Add schedule to debug tracing */ ecs_dbg("#[bold]pipeline rebuild"); ecs_log_push_1(); ecs_dbg("#[green]schedule#[reset]: threading: %d, staging: %d:", op->multi_threaded, !op->no_readonly); ecs_log_push_1(); int32_t i, count = ecs_vec_count(&pq->systems); int32_t op_index = 0, ran_since_merge = 0; ecs_entity_t *systems = ecs_vec_first_t(&pq->systems, ecs_entity_t); for (i = 0; i < count; i ++) { ecs_entity_t system = systems[i]; const EcsPoly *poly = ecs_get_pair(world, system, EcsPoly, EcsSystem); ecs_assert(poly != NULL, ECS_INTERNAL_ERROR, NULL); ecs_system_t *sys = ecs_poly(poly->poly, ecs_system_t); #ifdef FLECS_LOG_1 char *path = ecs_get_fullpath(world, system); const char *doc_name = NULL; #ifdef FLECS_DOC const EcsDocDescription *doc_name_id = ecs_get_pair(world, system, EcsDocDescription, EcsName); if (doc_name_id) { doc_name = doc_name_id->value; } #endif if (doc_name) { ecs_dbg("#[green]system#[reset] %s (%s)", path, doc_name); } else { ecs_dbg("#[green]system#[reset] %s", path); } ecs_os_free(path); #endif ecs_assert(op[op_index].offset + ran_since_merge == i, ECS_INTERNAL_ERROR, NULL); ran_since_merge ++; if (ran_since_merge == op[op_index].count) { ecs_dbg("#[magenta]merge#[reset]"); ecs_log_pop_1(); ran_since_merge = 0; op_index ++; if (op_index < ecs_vec_count(&pq->ops)) { ecs_dbg( "#[green]schedule#[reset]: " "threading: %d, staging: %d:", op[op_index].multi_threaded, !op[op_index].no_readonly); } ecs_log_push_1(); } if (sys->last_frame == (world->info.frame_count_total + 1)) { if (op_index < ecs_vec_count(&pq->ops)) { pq->cur_op = &op[op_index]; pq->cur_i = i; } else { pq->cur_op = NULL; pq->cur_i = 0; } } } ecs_log_pop_1(); ecs_log_pop_1(); } pq->match_count = pq->query->match_count; ecs_assert(pq->cur_op <= ecs_vec_last_t(&pq->ops, ecs_pipeline_op_t), ECS_INTERNAL_ERROR, NULL); return true; } static void flecs_pipeline_next_system( ecs_pipeline_state_t *pq) { if (!pq->cur_op) { return; } pq->cur_i ++; if (pq->cur_i >= (pq->cur_op->offset + pq->cur_op->count)) { pq->cur_op ++; if (pq->cur_op > ecs_vec_last_t(&pq->ops, ecs_pipeline_op_t)) { pq->cur_op = NULL; } } } bool flecs_pipeline_update( ecs_world_t *world, ecs_pipeline_state_t *pq, bool start_of_frame) { ecs_poly_assert(world, ecs_world_t); ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INVALID_OPERATION, NULL); /* If any entity mutations happened that could have affected query matching * notify appropriate queries so caches are up to date. This includes the * pipeline query. */ if (start_of_frame) { ecs_run_aperiodic(world, 0); } ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(pq->query != NULL, ECS_INTERNAL_ERROR, NULL); bool rebuilt = flecs_pipeline_build(world, pq); if (start_of_frame) { /* Initialize iterators */ int32_t i, count = pq->iter_count; for (i = 0; i < count; i ++) { ecs_world_t *stage = ecs_get_stage(world, i); pq->iters[i] = ecs_query_iter(stage, pq->query); } pq->cur_op = ecs_vec_first_t(&pq->ops, ecs_pipeline_op_t); pq->cur_i = 0; } else { flecs_pipeline_next_system(pq); } return rebuilt; } void ecs_run_pipeline( ecs_world_t *world, ecs_entity_t pipeline, ecs_ftime_t delta_time) { if (!pipeline) { pipeline = world->pipeline; } EcsPipeline *pq = (EcsPipeline*)ecs_get(world, pipeline, EcsPipeline); flecs_pipeline_update(world, pq->state, true); flecs_run_pipeline((ecs_world_t*)flecs_stage_from_world(&world), pq->state, delta_time); } void flecs_run_pipeline( ecs_world_t *world, ecs_pipeline_state_t *pq, ecs_ftime_t delta_time) { ecs_assert(world != NULL, ECS_INVALID_OPERATION, NULL); ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(pq->query != NULL, ECS_INTERNAL_ERROR, NULL); ecs_poly_assert(world, ecs_stage_t); ecs_stage_t *stage = flecs_stage_from_world(&world); int32_t stage_index = ecs_get_stage_id(stage->thread_ctx); int32_t stage_count = ecs_get_stage_count(world); if (!flecs_worker_begin(world, stage, pq, true)) { return; } ecs_time_t st = {0}; bool main_thread = !stage_index; bool measure_time = main_thread && (world->flags & EcsWorldMeasureSystemTime); ecs_pipeline_op_t *op = ecs_vec_first_t(&pq->ops, ecs_pipeline_op_t); int32_t i = 0; do { int32_t count = ecs_vec_count(&pq->systems); ecs_entity_t *systems = ecs_vec_first_t(&pq->systems, ecs_entity_t); int32_t ran_since_merge = i - op->offset; if (i == count) { break; } if (measure_time) { ecs_time_measure(&st); } for (; i < count; i ++) { /* Run system if: * - this is the main thread, or if * - the system is multithreaded */ if (main_thread || op->multi_threaded) { ecs_entity_t system = systems[i]; const EcsPoly *poly = ecs_get_pair(world, system, EcsPoly, EcsSystem); ecs_assert(poly != NULL, ECS_INTERNAL_ERROR, NULL); ecs_system_t *sys = ecs_poly(poly->poly, ecs_system_t); /* Keep track of the last frame for which the system has ran, so we * know from where to resume the schedule in case the schedule * changes during a merge. */ sys->last_frame = world->info.frame_count_total + 1; ecs_stage_t *s = NULL; if (!op->no_readonly) { /* If system is no_readonly it operates on the actual world, not * the stage. Only pass stage to system if it's readonly. */ s = stage; } ecs_run_intern(world, s, system, sys, stage_index, stage_count, delta_time, 0, 0, NULL); } world->info.systems_ran_frame ++; ran_since_merge ++; if (ran_since_merge == op->count) { /* Merge */ break; } } if (measure_time) { /* Don't include merge time in system time */ world->info.system_time_total += (ecs_ftime_t)ecs_time_measure(&st); } /* Synchronize workers, rebuild pipeline if necessary. Pass current op * and system index to function, so we know where to resume from. */ } while (flecs_worker_sync(world, stage, pq, &op, &i)); if (measure_time) { world->info.system_time_total += (ecs_ftime_t)ecs_time_measure(&st); } flecs_worker_end(world, stage); return; } static void flecs_run_startup_systems( ecs_world_t *world) { ecs_id_record_t *idr = flecs_id_record_get(world, ecs_dependson(EcsOnStart)); if (!idr || !flecs_table_cache_count(&idr->cache)) { /* Don't bother creating startup pipeline if no systems exist */ return; } ecs_dbg_2("#[bold]startup#[reset]"); ecs_log_push_2(); int32_t stage_count = world->stage_count; world->stage_count = 1; /* Prevents running startup systems on workers */ /* Creating a pipeline is relatively expensive, but this only happens * for the first frame. The startup pipeline is deleted afterwards, which * eliminates the overhead of keeping its query cache in sync. */ ecs_dbg_2("#[bold]create startup pipeline#[reset]"); ecs_log_push_2(); ecs_entity_t start_pip = ecs_pipeline_init(world, &(ecs_pipeline_desc_t){ .query = { .filter.terms = { { .id = EcsSystem }, { .id = EcsPhase, .src.flags = EcsCascade, .src.trav = EcsDependsOn }, { .id = ecs_dependson(EcsOnStart), .src.trav = EcsDependsOn }, { .id = EcsDisabled, .src.flags = EcsUp, .src.trav = EcsDependsOn, .oper = EcsNot }, { .id = EcsDisabled, .src.flags = EcsUp, .src.trav = EcsChildOf, .oper = EcsNot } }, .order_by = flecs_entity_compare } }); ecs_log_pop_2(); /* Run & delete pipeline */ ecs_dbg_2("#[bold]run startup systems#[reset]"); ecs_log_push_2(); ecs_assert(start_pip != 0, ECS_INTERNAL_ERROR, NULL); const EcsPipeline *p = ecs_get(world, start_pip, EcsPipeline); ecs_check(p != NULL, ECS_INVALID_OPERATION, NULL); flecs_workers_progress(world, p->state, 0); ecs_log_pop_2(); ecs_dbg_2("#[bold]delete startup pipeline#[reset]"); ecs_log_push_2(); ecs_delete(world, start_pip); ecs_log_pop_2(); world->stage_count = stage_count; ecs_log_pop_2(); error: return; } bool ecs_progress( ecs_world_t *world, ecs_ftime_t user_delta_time) { ecs_ftime_t delta_time = ecs_frame_begin(world, user_delta_time); /* If this is the first frame, run startup systems */ if (world->info.frame_count_total == 0) { flecs_run_startup_systems(world); } ecs_dbg_3("#[bold]progress#[reset](dt = %.2f)", (double)delta_time); ecs_log_push_3(); const EcsPipeline *p = ecs_get(world, world->pipeline, EcsPipeline); ecs_check(p != NULL, ECS_INVALID_OPERATION, NULL); flecs_workers_progress(world, p->state, delta_time); ecs_log_pop_3(); ecs_frame_end(world); return !ECS_BIT_IS_SET(world->flags, EcsWorldQuit); error: return false; } void ecs_set_time_scale( ecs_world_t *world, ecs_ftime_t scale) { world->info.time_scale = scale; } void ecs_reset_clock( ecs_world_t *world) { world->info.world_time_total = 0; world->info.world_time_total_raw = 0; } void ecs_set_pipeline( ecs_world_t *world, ecs_entity_t pipeline) { ecs_poly_assert(world, ecs_world_t); ecs_check( ecs_get(world, pipeline, EcsPipeline) != NULL, ECS_INVALID_PARAMETER, "not a pipeline"); int32_t thread_count = ecs_get_stage_count(world); if (thread_count > 1) { ecs_set_threads(world, 1); } world->pipeline = pipeline; if (thread_count > 1) { ecs_set_threads(world, thread_count); } error: return; } ecs_entity_t ecs_get_pipeline( const ecs_world_t *world) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); return world->pipeline; error: return 0; } ecs_entity_t ecs_pipeline_init( ecs_world_t *world, const ecs_pipeline_desc_t *desc) { ecs_poly_assert(world, ecs_world_t); ecs_assert(desc != NULL, ECS_INVALID_PARAMETER, NULL); ecs_entity_t result = desc->entity; if (!result) { result = ecs_new(world, 0); } ecs_query_desc_t qd = desc->query; if (!qd.order_by) { qd.order_by = flecs_entity_compare; } qd.filter.entity = result; ecs_query_t *query = ecs_query_init(world, &qd); if (!query) { ecs_delete(world, result); return 0; } ecs_assert(query->filter.terms[0].id == EcsSystem, ECS_INVALID_PARAMETER, NULL); ecs_pipeline_state_t *pq = ecs_os_calloc_t(ecs_pipeline_state_t); pq->query = query; pq->match_count = -1; pq->idr_inactive = flecs_id_record_ensure(world, EcsEmpty); ecs_set(world, result, EcsPipeline, { pq }); return result; } /* -- Module implementation -- */ static void FlecsPipelineFini( ecs_world_t *world, void *ctx) { (void)ctx; if (ecs_get_stage_count(world)) { ecs_set_threads(world, 0); } ecs_assert(world->workers_running == 0, ECS_INTERNAL_ERROR, NULL); } #define flecs_bootstrap_phase(world, phase, depends_on)\ flecs_bootstrap_tag(world, phase);\ _flecs_bootstrap_phase(world, phase, depends_on) static void _flecs_bootstrap_phase( ecs_world_t *world, ecs_entity_t phase, ecs_entity_t depends_on) { ecs_add_id(world, phase, EcsPhase); if (depends_on) { ecs_add_pair(world, phase, EcsDependsOn, depends_on); } } void FlecsPipelineImport( ecs_world_t *world) { ECS_MODULE(world, FlecsPipeline); ECS_IMPORT(world, FlecsSystem); ecs_set_name_prefix(world, "Ecs"); flecs_bootstrap_component(world, EcsPipeline); flecs_bootstrap_tag(world, EcsPhase); /* Create anonymous phases to which the builtin phases will have DependsOn * relationships. This ensures that, for example, EcsOnUpdate doesn't have a * direct DependsOn relationship on EcsPreUpdate, which ensures that when * the EcsPreUpdate phase is disabled, EcsOnUpdate still runs. */ ecs_entity_t phase_0 = ecs_new(world, 0); ecs_entity_t phase_1 = ecs_new_w_pair(world, EcsDependsOn, phase_0); ecs_entity_t phase_2 = ecs_new_w_pair(world, EcsDependsOn, phase_1); ecs_entity_t phase_3 = ecs_new_w_pair(world, EcsDependsOn, phase_2); ecs_entity_t phase_4 = ecs_new_w_pair(world, EcsDependsOn, phase_3); ecs_entity_t phase_5 = ecs_new_w_pair(world, EcsDependsOn, phase_4); ecs_entity_t phase_6 = ecs_new_w_pair(world, EcsDependsOn, phase_5); ecs_entity_t phase_7 = ecs_new_w_pair(world, EcsDependsOn, phase_6); ecs_entity_t phase_8 = ecs_new_w_pair(world, EcsDependsOn, phase_7); flecs_bootstrap_phase(world, EcsOnStart, 0); flecs_bootstrap_phase(world, EcsPreFrame, 0); flecs_bootstrap_phase(world, EcsOnLoad, phase_0); flecs_bootstrap_phase(world, EcsPostLoad, phase_1); flecs_bootstrap_phase(world, EcsPreUpdate, phase_2); flecs_bootstrap_phase(world, EcsOnUpdate, phase_3); flecs_bootstrap_phase(world, EcsOnValidate, phase_4); flecs_bootstrap_phase(world, EcsPostUpdate, phase_5); flecs_bootstrap_phase(world, EcsPreStore, phase_6); flecs_bootstrap_phase(world, EcsOnStore, phase_7); flecs_bootstrap_phase(world, EcsPostFrame, phase_8); ecs_set_hooks(world, EcsPipeline, { .ctor = ecs_default_ctor, .dtor = ecs_dtor(EcsPipeline), .move = ecs_move(EcsPipeline) }); world->pipeline = ecs_pipeline_init(world, &(ecs_pipeline_desc_t){ .entity = ecs_entity(world, { .name = "BuiltinPipeline" }), .query = { .filter.terms = { { .id = EcsSystem }, { .id = EcsPhase, .src.flags = EcsCascade, .src.trav = EcsDependsOn }, { .id = ecs_dependson(EcsOnStart), .src.trav = EcsDependsOn, .oper = EcsNot }, { .id = EcsDisabled, .src.flags = EcsUp, .src.trav = EcsDependsOn, .oper = EcsNot }, { .id = EcsDisabled, .src.flags = EcsUp, .src.trav = EcsChildOf, .oper = EcsNot } }, .order_by = flecs_entity_compare } }); /* Cleanup thread administration when world is destroyed */ ecs_atfini(world, FlecsPipelineFini, NULL); } #endif /** * @file addons/monitor.c * @brief Monitor addon. */ #ifdef FLECS_MONITOR ECS_COMPONENT_DECLARE(FlecsMonitor); ECS_COMPONENT_DECLARE(EcsWorldStats); ECS_COMPONENT_DECLARE(EcsPipelineStats); ecs_entity_t EcsPeriod1s = 0; ecs_entity_t EcsPeriod1m = 0; ecs_entity_t EcsPeriod1h = 0; ecs_entity_t EcsPeriod1d = 0; ecs_entity_t EcsPeriod1w = 0; static int32_t flecs_day_interval_count = 24; static int32_t flecs_week_interval_count = 168; static ECS_COPY(EcsPipelineStats, dst, src, { (void)dst; (void)src; ecs_abort(ECS_INVALID_OPERATION, "cannot copy pipeline stats component"); }) static ECS_MOVE(EcsPipelineStats, dst, src, { ecs_os_memcpy_t(dst, src, EcsPipelineStats); ecs_os_zeromem(src); }) static ECS_DTOR(EcsPipelineStats, ptr, { ecs_pipeline_stats_fini(&ptr->stats); }) static void MonitorStats(ecs_iter_t *it) { ecs_world_t *world = it->real_world; EcsStatsHeader *hdr = ecs_field_w_size(it, 0, 1); ecs_id_t kind = ecs_pair_first(it->world, ecs_field_id(it, 1)); void *stats = ECS_OFFSET_T(hdr, EcsStatsHeader); ecs_ftime_t elapsed = hdr->elapsed; hdr->elapsed += it->delta_time; int32_t t_last = (int32_t)(elapsed * 60); int32_t t_next = (int32_t)(hdr->elapsed * 60); int32_t i, dif = t_last - t_next; ecs_world_stats_t last_world = {0}; ecs_pipeline_stats_t last_pipeline = {0}; void *last = NULL; if (!dif) { /* Copy last value so we can pass it to reduce_last */ if (kind == ecs_id(EcsWorldStats)) { last = &last_world; ecs_world_stats_copy_last(&last_world, stats); } else if (kind == ecs_id(EcsPipelineStats)) { last = &last_pipeline; ecs_pipeline_stats_copy_last(&last_pipeline, stats); } } if (kind == ecs_id(EcsWorldStats)) { ecs_world_stats_get(world, stats); } else if (kind == ecs_id(EcsPipelineStats)) { ecs_pipeline_stats_get(world, ecs_get_pipeline(world), stats); } if (!dif) { /* Still in same interval, combine with last measurement */ hdr->reduce_count ++; if (kind == ecs_id(EcsWorldStats)) { ecs_world_stats_reduce_last(stats, last, hdr->reduce_count); } else if (kind == ecs_id(EcsPipelineStats)) { ecs_pipeline_stats_reduce_last(stats, last, hdr->reduce_count); } } else if (dif > 1) { /* More than 16ms has passed, backfill */ for (i = 1; i < dif; i ++) { if (kind == ecs_id(EcsWorldStats)) { ecs_world_stats_repeat_last(stats); } else if (kind == ecs_id(EcsPipelineStats)) { ecs_world_stats_repeat_last(stats); } } hdr->reduce_count = 0; } if (last && kind == ecs_id(EcsPipelineStats)) { ecs_pipeline_stats_fini(last); } } static void ReduceStats(ecs_iter_t *it) { void *dst = ecs_field_w_size(it, 0, 1); void *src = ecs_field_w_size(it, 0, 2); ecs_id_t kind = ecs_pair_first(it->world, ecs_field_id(it, 1)); dst = ECS_OFFSET_T(dst, EcsStatsHeader); src = ECS_OFFSET_T(src, EcsStatsHeader); if (kind == ecs_id(EcsWorldStats)) { ecs_world_stats_reduce(dst, src); } else if (kind == ecs_id(EcsPipelineStats)) { ecs_pipeline_stats_reduce(dst, src); } } static void AggregateStats(ecs_iter_t *it) { int32_t interval = *(int32_t*)it->ctx; EcsStatsHeader *dst_hdr = ecs_field_w_size(it, 0, 1); EcsStatsHeader *src_hdr = ecs_field_w_size(it, 0, 2); void *dst = ECS_OFFSET_T(dst_hdr, EcsStatsHeader); void *src = ECS_OFFSET_T(src_hdr, EcsStatsHeader); ecs_id_t kind = ecs_pair_first(it->world, ecs_field_id(it, 1)); ecs_world_stats_t last_world = {0}; ecs_pipeline_stats_t last_pipeline = {0}; void *last = NULL; if (dst_hdr->reduce_count != 0) { /* Copy last value so we can pass it to reduce_last */ if (kind == ecs_id(EcsWorldStats)) { last_world.t = 0; ecs_world_stats_copy_last(&last_world, dst); last = &last_world; } else if (kind == ecs_id(EcsPipelineStats)) { last_pipeline.t = 0; ecs_pipeline_stats_copy_last(&last_pipeline, dst); last = &last_pipeline; } } /* Reduce from minutes to the current day */ if (kind == ecs_id(EcsWorldStats)) { ecs_world_stats_reduce(dst, src); } else if (kind == ecs_id(EcsPipelineStats)) { ecs_pipeline_stats_reduce(dst, src); } if (dst_hdr->reduce_count != 0) { if (kind == ecs_id(EcsWorldStats)) { ecs_world_stats_reduce_last(dst, last, dst_hdr->reduce_count); } else if (kind == ecs_id(EcsPipelineStats)) { ecs_pipeline_stats_reduce_last(dst, last, dst_hdr->reduce_count); } } /* A day has 60 24 minute intervals */ dst_hdr->reduce_count ++; if (dst_hdr->reduce_count >= interval) { dst_hdr->reduce_count = 0; } if (last && kind == ecs_id(EcsPipelineStats)) { ecs_pipeline_stats_fini(last); } } static void flecs_stats_monitor_import( ecs_world_t *world, ecs_id_t kind, size_t size) { ecs_entity_t prev = ecs_set_scope(world, kind); // Called each frame, collects 60 measurements per second ecs_system_init(world, &(ecs_system_desc_t){ .entity = ecs_entity(world, { .name = "Monitor1s", .add = {ecs_dependson(EcsPreFrame)} }), .query.filter.terms = {{ .id = ecs_pair(kind, EcsPeriod1s), .src.id = EcsWorld }}, .callback = MonitorStats }); // Called each second, reduces into 60 measurements per minute ecs_entity_t mw1m = ecs_system_init(world, &(ecs_system_desc_t){ .entity = ecs_entity(world, { .name = "Monitor1m", .add = {ecs_dependson(EcsPreFrame)} }), .query.filter.terms = {{ .id = ecs_pair(kind, EcsPeriod1m), .src.id = EcsWorld }, { .id = ecs_pair(kind, EcsPeriod1s), .src.id = EcsWorld }}, .callback = ReduceStats, .interval = 1.0 }); // Called each minute, reduces into 60 measurements per hour ecs_system_init(world, &(ecs_system_desc_t){ .entity = ecs_entity(world, { .name = "Monitor1h", .add = {ecs_dependson(EcsPreFrame)} }), .query.filter.terms = {{ .id = ecs_pair(kind, EcsPeriod1h), .src.id = EcsWorld }, { .id = ecs_pair(kind, EcsPeriod1m), .src.id = EcsWorld }}, .callback = ReduceStats, .rate = 60, .tick_source = mw1m }); // Called each minute, reduces into 60 measurements per day ecs_system_init(world, &(ecs_system_desc_t){ .entity = ecs_entity(world, { .name = "Monitor1d", .add = {ecs_dependson(EcsPreFrame)} }), .query.filter.terms = {{ .id = ecs_pair(kind, EcsPeriod1d), .src.id = EcsWorld }, { .id = ecs_pair(kind, EcsPeriod1m), .src.id = EcsWorld }}, .callback = AggregateStats, .rate = 60, .tick_source = mw1m, .ctx = &flecs_day_interval_count }); // Called each hour, reduces into 60 measurements per week ecs_system_init(world, &(ecs_system_desc_t){ .entity = ecs_entity(world, { .name = "Monitor1w", .add = {ecs_dependson(EcsPreFrame)} }), .query.filter.terms = {{ .id = ecs_pair(kind, EcsPeriod1w), .src.id = EcsWorld }, { .id = ecs_pair(kind, EcsPeriod1h), .src.id = EcsWorld }}, .callback = AggregateStats, .rate = 60, .tick_source = mw1m, .ctx = &flecs_week_interval_count }); ecs_set_scope(world, prev); ecs_set_id(world, EcsWorld, ecs_pair(kind, EcsPeriod1s), size, NULL); ecs_set_id(world, EcsWorld, ecs_pair(kind, EcsPeriod1m), size, NULL); ecs_set_id(world, EcsWorld, ecs_pair(kind, EcsPeriod1h), size, NULL); ecs_set_id(world, EcsWorld, ecs_pair(kind, EcsPeriod1d), size, NULL); ecs_set_id(world, EcsWorld, ecs_pair(kind, EcsPeriod1w), size, NULL); } static void flecs_world_monitor_import( ecs_world_t *world) { ECS_COMPONENT_DEFINE(world, EcsWorldStats); flecs_stats_monitor_import(world, ecs_id(EcsWorldStats), sizeof(EcsWorldStats)); } static void flecs_pipeline_monitor_import( ecs_world_t *world) { ECS_COMPONENT_DEFINE(world, EcsPipelineStats); ecs_set_hooks(world, EcsPipelineStats, { .ctor = ecs_default_ctor, .copy = ecs_copy(EcsPipelineStats), .move = ecs_move(EcsPipelineStats), .dtor = ecs_dtor(EcsPipelineStats) }); flecs_stats_monitor_import(world, ecs_id(EcsPipelineStats), sizeof(EcsPipelineStats)); } void FlecsMonitorImport( ecs_world_t *world) { ECS_MODULE_DEFINE(world, FlecsMonitor); ecs_set_name_prefix(world, "Ecs"); EcsPeriod1s = ecs_new_entity(world, "EcsPeriod1s"); EcsPeriod1m = ecs_new_entity(world, "EcsPeriod1m"); EcsPeriod1h = ecs_new_entity(world, "EcsPeriod1h"); EcsPeriod1d = ecs_new_entity(world, "EcsPeriod1d"); EcsPeriod1w = ecs_new_entity(world, "EcsPeriod1w"); flecs_world_monitor_import(world); flecs_pipeline_monitor_import(world); if (ecs_os_has_time()) { ecs_measure_frame_time(world, true); ecs_measure_system_time(world, true); } } #endif /** * @file addons/timer.c * @brief Timer addon. */ #ifdef FLECS_TIMER static void AddTickSource(ecs_iter_t *it) { int32_t i; for (i = 0; i < it->count; i ++) { ecs_set(it->world, it->entities[i], EcsTickSource, {0}); } } static void ProgressTimers(ecs_iter_t *it) { EcsTimer *timer = ecs_field(it, EcsTimer, 1); EcsTickSource *tick_source = ecs_field(it, EcsTickSource, 2); ecs_assert(timer != NULL, ECS_INTERNAL_ERROR, NULL); int i; for (i = 0; i < it->count; i ++) { tick_source[i].tick = false; if (!timer[i].active) { continue; } const ecs_world_info_t *info = ecs_get_world_info(it->world); ecs_ftime_t time_elapsed = timer[i].time + info->delta_time_raw; ecs_ftime_t timeout = timer[i].timeout; if (time_elapsed >= timeout) { ecs_ftime_t t = time_elapsed - timeout; if (t > timeout) { t = 0; } timer[i].time = t; /* Initialize with remainder */ tick_source[i].tick = true; tick_source[i].time_elapsed = time_elapsed; if (timer[i].single_shot) { timer[i].active = false; } } else { timer[i].time = time_elapsed; } } } static void ProgressRateFilters(ecs_iter_t *it) { EcsRateFilter *filter = ecs_field(it, EcsRateFilter, 1); EcsTickSource *tick_dst = ecs_field(it, EcsTickSource, 2); int i; for (i = 0; i < it->count; i ++) { ecs_entity_t src = filter[i].src; bool inc = false; filter[i].time_elapsed += it->delta_time; if (src) { const EcsTickSource *tick_src = ecs_get(it->world, src, EcsTickSource); if (tick_src) { inc = tick_src->tick; } else { inc = true; } } else { inc = true; } if (inc) { filter[i].tick_count ++; bool triggered = !(filter[i].tick_count % filter[i].rate); tick_dst[i].tick = triggered; tick_dst[i].time_elapsed = filter[i].time_elapsed; if (triggered) { filter[i].time_elapsed = 0; } } else { tick_dst[i].tick = false; } } } static void ProgressTickSource(ecs_iter_t *it) { EcsTickSource *tick_src = ecs_field(it, EcsTickSource, 1); /* If tick source has no filters, tick unconditionally */ int i; for (i = 0; i < it->count; i ++) { tick_src[i].tick = true; tick_src[i].time_elapsed = it->delta_time; } } ecs_entity_t ecs_set_timeout( ecs_world_t *world, ecs_entity_t timer, ecs_ftime_t timeout) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); timer = ecs_set(world, timer, EcsTimer, { .timeout = timeout, .single_shot = true, .active = true }); ecs_system_t *system_data = ecs_poly_get(world, timer, ecs_system_t); if (system_data) { system_data->tick_source = timer; } error: return timer; } ecs_ftime_t ecs_get_timeout( const ecs_world_t *world, ecs_entity_t timer) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(timer != 0, ECS_INVALID_PARAMETER, NULL); const EcsTimer *value = ecs_get(world, timer, EcsTimer); if (value) { return value->timeout; } error: return 0; } ecs_entity_t ecs_set_interval( ecs_world_t *world, ecs_entity_t timer, ecs_ftime_t interval) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); timer = ecs_set(world, timer, EcsTimer, { .timeout = interval, .active = true }); ecs_system_t *system_data = ecs_poly_get(world, timer, ecs_system_t); if (system_data) { system_data->tick_source = timer; } error: return timer; } ecs_ftime_t ecs_get_interval( const ecs_world_t *world, ecs_entity_t timer) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); if (!timer) { return 0; } const EcsTimer *value = ecs_get(world, timer, EcsTimer); if (value) { return value->timeout; } error: return 0; } void ecs_start_timer( ecs_world_t *world, ecs_entity_t timer) { EcsTimer *ptr = ecs_get_mut(world, timer, EcsTimer); ecs_check(ptr != NULL, ECS_INVALID_PARAMETER, NULL); ptr->active = true; ptr->time = 0; error: return; } void ecs_stop_timer( ecs_world_t *world, ecs_entity_t timer) { EcsTimer *ptr = ecs_get_mut(world, timer, EcsTimer); ecs_check(ptr != NULL, ECS_INVALID_PARAMETER, NULL); ptr->active = false; error: return; } ecs_entity_t ecs_set_rate( ecs_world_t *world, ecs_entity_t filter, int32_t rate, ecs_entity_t source) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); filter = ecs_set(world, filter, EcsRateFilter, { .rate = rate, .src = source }); ecs_system_t *system_data = ecs_poly_get(world, filter, ecs_system_t); if (system_data) { system_data->tick_source = filter; } error: return filter; } void ecs_set_tick_source( ecs_world_t *world, ecs_entity_t system, ecs_entity_t tick_source) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(system != 0, ECS_INVALID_PARAMETER, NULL); ecs_check(tick_source != 0, ECS_INVALID_PARAMETER, NULL); ecs_system_t *system_data = ecs_poly_get(world, system, ecs_system_t); ecs_check(system_data != NULL, ECS_INVALID_PARAMETER, NULL); system_data->tick_source = tick_source; error: return; } void FlecsTimerImport( ecs_world_t *world) { ECS_MODULE(world, FlecsTimer); ECS_IMPORT(world, FlecsPipeline); ecs_set_name_prefix(world, "Ecs"); flecs_bootstrap_component(world, EcsTimer); flecs_bootstrap_component(world, EcsRateFilter); /* Add EcsTickSource to timers and rate filters */ ecs_system_init(world, &(ecs_system_desc_t){ .entity = ecs_entity(world, {.name = "AddTickSource", .add = { ecs_dependson(EcsPreFrame) }}), .query.filter.terms = { { .id = ecs_id(EcsTimer), .oper = EcsOr, .inout = EcsIn }, { .id = ecs_id(EcsRateFilter), .oper = EcsAnd, .inout = EcsIn }, { .id = ecs_id(EcsTickSource), .oper = EcsNot, .inout = EcsOut} }, .callback = AddTickSource }); /* Timer handling */ ecs_system_init(world, &(ecs_system_desc_t){ .entity = ecs_entity(world, {.name = "ProgressTimers", .add = { ecs_dependson(EcsPreFrame)}}), .query.filter.terms = { { .id = ecs_id(EcsTimer) }, { .id = ecs_id(EcsTickSource) } }, .callback = ProgressTimers }); /* Rate filter handling */ ecs_system_init(world, &(ecs_system_desc_t){ .entity = ecs_entity(world, {.name = "ProgressRateFilters", .add = { ecs_dependson(EcsPreFrame)}}), .query.filter.terms = { { .id = ecs_id(EcsRateFilter), .inout = EcsIn }, { .id = ecs_id(EcsTickSource), .inout = EcsOut } }, .callback = ProgressRateFilters }); /* TickSource without a timer or rate filter just increases each frame */ ecs_system_init(world, &(ecs_system_desc_t){ .entity = ecs_entity(world, { .name = "ProgressTickSource", .add = { ecs_dependson(EcsPreFrame)}}), .query.filter.terms = { { .id = ecs_id(EcsTickSource), .inout = EcsOut }, { .id = ecs_id(EcsRateFilter), .oper = EcsNot }, { .id = ecs_id(EcsTimer), .oper = EcsNot } }, .callback = ProgressTickSource }); } #endif /** * @file addons/flecs_cpp.c * @brief Utilities for C++ addon. */ #include /* Utilities for C++ API */ #ifdef FLECS_CPP /* Convert compiler-specific typenames extracted from __PRETTY_FUNCTION__ to * a uniform identifier */ #define ECS_CONST_PREFIX "const " #define ECS_STRUCT_PREFIX "struct " #define ECS_CLASS_PREFIX "class " #define ECS_ENUM_PREFIX "enum " #define ECS_CONST_LEN (-1 + (ecs_size_t)sizeof(ECS_CONST_PREFIX)) #define ECS_STRUCT_LEN (-1 + (ecs_size_t)sizeof(ECS_STRUCT_PREFIX)) #define ECS_CLASS_LEN (-1 + (ecs_size_t)sizeof(ECS_CLASS_PREFIX)) #define ECS_ENUM_LEN (-1 + (ecs_size_t)sizeof(ECS_ENUM_PREFIX)) static ecs_size_t ecs_cpp_strip_prefix( char *typeName, ecs_size_t len, const char *prefix, ecs_size_t prefix_len) { if ((len > prefix_len) && !ecs_os_strncmp(typeName, prefix, prefix_len)) { ecs_os_memmove(typeName, typeName + prefix_len, len - prefix_len); typeName[len - prefix_len] = '\0'; len -= prefix_len; } return len; } static void ecs_cpp_trim_type_name( char *typeName) { ecs_size_t len = ecs_os_strlen(typeName); len = ecs_cpp_strip_prefix(typeName, len, ECS_CONST_PREFIX, ECS_CONST_LEN); len = ecs_cpp_strip_prefix(typeName, len, ECS_STRUCT_PREFIX, ECS_STRUCT_LEN); len = ecs_cpp_strip_prefix(typeName, len, ECS_CLASS_PREFIX, ECS_CLASS_LEN); len = ecs_cpp_strip_prefix(typeName, len, ECS_ENUM_PREFIX, ECS_ENUM_LEN); while (typeName[len - 1] == ' ' || typeName[len - 1] == '&' || typeName[len - 1] == '*') { len --; typeName[len] = '\0'; } /* Remove const at end of string */ if (len > ECS_CONST_LEN) { if (!ecs_os_strncmp(&typeName[len - ECS_CONST_LEN], " const", ECS_CONST_LEN)) { typeName[len - ECS_CONST_LEN] = '\0'; } len -= ECS_CONST_LEN; } /* Check if there are any remaining "struct " strings, which can happen * if this is a template type on msvc. */ if (len > ECS_STRUCT_LEN) { char *ptr = typeName; while ((ptr = strstr(ptr + 1, ECS_STRUCT_PREFIX)) != 0) { /* Make sure we're not matched with part of a longer identifier * that contains 'struct' */ if (ptr[-1] == '<' || ptr[-1] == ',' || isspace(ptr[-1])) { ecs_os_memmove(ptr, ptr + ECS_STRUCT_LEN, ecs_os_strlen(ptr + ECS_STRUCT_LEN) + 1); len -= ECS_STRUCT_LEN; } } } } char* ecs_cpp_get_type_name( char *type_name, const char *func_name, size_t len) { memcpy(type_name, func_name + ECS_FUNC_NAME_FRONT(const char*, type_name), len); type_name[len] = '\0'; ecs_cpp_trim_type_name(type_name); return type_name; } char* ecs_cpp_get_symbol_name( char *symbol_name, const char *type_name, size_t len) { // Symbol is same as name, but with '::' replaced with '.' ecs_os_strcpy(symbol_name, type_name); char *ptr; size_t i; for (i = 0, ptr = symbol_name; i < len && *ptr; i ++, ptr ++) { if (*ptr == ':') { symbol_name[i] = '.'; ptr ++; } else { symbol_name[i] = *ptr; } } symbol_name[i] = '\0'; return symbol_name; } static const char* cpp_func_rchr( const char *func_name, ecs_size_t func_name_len, char ch) { const char *r = strrchr(func_name, ch); if ((r - func_name) >= (func_name_len - flecs_uto(ecs_size_t, ECS_FUNC_NAME_BACK))) { return NULL; } return r; } static const char* cpp_func_max( const char *a, const char *b) { if (a > b) return a; return b; } char* ecs_cpp_get_constant_name( char *constant_name, const char *func_name, size_t func_name_len) { ecs_size_t f_len = flecs_uto(ecs_size_t, func_name_len); const char *start = cpp_func_rchr(func_name, f_len, ' '); start = cpp_func_max(start, cpp_func_rchr(func_name, f_len, ')')); start = cpp_func_max(start, cpp_func_rchr(func_name, f_len, ':')); start = cpp_func_max(start, cpp_func_rchr(func_name, f_len, ',')); ecs_assert(start != NULL, ECS_INVALID_PARAMETER, func_name); start ++; ecs_size_t len = flecs_uto(ecs_size_t, (f_len - (start - func_name) - flecs_uto(ecs_size_t, ECS_FUNC_NAME_BACK))); ecs_os_memcpy_n(constant_name, start, char, len); constant_name[len] = '\0'; return constant_name; } // Names returned from the name_helper class do not start with :: // but are relative to the root. If the namespace of the type // overlaps with the namespace of the current module, strip it from // the implicit identifier. // This allows for registration of component types that are not in the // module namespace to still be registered under the module scope. const char* ecs_cpp_trim_module( ecs_world_t *world, const char *type_name) { ecs_entity_t scope = ecs_get_scope(world); if (!scope) { return type_name; } char *path = ecs_get_path_w_sep(world, 0, scope, "::", NULL); if (path) { const char *ptr = strrchr(type_name, ':'); ecs_assert(ptr != type_name, ECS_INTERNAL_ERROR, NULL); if (ptr) { ptr --; ecs_assert(ptr[0] == ':', ECS_INTERNAL_ERROR, NULL); ecs_size_t name_path_len = (ecs_size_t)(ptr - type_name); if (name_path_len <= ecs_os_strlen(path)) { if (!ecs_os_strncmp(type_name, path, name_path_len)) { type_name = &type_name[name_path_len + 2]; } } } } ecs_os_free(path); return type_name; } // Validate registered component void ecs_cpp_component_validate( ecs_world_t *world, ecs_entity_t id, const char *name, const char *symbol, size_t size, size_t alignment, bool implicit_name) { /* If entity has a name check if it matches */ if (ecs_is_valid(world, id) && ecs_get_name(world, id) != NULL) { if (!implicit_name && id >= EcsFirstUserComponentId) { #ifndef FLECS_NDEBUG char *path = ecs_get_path_w_sep( world, 0, id, "::", NULL); if (ecs_os_strcmp(path, name)) { ecs_abort(ECS_INCONSISTENT_NAME, "component '%s' already registered with name '%s'", name, path); } ecs_os_free(path); #endif } if (symbol) { const char *existing_symbol = ecs_get_symbol(world, id); if (existing_symbol) { if (ecs_os_strcmp(symbol, existing_symbol)) { ecs_abort(ECS_INCONSISTENT_NAME, "component '%s' with symbol '%s' already registered with symbol '%s'", name, symbol, existing_symbol); } } } } else { /* Ensure that the entity id valid */ if (!ecs_is_alive(world, id)) { ecs_ensure(world, id); } /* Register name with entity, so that when the entity is created the * correct id will be resolved from the name. Only do this when the * entity is empty. */ ecs_add_path_w_sep(world, id, 0, name, "::", "::"); } /* If a component was already registered with this id but with a * different size, the ecs_component_init function will fail. */ /* We need to explicitly call ecs_component_init here again. Even though * the component was already registered, it may have been registered * with a different world. This ensures that the component is registered * with the same id for the current world. * If the component was registered already, nothing will change. */ ecs_entity_t ent = ecs_component_init(world, &(ecs_component_desc_t){ .entity = id, .type.size = flecs_uto(int32_t, size), .type.alignment = flecs_uto(int32_t, alignment) }); (void)ent; ecs_assert(ent == id, ECS_INTERNAL_ERROR, NULL); } ecs_entity_t ecs_cpp_component_register( ecs_world_t *world, ecs_entity_t id, const char *name, const char *symbol, ecs_size_t size, ecs_size_t alignment, bool implicit_name, bool *existing_out) { (void)size; (void)alignment; /* If the component is not yet registered, ensure no other component * or entity has been registered with this name. Ensure component is * looked up from root. */ bool existing = false; ecs_entity_t prev_scope = ecs_set_scope(world, 0); ecs_entity_t ent; if (id) { ent = id; } else { ent = ecs_lookup_path_w_sep(world, 0, name, "::", "::", false); existing = ent != 0; } ecs_set_scope(world, prev_scope); /* If entity exists, compare symbol name to ensure that the component * we are trying to register under this name is the same */ if (ent) { const EcsComponent *component = ecs_get(world, ent, EcsComponent); if (component != NULL) { const char *sym = ecs_get_symbol(world, ent); if (sym && ecs_os_strcmp(sym, symbol)) { /* Application is trying to register a type with an entity that * was already associated with another type. In most cases this * is an error, with the exception of a scenario where the * application is wrapping a C type with a C++ type. * * In this case the C++ type typically inherits from the C type, * and adds convenience methods to the derived class without * changing anything that would change the size or layout. * * To meet this condition, the new type must have the same size * and alignment as the existing type, and the name of the type * type must be equal to the registered name (not symbol). * * The latter ensures that it was the intent of the application * to alias the type, vs. accidentally registering an unrelated * type with the same size/alignment. */ char *type_path = ecs_get_fullpath(world, ent); if (ecs_os_strcmp(type_path, symbol) || component->size != size || component->alignment != alignment) { ecs_err( "component with name '%s' is already registered for"\ " type '%s' (trying to register for type '%s')", name, sym, symbol); ecs_abort(ECS_NAME_IN_USE, NULL); } ecs_os_free(type_path); } else if (!sym) { ecs_set_symbol(world, ent, symbol); } } /* If no entity is found, lookup symbol to check if the component was * registered under a different name. */ } else if (!implicit_name) { ent = ecs_lookup_symbol(world, symbol, false); ecs_assert(ent == 0 || (ent == id), ECS_INCONSISTENT_COMPONENT_ID, symbol); } if (existing_out) { *existing_out = existing; } return ent; } ecs_entity_t ecs_cpp_component_register_explicit( ecs_world_t *world, ecs_entity_t s_id, ecs_entity_t id, const char *name, const char *type_name, const char *symbol, size_t size, size_t alignment, bool is_component, bool *existing_out) { char *existing_name = NULL; if (existing_out) *existing_out = false; // If an explicit id is provided, it is possible that the symbol and // name differ from the actual type, as the application may alias // one type to another. if (!id) { if (!name) { // If no name was provided first check if a type with the provided // symbol was already registered. id = ecs_lookup_symbol(world, symbol, false); if (id) { existing_name = ecs_get_path_w_sep(world, 0, id, "::", "::"); name = existing_name; if (existing_out) *existing_out = true; } else { // If type is not yet known, derive from type name name = ecs_cpp_trim_module(world, type_name); } } } else { // If an explicit id is provided but it has no name, inherit // the name from the type. if (!ecs_is_valid(world, id) || !ecs_get_name(world, id)) { name = ecs_cpp_trim_module(world, type_name); } } ecs_entity_t entity; if (is_component || size != 0) { entity = ecs_entity(world, { .id = s_id, .name = name, .sep = "::", .root_sep = "::", .symbol = symbol, .use_low_id = true }); ecs_assert(entity != 0, ECS_INVALID_OPERATION, NULL); entity = ecs_component_init(world, &(ecs_component_desc_t){ .entity = entity, .type.size = flecs_uto(int32_t, size), .type.alignment = flecs_uto(int32_t, alignment) }); ecs_assert(entity != 0, ECS_INVALID_OPERATION, NULL); } else { entity = ecs_entity(world, { .id = s_id, .name = name, .sep = "::", .root_sep = "::", .symbol = symbol, .use_low_id = true }); } ecs_assert(entity != 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(!s_id || s_id == entity, ECS_INTERNAL_ERROR, NULL); ecs_os_free(existing_name); return entity; } void ecs_cpp_enum_init( ecs_world_t *world, ecs_entity_t id) { ecs_suspend_readonly_state_t readonly_state; world = flecs_suspend_readonly(world, &readonly_state); ecs_add_id(world, id, EcsExclusive); ecs_add_id(world, id, EcsOneOf); ecs_add_id(world, id, EcsTag); flecs_resume_readonly(world, &readonly_state); } ecs_entity_t ecs_cpp_enum_constant_register( ecs_world_t *world, ecs_entity_t parent, ecs_entity_t id, const char *name, int value) { ecs_suspend_readonly_state_t readonly_state; world = flecs_suspend_readonly(world, &readonly_state); const char *parent_name = ecs_get_name(world, parent); ecs_size_t parent_name_len = ecs_os_strlen(parent_name); if (!ecs_os_strncmp(name, parent_name, parent_name_len)) { name += parent_name_len; if (name[0] == '_') { name ++; } } ecs_entity_t prev = ecs_set_scope(world, parent); id = ecs_entity_init(world, &(ecs_entity_desc_t){ .id = id, .name = name }); ecs_assert(id != 0, ECS_INVALID_OPERATION, name); ecs_set_scope(world, prev); #ifdef FLECS_DEBUG const EcsComponent *cptr = ecs_get(world, parent, EcsComponent); ecs_assert(cptr != NULL, ECS_INVALID_PARAMETER, "enum is not a component"); ecs_assert(cptr->size == ECS_SIZEOF(int32_t), ECS_UNSUPPORTED, "enum component must have 32bit size"); #endif ecs_set_id(world, id, parent, sizeof(int), &value); flecs_resume_readonly(world, &readonly_state); ecs_trace("#[green]constant#[reset] %s.%s created with value %d", ecs_get_name(world, parent), name, value); return id; } static int32_t flecs_reset_count = 0; int32_t ecs_cpp_reset_count_get(void) { return flecs_reset_count; } int32_t ecs_cpp_reset_count_inc(void) { return ++flecs_reset_count; } #endif /** * @file addons/os_api_impl/os_api_impl.c * @brief Builtin implementation for OS API. */ #ifdef FLECS_OS_API_IMPL #ifdef ECS_TARGET_WINDOWS /** * @file addons/os_api_impl/posix_impl.inl * @brief Builtin Windows implementation for OS API. */ #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include #include static ecs_os_thread_t win_thread_new( ecs_os_thread_callback_t callback, void *arg) { HANDLE *thread = ecs_os_malloc_t(HANDLE); *thread = CreateThread( NULL, 0, (LPTHREAD_START_ROUTINE)callback, arg, 0, NULL); return (ecs_os_thread_t)(uintptr_t)thread; } static void* win_thread_join( ecs_os_thread_t thr) { HANDLE *thread = (HANDLE*)(uintptr_t)thr; DWORD r = WaitForSingleObject(*thread, INFINITE); if (r == WAIT_FAILED) { ecs_err("win_thread_join: WaitForSingleObject failed"); } ecs_os_free(thread); return NULL; } static ecs_os_thread_id_t win_thread_self(void) { return (ecs_os_thread_id_t)GetCurrentThreadId(); } static int32_t win_ainc( int32_t *count) { return InterlockedIncrement(count); } static int32_t win_adec( int32_t *count) { return InterlockedDecrement(count); } static int64_t win_lainc( int64_t *count) { return InterlockedIncrement64(count); } static int64_t win_ladec( int64_t *count) { return InterlockedDecrement64(count); } static ecs_os_mutex_t win_mutex_new(void) { CRITICAL_SECTION *mutex = ecs_os_malloc_t(CRITICAL_SECTION); InitializeCriticalSection(mutex); return (ecs_os_mutex_t)(uintptr_t)mutex; } static void win_mutex_free( ecs_os_mutex_t m) { CRITICAL_SECTION *mutex = (CRITICAL_SECTION*)(intptr_t)m; DeleteCriticalSection(mutex); ecs_os_free(mutex); } static void win_mutex_lock( ecs_os_mutex_t m) { CRITICAL_SECTION *mutex = (CRITICAL_SECTION*)(intptr_t)m; EnterCriticalSection(mutex); } static void win_mutex_unlock( ecs_os_mutex_t m) { CRITICAL_SECTION *mutex = (CRITICAL_SECTION*)(intptr_t)m; LeaveCriticalSection(mutex); } static ecs_os_cond_t win_cond_new(void) { CONDITION_VARIABLE *cond = ecs_os_malloc_t(CONDITION_VARIABLE); InitializeConditionVariable(cond); return (ecs_os_cond_t)(uintptr_t)cond; } static void win_cond_free( ecs_os_cond_t c) { (void)c; } static void win_cond_signal( ecs_os_cond_t c) { CONDITION_VARIABLE *cond = (CONDITION_VARIABLE*)(intptr_t)c; WakeConditionVariable(cond); } static void win_cond_broadcast( ecs_os_cond_t c) { CONDITION_VARIABLE *cond = (CONDITION_VARIABLE*)(intptr_t)c; WakeAllConditionVariable(cond); } static void win_cond_wait( ecs_os_cond_t c, ecs_os_mutex_t m) { CRITICAL_SECTION *mutex = (CRITICAL_SECTION*)(intptr_t)m; CONDITION_VARIABLE *cond = (CONDITION_VARIABLE*)(intptr_t)c; SleepConditionVariableCS(cond, mutex, INFINITE); } static bool win_time_initialized; static double win_time_freq; static LARGE_INTEGER win_time_start; static void win_time_setup(void) { if ( win_time_initialized) { return; } win_time_initialized = true; LARGE_INTEGER freq; QueryPerformanceFrequency(&freq); QueryPerformanceCounter(&win_time_start); win_time_freq = (double)freq.QuadPart / 1000000000.0; } static void win_sleep( int32_t sec, int32_t nanosec) { HANDLE timer; LARGE_INTEGER ft; ft.QuadPart = -((int64_t)sec * 10000000 + (int64_t)nanosec / 100); timer = CreateWaitableTimer(NULL, TRUE, NULL); SetWaitableTimer(timer, &ft, 0, NULL, NULL, 0); WaitForSingleObject(timer, INFINITE); CloseHandle(timer); } static double win_time_freq; static ULONG win_current_resolution; static void win_enable_high_timer_resolution(bool enable) { HMODULE hntdll = GetModuleHandle((LPCTSTR)"ntdll.dll"); if (!hntdll) { return; } LONG (__stdcall *pNtSetTimerResolution)( ULONG desired, BOOLEAN set, ULONG * current); pNtSetTimerResolution = (LONG(__stdcall*)(ULONG, BOOLEAN, ULONG*)) GetProcAddress(hntdll, "NtSetTimerResolution"); if(!pNtSetTimerResolution) { return; } ULONG current, resolution = 10000; /* 1 ms */ if (!enable && win_current_resolution) { pNtSetTimerResolution(win_current_resolution, 0, ¤t); win_current_resolution = 0; return; } else if (!enable) { return; } if (resolution == win_current_resolution) { return; } if (win_current_resolution) { pNtSetTimerResolution(win_current_resolution, 0, ¤t); } if (pNtSetTimerResolution(resolution, 1, ¤t)) { /* Try setting a lower resolution */ resolution *= 2; if(pNtSetTimerResolution(resolution, 1, ¤t)) return; } win_current_resolution = resolution; } static uint64_t win_time_now(void) { uint64_t now; LARGE_INTEGER qpc_t; QueryPerformanceCounter(&qpc_t); now = (uint64_t)(qpc_t.QuadPart / win_time_freq); return now; } static void win_fini(void) { if (ecs_os_api.flags_ & EcsOsApiHighResolutionTimer) { win_enable_high_timer_resolution(false); } } void ecs_set_os_api_impl(void) { ecs_os_set_api_defaults(); ecs_os_api_t api = ecs_os_api; api.thread_new_ = win_thread_new; api.thread_join_ = win_thread_join; api.thread_self_ = win_thread_self; api.ainc_ = win_ainc; api.adec_ = win_adec; api.lainc_ = win_lainc; api.ladec_ = win_ladec; api.mutex_new_ = win_mutex_new; api.mutex_free_ = win_mutex_free; api.mutex_lock_ = win_mutex_lock; api.mutex_unlock_ = win_mutex_unlock; api.cond_new_ = win_cond_new; api.cond_free_ = win_cond_free; api.cond_signal_ = win_cond_signal; api.cond_broadcast_ = win_cond_broadcast; api.cond_wait_ = win_cond_wait; api.sleep_ = win_sleep; api.now_ = win_time_now; api.fini_ = win_fini; win_time_setup(); if (ecs_os_api.flags_ & EcsOsApiHighResolutionTimer) { win_enable_high_timer_resolution(true); } ecs_os_set_api(&api); } #else /** * @file addons/os_api_impl/posix_impl.inl * @brief Builtin POSIX implementation for OS API. */ #include "pthread.h" #if defined(__APPLE__) && defined(__MACH__) #include #elif defined(__EMSCRIPTEN__) #include #else #include #endif static ecs_os_thread_t posix_thread_new( ecs_os_thread_callback_t callback, void *arg) { pthread_t *thread = ecs_os_malloc(sizeof(pthread_t)); if (pthread_create (thread, NULL, callback, arg) != 0) { ecs_os_abort(); } return (ecs_os_thread_t)(uintptr_t)thread; } static void* posix_thread_join( ecs_os_thread_t thread) { void *arg; pthread_t *thr = (pthread_t*)(uintptr_t)thread; pthread_join(*thr, &arg); ecs_os_free(thr); return arg; } static ecs_os_thread_id_t posix_thread_self(void) { return (ecs_os_thread_id_t)pthread_self(); } static int32_t posix_ainc( int32_t *count) { int value; #ifdef __GNUC__ value = __sync_add_and_fetch (count, 1); return value; #else /* Unsupported */ abort(); #endif } static int32_t posix_adec( int32_t *count) { int32_t value; #ifdef __GNUC__ value = __sync_sub_and_fetch (count, 1); return value; #else /* Unsupported */ abort(); #endif } static int64_t posix_lainc( int64_t *count) { int64_t value; #ifdef __GNUC__ value = __sync_add_and_fetch (count, 1); return value; #else /* Unsupported */ abort(); #endif } static int64_t posix_ladec( int64_t *count) { int64_t value; #ifdef __GNUC__ value = __sync_sub_and_fetch (count, 1); return value; #else /* Unsupported */ abort(); #endif } static ecs_os_mutex_t posix_mutex_new(void) { pthread_mutex_t *mutex = ecs_os_malloc(sizeof(pthread_mutex_t)); if (pthread_mutex_init(mutex, NULL)) { abort(); } return (ecs_os_mutex_t)(uintptr_t)mutex; } static void posix_mutex_free( ecs_os_mutex_t m) { pthread_mutex_t *mutex = (pthread_mutex_t*)(intptr_t)m; pthread_mutex_destroy(mutex); ecs_os_free(mutex); } static void posix_mutex_lock( ecs_os_mutex_t m) { pthread_mutex_t *mutex = (pthread_mutex_t*)(intptr_t)m; if (pthread_mutex_lock(mutex)) { abort(); } } static void posix_mutex_unlock( ecs_os_mutex_t m) { pthread_mutex_t *mutex = (pthread_mutex_t*)(intptr_t)m; if (pthread_mutex_unlock(mutex)) { abort(); } } static ecs_os_cond_t posix_cond_new(void) { pthread_cond_t *cond = ecs_os_malloc(sizeof(pthread_cond_t)); if (pthread_cond_init(cond, NULL)) { abort(); } return (ecs_os_cond_t)(uintptr_t)cond; } static void posix_cond_free( ecs_os_cond_t c) { pthread_cond_t *cond = (pthread_cond_t*)(intptr_t)c; if (pthread_cond_destroy(cond)) { abort(); } ecs_os_free(cond); } static void posix_cond_signal( ecs_os_cond_t c) { pthread_cond_t *cond = (pthread_cond_t*)(intptr_t)c; if (pthread_cond_signal(cond)) { abort(); } } static void posix_cond_broadcast( ecs_os_cond_t c) { pthread_cond_t *cond = (pthread_cond_t*)(intptr_t)c; if (pthread_cond_broadcast(cond)) { abort(); } } static void posix_cond_wait( ecs_os_cond_t c, ecs_os_mutex_t m) { pthread_cond_t *cond = (pthread_cond_t*)(intptr_t)c; pthread_mutex_t *mutex = (pthread_mutex_t*)(intptr_t)m; if (pthread_cond_wait(cond, mutex)) { abort(); } } static bool posix_time_initialized; #if defined(__APPLE__) && defined(__MACH__) static mach_timebase_info_data_t posix_osx_timebase; static uint64_t posix_time_start; #else static uint64_t posix_time_start; #endif static void posix_time_setup(void) { if (posix_time_initialized) { return; } posix_time_initialized = true; #if defined(__APPLE__) && defined(__MACH__) mach_timebase_info(&posix_osx_timebase); posix_time_start = mach_absolute_time(); #else struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); posix_time_start = (uint64_t)ts.tv_sec*1000000000 + (uint64_t)ts.tv_nsec; #endif } static void posix_sleep( int32_t sec, int32_t nanosec) { struct timespec sleepTime; ecs_assert(sec >= 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(nanosec >= 0, ECS_INTERNAL_ERROR, NULL); sleepTime.tv_sec = sec; sleepTime.tv_nsec = nanosec; if (nanosleep(&sleepTime, NULL)) { ecs_err("nanosleep failed"); } } /* prevent 64-bit overflow when computing relative timestamp see https://gist.github.com/jspohr/3dc4f00033d79ec5bdaf67bc46c813e3 */ #if defined(ECS_TARGET_DARWIN) static int64_t posix_int64_muldiv(int64_t value, int64_t numer, int64_t denom) { int64_t q = value / denom; int64_t r = value % denom; return q * numer + r * numer / denom; } #endif static uint64_t posix_time_now(void) { ecs_assert(posix_time_initialized != 0, ECS_INTERNAL_ERROR, NULL); uint64_t now; #if defined(ECS_TARGET_DARWIN) now = (uint64_t) posix_int64_muldiv( (int64_t)mach_absolute_time(), (int64_t)posix_osx_timebase.numer, (int64_t)posix_osx_timebase.denom); #elif defined(__EMSCRIPTEN__) now = (long long)(emscripten_get_now() * 1000.0 * 1000); #else struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); now = ((uint64_t)ts.tv_sec * 1000 * 1000 * 1000 + (uint64_t)ts.tv_nsec); #endif return now; } void ecs_set_os_api_impl(void) { ecs_os_set_api_defaults(); ecs_os_api_t api = ecs_os_api; api.thread_new_ = posix_thread_new; api.thread_join_ = posix_thread_join; api.thread_self_ = posix_thread_self; api.ainc_ = posix_ainc; api.adec_ = posix_adec; api.lainc_ = posix_lainc; api.ladec_ = posix_ladec; api.mutex_new_ = posix_mutex_new; api.mutex_free_ = posix_mutex_free; api.mutex_lock_ = posix_mutex_lock; api.mutex_unlock_ = posix_mutex_unlock; api.cond_new_ = posix_cond_new; api.cond_free_ = posix_cond_free; api.cond_signal_ = posix_cond_signal; api.cond_broadcast_ = posix_cond_broadcast; api.cond_wait_ = posix_cond_wait; api.sleep_ = posix_sleep; api.now_ = posix_time_now; posix_time_setup(); ecs_os_set_api(&api); } #endif #endif /** * @file addons/plecs.c * @brief Plecs addon. */ #ifdef FLECS_PLECS ECS_COMPONENT_DECLARE(EcsScript); #include #define TOK_NEWLINE '\n' #define TOK_USING "using" #define TOK_MODULE "module" #define TOK_WITH "with" #define TOK_CONST "const" #define TOK_PROP "prop" #define TOK_ASSEMBLY "assembly" #define STACK_MAX_SIZE (64) typedef struct { ecs_value_t value; bool owned; } plecs_with_value_t; typedef struct { const char *name; const char *code; ecs_entity_t last_predicate; ecs_entity_t last_subject; ecs_entity_t last_object; ecs_id_t last_assign_id; ecs_entity_t assign_to; ecs_entity_t scope[STACK_MAX_SIZE]; ecs_entity_t default_scope_type[STACK_MAX_SIZE]; ecs_entity_t with[STACK_MAX_SIZE]; ecs_entity_t using[STACK_MAX_SIZE]; int32_t with_frames[STACK_MAX_SIZE]; plecs_with_value_t with_value_frames[STACK_MAX_SIZE]; int32_t using_frames[STACK_MAX_SIZE]; int32_t sp; int32_t with_frame; int32_t using_frame; ecs_entity_t global_with; ecs_entity_t assembly; const char *assembly_start, *assembly_stop; char *annot[STACK_MAX_SIZE]; int32_t annot_count; ecs_vars_t vars; char var_name[256]; ecs_entity_t var_type; bool with_stmt; bool scope_assign_stmt; bool assign_stmt; bool assembly_stmt; bool assembly_instance; bool isa_stmt; bool decl_stmt; bool decl_type; bool var_stmt; bool var_is_prop; bool is_module; int32_t errors; } plecs_state_t; static int flecs_plecs_parse( ecs_world_t *world, const char *name, const char *expr, ecs_vars_t *vars, ecs_entity_t script, ecs_entity_t instance); static void flecs_dtor_script(EcsScript *ptr) { ecs_os_free(ptr->script); ecs_vec_fini_t(NULL, &ptr->using_, ecs_entity_t); int i, count = ptr->prop_defaults.count; ecs_value_t *values = ptr->prop_defaults.array; for (i = 0; i < count; i ++) { ecs_value_free(ptr->world, values[i].type, values[i].ptr); } ecs_vec_fini_t(NULL, &ptr->prop_defaults, ecs_value_t); } static ECS_MOVE(EcsScript, dst, src, { flecs_dtor_script(dst); dst->using_ = src->using_; dst->prop_defaults = src->prop_defaults; dst->script = src->script; dst->world = src->world; ecs_os_zeromem(&src->using_); ecs_os_zeromem(&src->prop_defaults); src->script = NULL; src->world = NULL; }) static ECS_DTOR(EcsScript, ptr, { flecs_dtor_script(ptr); }) /* Assembly ctor to initialize with default property values */ static void flecs_assembly_ctor( void *ptr, int32_t count, const ecs_type_info_t *ti) { ecs_world_t *world = ti->hooks.ctx; ecs_entity_t assembly = ti->component; const EcsStruct *st = ecs_get(world, assembly, EcsStruct); if (!st) { ecs_err("assembly '%s' is not a struct, cannot construct", ti->name); return; } const EcsScript *script = ecs_get(world, assembly, EcsScript); if (!script) { ecs_err("assembly '%s' is not a script, cannot construct", ti->name); return; } if (st->members.count != script->prop_defaults.count) { ecs_err("number of props (%d) of assembly '%s' does not match members" " (%d), cannot construct", script->prop_defaults.count, ti->name, st->members.count); return; } const ecs_member_t *members = st->members.array; int32_t i, m, member_count = st->members.count; ecs_value_t *values = script->prop_defaults.array; for (m = 0; m < member_count; m ++) { const ecs_member_t *member = &members[m]; ecs_value_t *value = &values[m]; const ecs_type_info_t *mti = ecs_get_type_info(world, member->type); if (!mti) { ecs_err("failed to get type info for prop '%s' of assembly '%s'", member->name, ti->name); return; } for (i = 0; i < count; i ++) { void *el = ECS_ELEM(ptr, ti->size, i); ecs_value_copy_w_type_info(world, mti, ECS_OFFSET(el, member->offset), value->ptr); } } } /* Assembly on_set handler to update contents for new property values */ static void flecs_assembly_on_set( ecs_iter_t *it) { if (it->table->flags & EcsTableIsPrefab) { /* Don't instantiate assemblies for prefabs */ return; } ecs_world_t *world = it->world; ecs_entity_t assembly = ecs_field_id(it, 1); const char *name = ecs_get_name(world, assembly); const EcsComponent *ct = ecs_get(world, assembly, EcsComponent); if (!ct) { ecs_err("assembly '%s' is not a component", name); return; } const EcsStruct *st = ecs_get(world, assembly, EcsStruct); if (!st) { ecs_err("assembly '%s' is not a struct", name); return; } const EcsScript *script = ecs_get(world, assembly, EcsScript); if (!script) { ecs_err("assembly '%s' is missing a script", name); return; } void *data = ecs_field_w_size(it, flecs_ito(size_t, ct->size), 1); int32_t i, m; for (i = 0; i < it->count; i ++) { /* Create variables to hold assembly properties */ ecs_vars_t vars = {0}; ecs_vars_init(world, &vars); /* Populate properties from assembly members */ const ecs_member_t *members = st->members.array; for (m = 0; m < st->members.count; m ++) { const ecs_member_t *member = &members[m]; ecs_value_t v = {0}; /* Prevent allocating value */ ecs_expr_var_t *var = ecs_vars_declare_w_value( &vars, member->name, &v); if (var == NULL) { ecs_err("could not create prop '%s' for assembly '%s'", member->name, name); break; } /* Assign assembly property from assembly instance */ var->value.type = member->type; var->value.ptr = ECS_OFFSET(data, member->offset); var->owned = false; } /* Update script with new code/properties */ ecs_entity_t instance = it->entities[i]; ecs_script_update(world, assembly, instance, script->script, &vars); ecs_vars_fini(&vars); data = ECS_OFFSET(data, ct->size); } } /* Delete contents of assembly instance */ static void flecs_assembly_on_remove( ecs_iter_t *it) { int32_t i; for (i = 0; i < it->count; i ++) { ecs_entity_t instance = it->entities[i]; ecs_script_clear(it->world, 0, instance); } } /* Set default property values on assembly Script component */ static int flecs_assembly_init_defaults( ecs_world_t *world, const char *name, const char *expr, const char *ptr, ecs_entity_t assembly, EcsScript *script, plecs_state_t *state) { const EcsStruct *st = ecs_get(world, assembly, EcsStruct); int32_t i, count = st->members.count; const ecs_member_t *members = st->members.array; ecs_vec_init_t(NULL, &script->prop_defaults, ecs_value_t, count); for (i = 0; i < count; i ++) { const ecs_member_t *member = &members[i]; ecs_expr_var_t *var = ecs_vars_lookup(&state->vars, member->name); if (!var) { char *assembly_name = ecs_get_fullpath(world, assembly); ecs_parser_error(name, expr, ptr - expr, "missing property '%s' for assembly '%s'", member->name, assembly_name); ecs_os_free(assembly_name); return -1; } if (member->type != var->value.type) { char *assembly_name = ecs_get_fullpath(world, assembly); ecs_parser_error(name, expr, ptr - expr, "property '%s' for assembly '%s' has mismatching type", member->name, assembly_name); ecs_os_free(assembly_name); return -1; } ecs_value_t *pv = ecs_vec_append_t(NULL, &script->prop_defaults, ecs_value_t); pv->type = member->type; pv->ptr = var->value.ptr; var->owned = false; /* Transfer ownership */ } return 0; } /* Create new assembly */ static int flecs_assembly_create( ecs_world_t *world, const char *name, const char *expr, const char *ptr, ecs_entity_t assembly, char *script_code, plecs_state_t *state) { const EcsStruct *st = ecs_get(world, assembly, EcsStruct); if (!st || !st->members.count) { char *assembly_name = ecs_get_fullpath(world, assembly); ecs_parser_error(name, expr, ptr - expr, "assembly '%s' has no properties", assembly_name); ecs_os_free(assembly_name); ecs_os_free(script_code); return -1; } ecs_add_id(world, assembly, EcsAlwaysOverride); EcsScript *script = ecs_get_mut(world, assembly, EcsScript); flecs_dtor_script(script); script->world = world; script->script = script_code; ecs_vec_reset_t(NULL, &script->using_, ecs_entity_t); ecs_entity_t scope = ecs_get_scope(world); if (scope && (scope = ecs_get_target(world, scope, EcsChildOf, 0))) { ecs_vec_append_t(NULL, &script->using_, ecs_entity_t)[0] = scope; } int i, count = state->using_frame; for (i = 0; i < count; i ++) { ecs_vec_append_t(NULL, &script->using_, ecs_entity_t)[0] = state->using[i]; } if (flecs_assembly_init_defaults( world, name, expr, ptr, assembly, script, state)) { return -1; } ecs_modified(world, assembly, EcsScript); ecs_set_hooks_id(world, assembly, &(ecs_type_hooks_t) { .ctor = flecs_assembly_ctor, .on_set = flecs_assembly_on_set, .on_remove = flecs_assembly_on_remove, .ctx = world }); return 0; } /* Parser */ static bool plecs_is_newline_comment( const char *ptr) { if (ptr[0] == '/' && ptr[1] == '/') { return true; } return false; } static const char* plecs_parse_fluff( const char *ptr) { do { /* Skip whitespaces before checking for a comment */ ptr = ecs_parse_ws(ptr); /* Newline comment, skip until newline character */ if (plecs_is_newline_comment(ptr)) { ptr += 2; while (ptr[0] && ptr[0] != TOK_NEWLINE) { ptr ++; } } /* If a newline character is found, skip it */ if (ptr[0] == TOK_NEWLINE) { ptr ++; } } while (isspace(ptr[0]) || plecs_is_newline_comment(ptr)); return ptr; } static ecs_entity_t plecs_lookup( const ecs_world_t *world, const char *path, plecs_state_t *state, ecs_entity_t rel, bool is_subject) { ecs_entity_t e = 0; if (!is_subject) { ecs_entity_t oneof = 0; if (rel) { if (ecs_has_id(world, rel, EcsOneOf)) { oneof = rel; } else { oneof = ecs_get_target(world, rel, EcsOneOf, 0); } if (oneof) { return ecs_lookup_path_w_sep( world, oneof, path, NULL, NULL, false); } } int using_scope = state->using_frame - 1; for (; using_scope >= 0; using_scope--) { e = ecs_lookup_path_w_sep( world, state->using[using_scope], path, NULL, NULL, false); if (e) { break; } } } if (!e) { e = ecs_lookup_path_w_sep(world, 0, path, NULL, NULL, !is_subject); } return e; } /* Lookup action used for deserializing entity refs in component values */ static ecs_entity_t plecs_lookup_action( const ecs_world_t *world, const char *path, void *ctx) { return plecs_lookup(world, path, ctx, 0, false); } static ecs_entity_t plecs_ensure_entity( ecs_world_t *world, plecs_state_t *state, const char *path, ecs_entity_t rel, bool is_subject) { if (!path) { return 0; } ecs_entity_t e = 0; bool is_anonymous = !ecs_os_strcmp(path, "_"); bool is_new = false; if (is_anonymous) { path = NULL; e = ecs_new_id(world); is_new = true; } if (!e) { e = plecs_lookup(world, path, state, rel, is_subject); } if (!e) { is_new = true; if (rel && flecs_get_oneof(world, rel)) { /* If relationship has oneof and entity was not found, don't proceed * with creating an entity as this can cause asserts later on */ char *relstr = ecs_get_fullpath(world, rel); ecs_parser_error(state->name, 0, 0, "invalid identifier '%s' for relationship '%s'", path, relstr); ecs_os_free(relstr); return 0; } ecs_entity_t prev_scope = 0; ecs_entity_t prev_with = 0; if (!is_subject) { /* Don't apply scope/with for non-subject entities */ prev_scope = ecs_set_scope(world, 0); prev_with = ecs_set_with(world, 0); } e = ecs_add_path(world, e, 0, path); ecs_assert(e != 0, ECS_INTERNAL_ERROR, NULL); if (prev_scope) { ecs_set_scope(world, prev_scope); } if (prev_with) { ecs_set_with(world, prev_with); } } else { /* If entity exists, make sure it gets the right scope and with */ if (is_subject) { ecs_entity_t scope = ecs_get_scope(world); if (scope) { ecs_add_pair(world, e, EcsChildOf, scope); } ecs_entity_t with = ecs_get_with(world); if (with) { ecs_add_id(world, e, with); } } } if (is_new) { if (state->assembly && !state->assembly_instance) { ecs_add_id(world, e, EcsPrefab); } if (state->global_with) { ecs_add_id(world, e, state->global_with); } } return e; } static bool plecs_pred_is_subj( ecs_term_t *term, plecs_state_t *state) { if (term->src.name != NULL) { return false; } if (term->second.name != NULL) { return false; } if (ecs_term_match_0(term)) { return false; } if (state->with_stmt) { return false; } if (state->assign_stmt) { return false; } if (state->isa_stmt) { return false; } if (state->decl_type) { return false; } return true; } /* Set masks aren't useful in plecs, so translate them back to entity names */ static const char* plecs_set_mask_to_name( ecs_flags32_t flags) { flags &= EcsTraverseFlags; if (flags == EcsSelf) { return "self"; } else if (flags == EcsUp) { return "up"; } else if (flags == EcsDown) { return "down"; } else if (flags == EcsCascade || flags == (EcsUp|EcsCascade)) { return "cascade"; } else if (flags == EcsParent) { return "parent"; } return NULL; } static char* plecs_trim_annot( char *annot) { annot = (char*)ecs_parse_ws(annot); int32_t len = ecs_os_strlen(annot) - 1; while (isspace(annot[len]) && (len > 0)) { annot[len] = '\0'; len --; } return annot; } static void plecs_apply_annotations( ecs_world_t *world, ecs_entity_t subj, plecs_state_t *state) { (void)world; (void)subj; (void)state; #ifdef FLECS_DOC int32_t i = 0, count = state->annot_count; for (i = 0; i < count; i ++) { char *annot = state->annot[i]; if (!ecs_os_strncmp(annot, "@brief ", 7)) { annot = plecs_trim_annot(annot + 7); ecs_doc_set_brief(world, subj, annot); } else if (!ecs_os_strncmp(annot, "@link ", 6)) { annot = plecs_trim_annot(annot + 6); ecs_doc_set_link(world, subj, annot); } else if (!ecs_os_strncmp(annot, "@name ", 6)) { annot = plecs_trim_annot(annot + 6); ecs_doc_set_name(world, subj, annot); } else if (!ecs_os_strncmp(annot, "@color ", 7)) { annot = plecs_trim_annot(annot + 7); ecs_doc_set_color(world, subj, annot); } } #else ecs_warn("cannot apply annotations, doc addon is missing"); #endif } static int plecs_create_term( ecs_world_t *world, ecs_term_t *term, const char *name, const char *expr, int64_t column, plecs_state_t *state) { state->last_subject = 0; state->last_predicate = 0; state->last_object = 0; state->last_assign_id = 0; const char *pred_name = term->first.name; const char *subj_name = term->src.name; const char *obj_name = term->second.name; if (!subj_name) { subj_name = plecs_set_mask_to_name(term->src.flags); } if (!obj_name) { obj_name = plecs_set_mask_to_name(term->second.flags); } if (!ecs_term_id_is_set(&term->first)) { ecs_parser_error(name, expr, column, "missing predicate in expression"); return -1; } if (state->assign_stmt && !ecs_term_match_this(term)) { ecs_parser_error(name, expr, column, "invalid statement in assign statement"); return -1; } bool pred_as_subj = plecs_pred_is_subj(term, state); ecs_entity_t pred = plecs_ensure_entity(world, state, pred_name, 0, pred_as_subj); ecs_entity_t subj = plecs_ensure_entity(world, state, subj_name, pred, true); ecs_entity_t obj = 0; if (ecs_term_id_is_set(&term->second)) { obj = plecs_ensure_entity(world, state, obj_name, pred, state->assign_stmt == false); if (!obj) { return -1; } } if (state->assign_stmt || state->isa_stmt) { subj = state->assign_to; } if (state->isa_stmt && obj) { ecs_parser_error(name, expr, column, "invalid object in inheritance statement"); return -1; } if (state->isa_stmt) { pred = ecs_pair(EcsIsA, pred); } if (subj == EcsVariable) { subj = pred; } if (subj) { if (!obj) { ecs_add_id(world, subj, pred); state->last_assign_id = pred; } else { ecs_add_pair(world, subj, pred, obj); state->last_object = obj; state->last_assign_id = ecs_pair(pred, obj); } state->last_predicate = pred; state->last_subject = subj; pred_as_subj = false; } else { if (!obj) { /* If no subject or object were provided, use predicate as subj * unless the expression explictly excluded the subject */ if (pred_as_subj) { state->last_subject = pred; subj = pred; } else { state->last_predicate = pred; pred_as_subj = false; } } else { state->last_predicate = pred; state->last_object = obj; pred_as_subj = false; } } /* If this is a with clause (the list of entities between 'with' and scope * open), add subject to the array of with frames */ if (state->with_stmt) { ecs_assert(pred != 0, ECS_INTERNAL_ERROR, NULL); ecs_id_t id; if (obj) { id = ecs_pair(pred, obj); } else { id = pred; } state->with[state->with_frame ++] = id; } else { if (subj) { int32_t i, frame_count = state->with_frames[state->sp]; for (i = 0; i < frame_count; i ++) { ecs_id_t id = state->with[i]; plecs_with_value_t *v = &state->with_value_frames[i]; if (v->value.type) { void *ptr = ecs_get_mut_id(world, subj, id); ecs_value_copy(world, v->value.type, ptr, v->value.ptr); ecs_modified_id(world, subj, id); } else { ecs_add_id(world, subj, id); } } } } /* If an id was provided by itself, add default scope type to it */ ecs_entity_t default_scope_type = state->default_scope_type[state->sp]; if (pred_as_subj && default_scope_type) { ecs_add_id(world, subj, default_scope_type); } /* If annotations preceded the statement, append */ if (!state->decl_type && state->annot_count) { if (!subj) { ecs_parser_error(name, expr, column, "missing subject for annotations"); return -1; } plecs_apply_annotations(world, subj, state); } return 0; } static const char* plecs_parse_inherit_stmt( const char *name, const char *expr, const char *ptr, plecs_state_t *state) { if (state->isa_stmt) { ecs_parser_error(name, expr, ptr - expr, "cannot nest inheritance"); return NULL; } if (!state->last_subject) { ecs_parser_error(name, expr, ptr - expr, "missing entity to assign inheritance to"); return NULL; } state->isa_stmt = true; state->assign_to = state->last_subject; return ptr; } static const char* plecs_parse_assign_var_expr( ecs_world_t *world, const char *name, const char *expr, const char *ptr, plecs_state_t *state) { ecs_value_t value = {0}; ecs_expr_var_t *var = NULL; if (state->last_assign_id) { value.type = state->last_assign_id; value.ptr = ecs_value_new(world, state->last_assign_id); var = ecs_vars_lookup(&state->vars, state->var_name); } ptr = ecs_parse_expr(world, ptr, &value, &(ecs_parse_expr_desc_t){ .name = name, .expr = expr, .lookup_action = plecs_lookup_action, .lookup_ctx = state, .vars = &state->vars }); if (!ptr) { if (state->last_assign_id) { ecs_value_free(world, value.type, value.ptr); } goto error; } if (var) { bool ignore = state->var_is_prop && state->assembly_instance; if (!ignore) { if (var->value.ptr) { ecs_value_free(world, var->value.type, var->value.ptr); var->value.ptr = value.ptr; var->value.type = value.type; } } else { ecs_value_free(world, value.type, value.ptr); } } else { var = ecs_vars_declare_w_value( &state->vars, state->var_name, &value); if (!var) { goto error; } } state->var_is_prop = false; return ptr; error: return NULL; } static const char* plecs_parse_assign_expr( ecs_world_t *world, const char *name, const char *expr, const char *ptr, plecs_state_t *state) { (void)world; if (state->var_stmt) { return plecs_parse_assign_var_expr(world, name, expr, ptr, state); } if (!state->assign_stmt) { ecs_parser_error(name, expr, ptr - expr, "unexpected value outside of assignment statement"); return NULL; } ecs_id_t assign_id = state->last_assign_id; if (!assign_id) { ecs_parser_error(name, expr, ptr - expr, "missing type for assignment statement"); return NULL; } ecs_entity_t assign_to = state->assign_to; if (!assign_to) { assign_to = state->last_subject; } if (!assign_to) { ecs_parser_error(name, expr, ptr - expr, "missing entity to assign to"); return NULL; } ecs_entity_t type = ecs_get_typeid(world, assign_id); if (!type) { char *id_str = ecs_id_str(world, assign_id); ecs_parser_error(name, expr, ptr - expr, "invalid assignment, '%s' is not a type", id_str); ecs_os_free(id_str); return NULL; } if (assign_to == EcsVariable) { assign_to = type; } void *value_ptr = ecs_get_mut_id(world, assign_to, assign_id); ptr = ecs_parse_expr(world, ptr, &(ecs_value_t){type, value_ptr}, &(ecs_parse_expr_desc_t){ .name = name, .expr = expr, .lookup_action = plecs_lookup_action, .lookup_ctx = state, .vars = &state->vars }); if (!ptr) { return NULL; } ecs_modified_id(world, assign_to, assign_id); return ptr; } static const char* plecs_parse_assign_stmt( ecs_world_t *world, const char *name, const char *expr, const char *ptr, plecs_state_t *state) { (void)world; state->isa_stmt = false; /* Component scope (add components to entity) */ if (!state->assign_to) { if (!state->last_subject) { ecs_parser_error(name, expr, ptr - expr, "missing entity to assign to"); return NULL; } state->assign_to = state->last_subject; } if (state->assign_stmt) { ecs_parser_error(name, expr, ptr - expr, "invalid assign statement in assign statement"); return NULL; } state->assign_stmt = true; /* Assignment without a preceding component */ if (ptr[0] == '{') { ecs_entity_t type = 0; /* If we're in a scope & last_subject is a type, assign to scope */ if (ecs_get_scope(world) != 0) { type = ecs_get_typeid(world, state->last_subject); if (type != 0) { type = state->last_subject; } } /* If type hasn't been set yet, check if scope has default type */ if (!type && !state->scope_assign_stmt) { type = state->default_scope_type[state->sp]; } /* If no type has been found still, check if last with id is a type */ if (!type && !state->scope_assign_stmt) { int32_t with_frame_count = state->with_frames[state->sp]; if (with_frame_count) { type = state->with[with_frame_count - 1]; } } if (!type) { ecs_parser_error(name, expr, ptr - expr, "missing type for assignment"); return NULL; } state->last_assign_id = type; } return ptr; } static const char* plecs_parse_assign_with_stmt( ecs_world_t *world, const char *name, const char *expr, const char *ptr, plecs_state_t *state) { int32_t with_frame = state->with_frame - 1; if (with_frame < 0) { ecs_parser_error(name, expr, ptr - expr, "missing type in with value"); return NULL; } ecs_id_t id = state->with[with_frame]; ecs_id_record_t *idr = flecs_id_record_get(world, id); const ecs_type_info_t *ti = idr->type_info; if (!ti) { char *typename = ecs_id_str(world, id); ecs_parser_error(name, expr, ptr - expr, "id '%s' in with value is not a type", typename); ecs_os_free(typename); return NULL; } plecs_with_value_t *v = &state->with_value_frames[with_frame]; v->value.type = ti->component; v->value.ptr = ecs_value_new(world, ti->component); v->owned = true; if (!v->value.ptr) { char *typename = ecs_id_str(world, id); ecs_parser_error(name, expr, ptr - expr, "failed to create value for '%s'", typename); ecs_os_free(typename); return NULL; } ptr = ecs_parse_expr(world, ptr, &v->value, &(ecs_parse_expr_desc_t){ .name = name, .expr = expr, .lookup_action = plecs_lookup_action, .lookup_ctx = state, .vars = &state->vars }); if (!ptr) { return NULL; } return ptr; } static const char* plecs_parse_assign_with_var( const char *name, const char *expr, const char *ptr, plecs_state_t *state) { ecs_assert(ptr[0] == '$', ECS_INTERNAL_ERROR, NULL); ecs_assert(state->with_stmt, ECS_INTERNAL_ERROR, NULL); char var_name[ECS_MAX_TOKEN_SIZE]; const char *tmp = ptr; ptr = ecs_parse_token(name, expr, ptr + 1, var_name, 0); if (!ptr) { ecs_parser_error(name, expr, tmp - expr, "unresolved variable '%s'", var_name); return NULL; } ecs_expr_var_t *var = ecs_vars_lookup(&state->vars, var_name); if (!var) { ecs_parser_error(name, expr, ptr - expr, "unresolved variable '%s'", var_name); return NULL; } int32_t with_frame = state->with_frame; state->with[with_frame] = var->value.type; state->with_value_frames[with_frame].value = var->value; state->with_value_frames[with_frame].owned = false; state->with_frame ++; return ptr; } static const char* plecs_parse_var_as_component( ecs_world_t *world, const char *name, const char *expr, const char *ptr, plecs_state_t *state) { ecs_assert(ptr[0] == '$', ECS_INTERNAL_ERROR, NULL); ecs_assert(!state->var_stmt, ECS_INTERNAL_ERROR, NULL); char var_name[ECS_MAX_TOKEN_SIZE]; const char *tmp = ptr; ptr = ecs_parse_token(name, expr, ptr + 1, var_name, 0); if (!ptr) { ecs_parser_error(name, expr, tmp - expr, "unresolved variable '%s'", var_name); return NULL; } ecs_expr_var_t *var = ecs_vars_lookup(&state->vars, var_name); if (!var) { ecs_parser_error(name, expr, ptr - expr, "unresolved variable '%s'", var_name); return NULL; } if (!state->assign_to) { ecs_parser_error(name, expr, ptr - expr, "missing lvalue for variable assignment '%s'", var_name); return NULL; } /* Use type of variable as component */ ecs_entity_t type = var->value.type; ecs_entity_t assign_to = state->assign_to; if (!assign_to) { assign_to = state->last_subject; } void *dst = ecs_get_mut_id(world, assign_to, type); if (!dst) { char *type_name = ecs_get_fullpath(world, type); ecs_parser_error(name, expr, ptr - expr, "failed to obtain component for type '%s' of variable '%s'", type_name, var_name); ecs_os_free(type_name); return NULL; } if (ecs_value_copy(world, type, dst, var->value.ptr)) { char *type_name = ecs_get_fullpath(world, type); ecs_parser_error(name, expr, ptr - expr, "failed to copy value for variable '%s' of type '%s'", var_name, type_name); ecs_os_free(type_name); return NULL; } ecs_modified_id(world, assign_to, type); return ptr; } static void plecs_push_using( ecs_entity_t scope, plecs_state_t *state) { for (int i = 0; i < state->using_frame; i ++) { if (state->using[i] == scope) { return; } } state->using[state->using_frame ++] = scope; } static const char* plecs_parse_using_stmt( ecs_world_t *world, const char *name, const char *expr, const char *ptr, plecs_state_t *state) { if (state->isa_stmt || state->assign_stmt) { ecs_parser_error(name, expr, ptr - expr, "invalid usage of using keyword"); return NULL; } char using_path[ECS_MAX_TOKEN_SIZE]; const char *tmp = ptr + 1; ptr = ecs_parse_token(name, expr, ptr + 5, using_path, 0); if (!ptr) { ecs_parser_error(name, expr, tmp - expr, "expected identifier for using statement"); return NULL; } ecs_size_t len = ecs_os_strlen(using_path); if (!len) { ecs_parser_error(name, expr, tmp - expr, "missing identifier for using statement"); return NULL; } /* Lookahead as * is not matched by parse_token */ if (ptr[0] == '*') { using_path[len] = '*'; using_path[len + 1] = '\0'; len ++; ptr ++; } ecs_entity_t scope; if (len > 2 && !ecs_os_strcmp(&using_path[len - 2], ".*")) { using_path[len - 2] = '\0'; scope = ecs_lookup_fullpath(world, using_path); if (!scope) { ecs_parser_error(name, expr, ptr - expr, "unresolved identifier '%s' in using statement", using_path); return NULL; } /* Add each child of the scope to using stack */ ecs_iter_t it = ecs_term_iter(world, &(ecs_term_t){ .id = ecs_childof(scope) }); while (ecs_term_next(&it)) { int32_t i, count = it.count; for (i = 0; i < count; i ++) { plecs_push_using(it.entities[i], state); } } } else { scope = plecs_ensure_entity(world, state, using_path, 0, false); if (!scope) { ecs_parser_error(name, expr, ptr - expr, "unresolved identifier '%s' in using statement", using_path); return NULL; } plecs_push_using(scope, state); } state->using_frames[state->sp] = state->using_frame; return ptr; } static const char* plecs_parse_module_stmt( ecs_world_t *world, const char *name, const char *expr, const char *ptr, plecs_state_t *state) { const char *expr_start = ecs_parse_ws_eol(expr); if (expr_start != ptr) { ecs_parser_error(name, expr, ptr - expr, "module must be first statement of script"); return NULL; } char module_path[ECS_MAX_TOKEN_SIZE]; const char *tmp = ptr + 1; ptr = ecs_parse_token(name, expr, ptr + 6, module_path, 0); if (!ptr) { ecs_parser_error(name, expr, tmp - expr, "expected identifier for module statement"); return NULL; } ecs_component_desc_t desc = {0}; desc.entity = ecs_entity(world, { .name = module_path }); ecs_entity_t module = ecs_module_init(world, NULL, &desc); if (!module) { return NULL; } state->is_module = true; state->sp ++; state->scope[state->sp] = module; ecs_set_scope(world, module); return ptr; } static const char* plecs_parse_with_stmt( const char *name, const char *expr, const char *ptr, plecs_state_t *state) { if (state->isa_stmt) { ecs_parser_error(name, expr, ptr - expr, "invalid with after inheritance"); return NULL; } if (state->assign_stmt) { ecs_parser_error(name, expr, ptr - expr, "invalid with in assign_stmt"); return NULL; } /* Add following expressions to with list */ state->with_stmt = true; return ptr + 5; } static const char* plecs_parse_assembly_stmt( const char *name, const char *expr, const char *ptr, plecs_state_t *state) { if (state->isa_stmt) { ecs_parser_error(name, expr, ptr - expr, "invalid with after inheritance"); return NULL; } if (state->assign_stmt) { ecs_parser_error(name, expr, ptr - expr, "invalid with in assign_stmt"); return NULL; } state->assembly_stmt = true; return ptr + 9; } static const char* plecs_parse_var_type( ecs_world_t *world, const char *name, const char *expr, const char *ptr, plecs_state_t *state, ecs_entity_t *type_out) { char prop_type_name[ECS_MAX_TOKEN_SIZE]; const char *tmp = ptr + 1; ptr = ecs_parse_token(name, expr, ptr + 1, prop_type_name, 0); if (!ptr) { ecs_parser_error(name, expr, tmp - expr, "expected type for prop declaration"); return NULL; } ecs_entity_t prop_type = plecs_lookup(world, prop_type_name, state, 0, false); if (!prop_type) { ecs_parser_error(name, expr, ptr - expr, "unresolved property type '%s'", prop_type_name); return NULL; } *type_out = prop_type; return ptr; } static const char* plecs_parse_const_stmt( ecs_world_t *world, const char *name, const char *expr, const char *ptr, plecs_state_t *state) { ptr = ecs_parse_token(name, expr, ptr + 5, state->var_name, 0); if (!ptr) { return NULL; } ptr = ecs_parse_ws(ptr); if (ptr[0] == ':') { ptr = plecs_parse_var_type( world, name, expr, ptr, state, &state->last_assign_id); if (!ptr) { return NULL; } ptr = ecs_parse_ws(ptr); } if (ptr[0] != '=') { ecs_parser_error(name, expr, ptr - expr, "expected '=' after const declaration"); return NULL; } state->var_stmt = true; return ptr + 1; } static const char* plecs_parse_prop_stmt( ecs_world_t *world, const char *name, const char *expr, const char *ptr, plecs_state_t *state) { char prop_name[ECS_MAX_TOKEN_SIZE]; ptr = ecs_parse_token(name, expr, ptr + 5, prop_name, 0); if (!ptr) { return NULL; } ptr = ecs_parse_ws(ptr); if (ptr[0] != ':') { ecs_parser_error(name, expr, ptr - expr, "expected ':' after prop declaration"); return NULL; } ecs_entity_t prop_type; ptr = plecs_parse_var_type(world, name, expr, ptr, state, &prop_type); if (!ptr) { return NULL; } ecs_entity_t assembly = state->assembly; if (!assembly) { ecs_parser_error(name, expr, ptr - expr, "unexpected prop '%s' outside of assembly", prop_name); return NULL; } if (!state->assembly_instance) { ecs_entity_t prop_member = ecs_entity(world, { .name = prop_name, .add = { ecs_childof(assembly) } }); if (!prop_member) { return NULL; } ecs_set(world, prop_member, EcsMember, { .type = prop_type }); } if (ptr[0] != '=') { ecs_parser_error(name, expr, ptr - expr, "expected '=' after prop type"); return NULL; } ecs_os_strcpy(state->var_name, prop_name); state->last_assign_id = prop_type; state->var_stmt = true; state->var_is_prop = true; return plecs_parse_fluff(ptr + 1); } static const char* plecs_parse_scope_open( ecs_world_t *world, const char *name, const char *expr, const char *ptr, plecs_state_t *state) { state->isa_stmt = false; if (state->assign_stmt) { ecs_parser_error(name, expr, ptr - expr, "invalid scope in assign_stmt"); return NULL; } state->sp ++; ecs_entity_t scope = 0; ecs_entity_t default_scope_type = 0; if (!state->with_stmt) { if (state->last_subject) { scope = state->last_subject; ecs_set_scope(world, state->last_subject); /* Check if scope has a default child component */ ecs_entity_t def_type_src = ecs_get_target_for_id(world, scope, 0, ecs_pair(EcsDefaultChildComponent, EcsWildcard)); if (def_type_src) { default_scope_type = ecs_get_target( world, def_type_src, EcsDefaultChildComponent, 0); } } else { if (state->last_object) { scope = ecs_pair( state->last_predicate, state->last_object); ecs_set_with(world, scope); } else { if (state->last_predicate) { scope = ecs_pair(EcsChildOf, state->last_predicate); } ecs_set_scope(world, state->last_predicate); } } state->scope[state->sp] = scope; state->default_scope_type[state->sp] = default_scope_type; if (state->assembly_stmt) { if (state->assembly) { ecs_parser_error(name, expr, ptr - expr, "invalid nested assembly"); return NULL; } state->assembly = scope; state->assembly_stmt = false; state->assembly_start = ptr; } } else { state->scope[state->sp] = state->scope[state->sp - 1]; state->default_scope_type[state->sp] = state->default_scope_type[state->sp - 1]; } state->using_frames[state->sp] = state->using_frame; state->with_frames[state->sp] = state->with_frame; state->with_stmt = false; ecs_vars_push(&state->vars); return ptr; } static const char* plecs_parse_scope_close( ecs_world_t *world, const char *name, const char *expr, const char *ptr, plecs_state_t *state) { if (state->isa_stmt) { ecs_parser_error(name, expr, ptr - expr, "invalid '}' after inheritance statement"); return NULL; } if (state->assign_stmt) { ecs_parser_error(name, expr, ptr - expr, "unfinished assignment before }"); return NULL; } ecs_entity_t cur = state->scope[state->sp], assembly = state->assembly; if (state->sp && (cur == state->scope[state->sp - 1])) { /* Previous scope is also from the assembly, not found the end yet */ cur = 0; } if (cur && cur == assembly) { ecs_size_t assembly_len = flecs_ito(ecs_size_t, ptr - state->assembly_start); if (assembly_len) { assembly_len --; char *script = ecs_os_malloc_n(char, assembly_len + 1); ecs_os_memcpy(script, state->assembly_start, assembly_len); script[assembly_len] = '\0'; state->assembly = 0; state->assembly_start = NULL; if (flecs_assembly_create(world, name, expr, ptr, assembly, script, state)) { return NULL; } } else { ecs_parser_error(name, expr, ptr - expr, "empty assembly"); return NULL; } } state->scope[state->sp] = 0; state->default_scope_type[state->sp] = 0; state->sp --; if (state->sp < 0) { ecs_parser_error(name, expr, ptr - expr, "invalid } without a {"); return NULL; } ecs_id_t id = state->scope[state->sp]; if (!id || ECS_HAS_ID_FLAG(id, PAIR)) { ecs_set_with(world, id); } if (!id || !ECS_HAS_ID_FLAG(id, PAIR)) { ecs_set_scope(world, id); } int32_t i, prev_with = state->with_frames[state->sp]; for (i = prev_with; i < state->with_frame; i ++) { plecs_with_value_t *v = &state->with_value_frames[i]; if (!v->owned) { continue; } if (v->value.type) { ecs_value_free(world, v->value.type, v->value.ptr); v->value.type = 0; v->value.ptr = NULL; v->owned = false; } } state->with_frame = state->with_frames[state->sp]; state->using_frame = state->using_frames[state->sp]; state->last_subject = 0; state->assign_stmt = false; ecs_vars_pop(&state->vars); return plecs_parse_fluff(ptr + 1); } static const char *plecs_parse_plecs_term( ecs_world_t *world, const char *name, const char *expr, const char *ptr, plecs_state_t *state) { ecs_term_t term = {0}; ecs_entity_t decl_id = 0; if (state->decl_stmt) { decl_id = state->last_predicate; } ptr = ecs_parse_term(world, name, expr, ptr, &term); if (!ptr) { return NULL; } if (flecs_isident(ptr[0])) { state->decl_type = true; } if (!ecs_term_is_initialized(&term)) { ecs_parser_error(name, expr, ptr - expr, "expected identifier"); return NULL; /* No term found */ } if (plecs_create_term(world, &term, name, expr, (ptr - expr), state)) { ecs_term_fini(&term); return NULL; /* Failed to create term */ } if (decl_id && state->last_subject) { ecs_add_id(world, state->last_subject, decl_id); } state->decl_type = false; ecs_term_fini(&term); return ptr; } static const char* plecs_parse_annotation( const char *name, const char *expr, const char *ptr, plecs_state_t *state) { do { if(state->annot_count >= STACK_MAX_SIZE) { ecs_parser_error(name, expr, ptr - expr, "max number of annotations reached"); return NULL; } char ch; const char *start = ptr; for (; (ch = *ptr) && ch != '\n'; ptr ++) { } int32_t len = (int32_t)(ptr - start); char *annot = ecs_os_malloc_n(char, len + 1); ecs_os_memcpy_n(annot, start, char, len); annot[len] = '\0'; state->annot[state->annot_count] = annot; state->annot_count ++; ptr = plecs_parse_fluff(ptr); } while (ptr[0] == '@'); return ptr; } static void plecs_clear_annotations( plecs_state_t *state) { int32_t i, count = state->annot_count; for (i = 0; i < count; i ++) { ecs_os_free(state->annot[i]); } state->annot_count = 0; } static const char* plecs_parse_stmt( ecs_world_t *world, const char *name, const char *expr, const char *ptr, plecs_state_t *state) { state->assign_stmt = false; state->scope_assign_stmt = false; state->isa_stmt = false; state->with_stmt = false; state->decl_stmt = false; state->var_stmt = false; state->last_subject = 0; state->last_predicate = 0; state->last_object = 0; state->assign_to = 0; state->last_assign_id = 0; plecs_clear_annotations(state); ptr = plecs_parse_fluff(ptr); char ch = ptr[0]; if (!ch) { goto done; } else if (ch == '{') { ptr = plecs_parse_fluff(ptr + 1); goto scope_open; } else if (ch == '}') { goto scope_close; } else if (ch == '-') { ptr = plecs_parse_fluff(ptr + 1); state->assign_to = ecs_get_scope(world); state->scope_assign_stmt = true; goto assign_stmt; } else if (ch == '@') { ptr = plecs_parse_annotation(name, expr, ptr, state); if (!ptr) goto error; goto term_expr; } else if (!ecs_os_strncmp(ptr, TOK_USING " ", 5)) { ptr = plecs_parse_using_stmt(world, name, expr, ptr, state); if (!ptr) goto error; goto done; } else if (!ecs_os_strncmp(ptr, TOK_MODULE " ", 6)) { ptr = plecs_parse_module_stmt(world, name, expr, ptr, state); if (!ptr) goto error; goto done; } else if (!ecs_os_strncmp(ptr, TOK_WITH " ", 5)) { ptr = plecs_parse_with_stmt(name, expr, ptr, state); if (!ptr) goto error; goto term_expr; } else if (!ecs_os_strncmp(ptr, TOK_CONST " ", 6)) { ptr = plecs_parse_const_stmt(world, name, expr, ptr, state); if (!ptr) goto error; goto assign_expr; } else if (!ecs_os_strncmp(ptr, TOK_ASSEMBLY " ", 9)) { ptr = plecs_parse_assembly_stmt(name, expr, ptr, state); if (!ptr) goto error; goto decl_stmt; } else if (!ecs_os_strncmp(ptr, TOK_PROP " ", 5)) { ptr = plecs_parse_prop_stmt(world, name, expr, ptr, state); if (!ptr) goto error; goto assign_expr; } else { goto term_expr; } term_expr: if (!ptr[0]) { goto done; } if (ptr[0] == '$' && !isspace(ptr[1])) { if (state->with_stmt) { ptr = plecs_parse_assign_with_var(name, expr, ptr, state); if (!ptr) { return NULL; } } else if (!state->var_stmt) { goto assign_var_as_component; } } else if (!(ptr = plecs_parse_plecs_term(world, name, ptr, ptr, state))) { goto error; } const char *tptr = ecs_parse_ws(ptr); if (flecs_isident(tptr[0])) { if (state->decl_stmt) { ecs_parser_error(name, expr, (ptr - expr), "unexpected ' ' in declaration statement"); goto error; } ptr = tptr; goto decl_stmt; } next_term: ptr = plecs_parse_fluff(ptr); if (ptr[0] == ':' && ptr[1] == '-') { ptr = plecs_parse_fluff(ptr + 2); goto assign_stmt; } else if (ptr[0] == ':') { ptr = plecs_parse_fluff(ptr + 1); goto inherit_stmt; } else if (ptr[0] == ',') { ptr = plecs_parse_fluff(ptr + 1); goto term_expr; } else if (ptr[0] == '{') { if (state->assign_stmt) { goto assign_expr; } else if (state->with_stmt && !isspace(ptr[-1])) { /* If this is a { in a with statement which directly follows a * non-whitespace character, the with id has a value */ ptr = plecs_parse_assign_with_stmt(world, name, expr, ptr, state); if (!ptr) { goto error; } goto next_term; } else { ptr = plecs_parse_fluff(ptr + 1); goto scope_open; } } state->assign_stmt = false; goto done; decl_stmt: state->decl_stmt = true; goto term_expr; inherit_stmt: ptr = plecs_parse_inherit_stmt(name, expr, ptr, state); if (!ptr) goto error; /* Expect base identifier */ goto term_expr; assign_stmt: ptr = plecs_parse_assign_stmt(world, name, expr, ptr, state); if (!ptr) goto error; ptr = plecs_parse_fluff(ptr); /* Assignment without a preceding component */ if (ptr[0] == '{') { goto assign_expr; } /* Expect component identifiers */ goto term_expr; assign_expr: ptr = plecs_parse_assign_expr(world, name, expr, ptr, state); if (!ptr) goto error; ptr = plecs_parse_fluff(ptr); if (ptr[0] == ',') { ptr ++; goto term_expr; } else if (ptr[0] == '{') { if (state->var_stmt) { const ecs_expr_var_t *var = ecs_vars_lookup( &state->vars, state->var_name); if (var && var->value.type == ecs_id(ecs_entity_t)) { ecs_assert(var->value.ptr != NULL, ECS_INTERNAL_ERROR, NULL); /* The code contained an entity{...} variable assignment, use * the assigned entity id as type for parsing the expression */ state->last_assign_id = *(ecs_entity_t*)var->value.ptr; ptr = plecs_parse_assign_expr(world, name, expr, ptr, state); goto done; } ecs_parser_error(name, expr, (ptr - expr), "variables not supported, missing FLECS_EXPR addon"); } ecs_parser_error(name, expr, (ptr - expr), "unexpected '{' after assignment"); goto error; } state->assign_stmt = false; state->assign_to = 0; goto done; assign_var_as_component: { ptr = plecs_parse_var_as_component(world, name, expr, ptr, state); if (!ptr) { goto error; } state->assign_stmt = false; state->assign_to = 0; goto done; } scope_open: ptr = plecs_parse_scope_open(world, name, expr, ptr, state); if (!ptr) goto error; goto done; scope_close: ptr = plecs_parse_scope_close(world, name, expr, ptr, state); if (!ptr) goto error; goto done; done: return ptr; error: return NULL; } static int flecs_plecs_parse( ecs_world_t *world, const char *name, const char *expr, ecs_vars_t *vars, ecs_entity_t script, ecs_entity_t instance) { const char *ptr = expr; ecs_term_t term = {0}; plecs_state_t state = {0}; if (!expr) { return 0; } state.scope[0] = 0; ecs_entity_t prev_scope = ecs_set_scope(world, 0); ecs_entity_t prev_with = ecs_set_with(world, 0); if (ECS_IS_PAIR(prev_with) && ECS_PAIR_FIRST(prev_with) == EcsChildOf) { ecs_set_scope(world, ECS_PAIR_SECOND(prev_with)); state.scope[0] = ecs_pair_second(world, prev_with); } else { state.global_with = prev_with; } ecs_vars_init(world, &state.vars); if (script) { const EcsScript *s = ecs_get(world, script, EcsScript); if (!s) { ecs_err("%s: provided script entity is not a script", name); goto error; } if (s && ecs_has(world, script, EcsStruct)) { state.assembly = script; state.assembly_instance = true; if (s->using_.count) { ecs_os_memcpy_n(state.using, s->using_.array, ecs_entity_t, s->using_.count); state.using_frame = s->using_.count; state.using_frames[0] = s->using_.count; } if (instance) { ecs_set_scope(world, instance); } } } if (vars) { state.vars.root.parent = vars->cur; } do { expr = ptr = plecs_parse_stmt(world, name, expr, ptr, &state); if (!ptr) { goto error; } if (!ptr[0]) { break; /* End of expression */ } } while (true); ecs_set_scope(world, prev_scope); ecs_set_with(world, prev_with); plecs_clear_annotations(&state); if (state.is_module) { state.sp --; } if (state.sp != 0) { ecs_parser_error(name, expr, 0, "missing end of scope"); goto error; } if (state.assign_stmt) { ecs_parser_error(name, expr, 0, "unfinished assignment"); goto error; } if (state.errors) { goto error; } ecs_vars_fini(&state.vars); return 0; error: ecs_vars_fini(&state.vars); ecs_set_scope(world, state.scope[0]); ecs_set_with(world, prev_with); ecs_term_fini(&term); return -1; } int ecs_plecs_from_str( ecs_world_t *world, const char *name, const char *expr) { return flecs_plecs_parse(world, name, expr, NULL, 0, 0); } static 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; } rewind(file); /* Load contents in memory */ content = ecs_os_malloc(bytes + 1); size = (size_t)bytes; if (!(size = fread(content, 1, size, file)) && bytes) { ecs_err("%s: read zero bytes instead of %d", filename, size); ecs_os_free(content); content = NULL; goto error; } else { content[size] = '\0'; } fclose(file); return content; error: ecs_os_free(content); return NULL; } int ecs_plecs_from_file( ecs_world_t *world, const char *filename) { char *script = flecs_load_from_file(filename); if (!script) { return -1; } int result = ecs_plecs_from_str(world, filename, script); ecs_os_free(script); return result; } static ecs_id_t flecs_script_tag( ecs_entity_t script, ecs_entity_t instance) { if (!instance) { return ecs_pair_t(EcsScript, script); } else { return ecs_pair(EcsChildOf, instance); } } void ecs_script_clear( ecs_world_t *world, ecs_entity_t script, ecs_entity_t instance) { ecs_delete_with(world, flecs_script_tag(script, instance)); } int ecs_script_update( ecs_world_t *world, ecs_entity_t e, ecs_entity_t instance, const char *script, ecs_vars_t *vars) { ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(script != NULL, ECS_INTERNAL_ERROR, NULL); int result = 0; bool is_defer = ecs_is_deferred(world); ecs_suspend_readonly_state_t srs; ecs_world_t *real_world = NULL; if (is_defer) { ecs_assert(ecs_poly_is(world, ecs_world_t), ECS_INTERNAL_ERROR, NULL); real_world = flecs_suspend_readonly(world, &srs); ecs_assert(real_world != NULL, ECS_INTERNAL_ERROR, NULL); } ecs_script_clear(world, e, instance); EcsScript *s = ecs_get_mut(world, e, EcsScript); if (!s->script || ecs_os_strcmp(s->script, script)) { s->script = ecs_os_strdup(script); ecs_modified(world, e, EcsScript); } ecs_entity_t prev = ecs_set_with(world, flecs_script_tag(e, instance)); if (flecs_plecs_parse(world, ecs_get_name(world, e), script, vars, e, instance)) { ecs_delete_with(world, ecs_pair_t(EcsScript, e)); result = -1; } ecs_set_with(world, prev); if (is_defer) { flecs_resume_readonly(real_world, &srs); } return result; } ecs_entity_t ecs_script_init( ecs_world_t *world, const ecs_script_desc_t *desc) { const char *script = NULL; ecs_entity_t e = desc->entity; ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); ecs_check(desc != NULL, ECS_INTERNAL_ERROR, NULL); if (!e) { if (desc->filename) { e = ecs_new_from_path_w_sep(world, 0, desc->filename, "/", NULL); } else { e = ecs_new_id(world); } } script = desc->str; if (!script && desc->filename) { script = flecs_load_from_file(desc->filename); if (!script) { goto error; } } if (ecs_script_update(world, e, 0, script, NULL)) { goto error; } if (script != desc->str) { /* Safe cast, only happens when script is loaded from file */ ecs_os_free((char*)script); } return e; error: if (script != desc->str) { /* Safe cast, only happens when script is loaded from file */ ecs_os_free((char*)script); } if (!desc->entity) { ecs_delete(world, e); } return 0; } void FlecsScriptImport( ecs_world_t *world) { ECS_MODULE(world, FlecsScript); ECS_IMPORT(world, FlecsMeta); ecs_set_name_prefix(world, "Ecs"); ECS_COMPONENT_DEFINE(world, EcsScript); ecs_set_hooks(world, EcsScript, { .ctor = ecs_default_ctor, .move = ecs_move(EcsScript), .dtor = ecs_dtor(EcsScript) }); ecs_add_id(world, ecs_id(EcsScript), EcsTag); ecs_struct(world, { .entity = ecs_id(EcsScript), .members = { { .name = "using", .type = ecs_vector(world, { .entity = ecs_entity(world, { .name = "UsingVector" }), .type = ecs_id(ecs_entity_t) }), .count = 0 }, { .name = "script", .type = ecs_id(ecs_string_t), .count = 0 } } }); } #endif /** * @file addons/journal.c * @brief Journal addon. */ #ifdef FLECS_JOURNAL static char* flecs_journal_entitystr( ecs_world_t *world, ecs_entity_t entity) { char *path; const char *_path = ecs_get_symbol(world, entity); if (_path && !strchr(_path, '.')) { path = ecs_asprintf("#[blue]%s", _path); } else { uint32_t gen = entity >> 32; if (gen) { path = ecs_asprintf("#[normal]_%u_%u", (uint32_t)entity, gen); } else { path = ecs_asprintf("#[normal]_%u", (uint32_t)entity); } } return path; } static char* flecs_journal_idstr( ecs_world_t *world, ecs_id_t id) { if (ECS_IS_PAIR(id)) { char *first_path = flecs_journal_entitystr(world, ecs_pair_first(world, id)); char *second_path = flecs_journal_entitystr(world, ecs_pair_second(world, id)); char *result = ecs_asprintf("#[cyan]ecs_pair#[normal](%s, %s)", first_path, second_path); ecs_os_free(first_path); ecs_os_free(second_path); return result; } else if (!(id & ECS_ID_FLAGS_MASK)) { return flecs_journal_entitystr(world, id); } else { return ecs_id_str(world, id); } } static int flecs_journal_sp = 0; void flecs_journal_begin( ecs_world_t *world, ecs_journal_kind_t kind, ecs_entity_t entity, ecs_type_t *add, ecs_type_t *remove) { flecs_journal_sp ++; if (ecs_os_api.log_level_ < FLECS_JOURNAL_LOG_LEVEL) { return; } char *path = NULL; char *var_id = NULL; if (entity) { path = ecs_get_fullpath(world, entity); var_id = flecs_journal_entitystr(world, entity); } if (kind == EcsJournalNew) { ecs_print(4, "#[magenta]#ifndef #[normal]_var_%s", var_id); ecs_print(4, "#[magenta]#define #[normal]_var_%s", var_id); ecs_print(4, "#[green]ecs_entity_t %s;", var_id); ecs_print(4, "#[magenta]#endif"); ecs_print(4, "%s = #[cyan]ecs_new_id#[reset](world); " "#[grey] // %s = new()", var_id, path); } if (add) { for (int i = 0; i < add->count; i ++) { char *jidstr = flecs_journal_idstr(world, add->array[i]); char *idstr = ecs_id_str(world, add->array[i]); ecs_print(4, "#[cyan]ecs_add_id#[reset](world, %s, %s); " "#[grey] // add(%s, %s)", var_id, jidstr, path, idstr); ecs_os_free(idstr); ecs_os_free(jidstr); } } if (remove) { for (int i = 0; i < remove->count; i ++) { char *jidstr = flecs_journal_idstr(world, remove->array[i]); char *idstr = ecs_id_str(world, remove->array[i]); ecs_print(4, "#[cyan]ecs_remove_id#[reset](world, %s, %s); " "#[grey] // remove(%s, %s)", var_id, jidstr, path, idstr); ecs_os_free(idstr); ecs_os_free(jidstr); } } if (kind == EcsJournalClear) { ecs_print(4, "#[cyan]ecs_clear#[reset](world, %s); " "#[grey] // clear(%s)", var_id, path); } else if (kind == EcsJournalDelete) { ecs_print(4, "#[cyan]ecs_delete#[reset](world, %s); " "#[grey] // delete(%s)", var_id, path); } else if (kind == EcsJournalDeleteWith) { ecs_print(4, "#[cyan]ecs_delete_with#[reset](world, %s); " "#[grey] // delete_with(%s)", var_id, path); } else if (kind == EcsJournalRemoveAll) { ecs_print(4, "#[cyan]ecs_remove_all#[reset](world, %s); " "#[grey] // remove_all(%s)", var_id, path); } else if (kind == EcsJournalTableEvents) { ecs_print(4, "#[cyan]ecs_run_aperiodic#[reset](world, " "EcsAperiodicEmptyTables);"); } ecs_os_free(var_id); ecs_os_free(path); ecs_log_push(); } void flecs_journal_end(void) { flecs_journal_sp --; ecs_assert(flecs_journal_sp >= 0, ECS_INTERNAL_ERROR, NULL); ecs_log_pop(); } #endif /** * @file addons/module.c * @brief Module addon. */ #ifdef FLECS_MODULE #include char* ecs_module_path_from_c( const char *c_name) { ecs_strbuf_t str = ECS_STRBUF_INIT; const char *ptr; char ch; for (ptr = c_name; (ch = *ptr); ptr++) { if (isupper(ch)) { ch = flecs_ito(char, tolower(ch)); if (ptr != c_name) { ecs_strbuf_appendstrn(&str, ".", 1); } } ecs_strbuf_appendstrn(&str, &ch, 1); } return ecs_strbuf_get(&str); } ecs_entity_t ecs_import( ecs_world_t *world, ecs_module_action_t module, const char *module_name) { ecs_check(!(world->flags & EcsWorldReadonly), ECS_INVALID_WHILE_READONLY, NULL); ecs_entity_t old_scope = ecs_set_scope(world, 0); const char *old_name_prefix = world->info.name_prefix; char *path = ecs_module_path_from_c(module_name); ecs_entity_t e = ecs_lookup_fullpath(world, path); ecs_os_free(path); if (!e) { ecs_trace("#[magenta]import#[reset] %s", module_name); ecs_log_push(); /* Load module */ module(world); /* Lookup module entity (must be registered by module) */ e = ecs_lookup_fullpath(world, module_name); ecs_check(e != 0, ECS_MODULE_UNDEFINED, module_name); ecs_log_pop(); } /* Restore to previous state */ ecs_set_scope(world, old_scope); world->info.name_prefix = old_name_prefix; return e; error: return 0; } ecs_entity_t ecs_import_c( ecs_world_t *world, ecs_module_action_t module, const char *c_name) { char *name = ecs_module_path_from_c(c_name); ecs_entity_t e = ecs_import(world, module, name); ecs_os_free(name); return e; } ecs_entity_t ecs_import_from_library( ecs_world_t *world, const char *library_name, const char *module_name) { ecs_check(library_name != NULL, ECS_INVALID_PARAMETER, NULL); char *import_func = (char*)module_name; /* safe */ char *module = (char*)module_name; if (!ecs_os_has_modules() || !ecs_os_has_dl()) { ecs_err( "library loading not supported, set module_to_dl, dlopen, dlclose " "and dlproc os API callbacks first"); return 0; } /* If no module name is specified, try default naming convention for loading * the main module from the library */ if (!import_func) { import_func = ecs_os_malloc(ecs_os_strlen(library_name) + ECS_SIZEOF("Import")); ecs_assert(import_func != NULL, ECS_OUT_OF_MEMORY, NULL); const char *ptr; char ch, *bptr = import_func; bool capitalize = true; for (ptr = library_name; (ch = *ptr); ptr ++) { if (ch == '.') { capitalize = true; } else { if (capitalize) { *bptr = flecs_ito(char, toupper(ch)); bptr ++; capitalize = false; } else { *bptr = flecs_ito(char, tolower(ch)); bptr ++; } } } *bptr = '\0'; module = ecs_os_strdup(import_func); ecs_assert(module != NULL, ECS_OUT_OF_MEMORY, NULL); ecs_os_strcat(bptr, "Import"); } char *library_filename = ecs_os_module_to_dl(library_name); if (!library_filename) { ecs_err("failed to find library file for '%s'", library_name); if (module != module_name) { ecs_os_free(module); } return 0; } else { ecs_trace("found file '%s' for library '%s'", library_filename, library_name); } ecs_os_dl_t dl = ecs_os_dlopen(library_filename); if (!dl) { ecs_err("failed to load library '%s' ('%s')", library_name, library_filename); ecs_os_free(library_filename); if (module != module_name) { ecs_os_free(module); } return 0; } else { ecs_trace("library '%s' ('%s') loaded", library_name, library_filename); } ecs_module_action_t action = (ecs_module_action_t) ecs_os_dlproc(dl, import_func); if (!action) { ecs_err("failed to load import function %s from library %s", import_func, library_name); ecs_os_free(library_filename); ecs_os_dlclose(dl); return 0; } else { ecs_trace("found import function '%s' in library '%s' for module '%s'", import_func, library_name, module); } /* Do not free id, as it will be stored as the component identifier */ ecs_entity_t result = ecs_import(world, action, module); if (import_func != module_name) { ecs_os_free(import_func); } if (module != module_name) { ecs_os_free(module); } ecs_os_free(library_filename); return result; error: return 0; } ecs_entity_t ecs_module_init( ecs_world_t *world, const char *c_name, const ecs_component_desc_t *desc) { ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); ecs_poly_assert(world, ecs_world_t); ecs_entity_t old_scope = ecs_set_scope(world, 0); ecs_entity_t e = desc->entity; if (!e) { char *module_path = ecs_module_path_from_c(c_name); e = ecs_new_from_fullpath(world, module_path); ecs_set_symbol(world, e, module_path); ecs_os_free(module_path); } ecs_add_id(world, e, EcsModule); ecs_component_desc_t private_desc = *desc; private_desc.entity = e; if (desc->type.size) { ecs_entity_t result = ecs_component_init(world, &private_desc); ecs_assert(result != 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(result == e, ECS_INTERNAL_ERROR, NULL); (void)result; } ecs_set_scope(world, old_scope); return e; error: return 0; } #endif /** * @file meta/api.c * @brief API for creating entities with reflection data. */ /** * @file meta/meta.h * @brief Private functions for meta addon. */ #ifndef FLECS_META_PRIVATE_H #define FLECS_META_PRIVATE_H #ifdef FLECS_META void ecs_meta_type_serialized_init( ecs_iter_t *it); void ecs_meta_dtor_serialized( EcsMetaTypeSerialized *ptr); bool flecs_unit_validate( ecs_world_t *world, ecs_entity_t t, EcsUnit *data); #endif #endif #ifdef FLECS_META ecs_entity_t ecs_primitive_init( ecs_world_t *world, const ecs_primitive_desc_t *desc) { ecs_entity_t t = desc->entity; if (!t) { t = ecs_new_low_id(world); } ecs_set(world, t, EcsPrimitive, { desc->kind }); return t; } ecs_entity_t ecs_enum_init( ecs_world_t *world, const ecs_enum_desc_t *desc) { ecs_entity_t t = desc->entity; if (!t) { t = ecs_new_low_id(world); } ecs_add(world, t, EcsEnum); ecs_entity_t old_scope = ecs_set_scope(world, t); int i; for (i = 0; i < ECS_MEMBER_DESC_CACHE_SIZE; i ++) { const ecs_enum_constant_t *m_desc = &desc->constants[i]; if (!m_desc->name) { break; } ecs_entity_t c = ecs_entity_init(world, &(ecs_entity_desc_t){ .name = m_desc->name }); if (!m_desc->value) { ecs_add_id(world, c, EcsConstant); } else { ecs_set_pair_object(world, c, EcsConstant, ecs_i32_t, {m_desc->value}); } } ecs_set_scope(world, old_scope); if (i == 0) { ecs_err("enum '%s' has no constants", ecs_get_name(world, t)); ecs_delete(world, t); return 0; } return t; } ecs_entity_t ecs_bitmask_init( ecs_world_t *world, const ecs_bitmask_desc_t *desc) { ecs_entity_t t = desc->entity; if (!t) { t = ecs_new_low_id(world); } ecs_add(world, t, EcsBitmask); ecs_entity_t old_scope = ecs_set_scope(world, t); int i; for (i = 0; i < ECS_MEMBER_DESC_CACHE_SIZE; i ++) { const ecs_bitmask_constant_t *m_desc = &desc->constants[i]; if (!m_desc->name) { break; } ecs_entity_t c = ecs_entity_init(world, &(ecs_entity_desc_t){ .name = m_desc->name }); if (!m_desc->value) { ecs_add_id(world, c, EcsConstant); } else { ecs_set_pair_object(world, c, EcsConstant, ecs_u32_t, {m_desc->value}); } } ecs_set_scope(world, old_scope); if (i == 0) { ecs_err("bitmask '%s' has no constants", ecs_get_name(world, t)); ecs_delete(world, t); return 0; } return t; } ecs_entity_t ecs_array_init( ecs_world_t *world, const ecs_array_desc_t *desc) { ecs_entity_t t = desc->entity; if (!t) { t = ecs_new_low_id(world); } ecs_set(world, t, EcsArray, { .type = desc->type, .count = desc->count }); return t; } ecs_entity_t ecs_vector_init( ecs_world_t *world, const ecs_vector_desc_t *desc) { ecs_entity_t t = desc->entity; if (!t) { t = ecs_new_low_id(world); } ecs_set(world, t, EcsVector, { .type = desc->type }); return t; } ecs_entity_t ecs_struct_init( ecs_world_t *world, const ecs_struct_desc_t *desc) { ecs_entity_t t = desc->entity; if (!t) { t = ecs_new_low_id(world); } ecs_entity_t old_scope = ecs_set_scope(world, t); int i; for (i = 0; i < ECS_MEMBER_DESC_CACHE_SIZE; i ++) { const ecs_member_t *m_desc = &desc->members[i]; if (!m_desc->type) { break; } if (!m_desc->name) { ecs_err("member %d of struct '%s' does not have a name", i, ecs_get_name(world, t)); ecs_delete(world, t); return 0; } ecs_entity_t m = ecs_entity_init(world, &(ecs_entity_desc_t){ .name = m_desc->name }); ecs_set(world, m, EcsMember, { .type = m_desc->type, .count = m_desc->count, .offset = m_desc->offset, .unit = m_desc->unit }); } ecs_set_scope(world, old_scope); if (i == 0) { ecs_err("struct '%s' has no members", ecs_get_name(world, t)); ecs_delete(world, t); return 0; } if (!ecs_has(world, t, EcsStruct)) { /* Invalid members */ ecs_delete(world, t); return 0; } return t; } ecs_entity_t ecs_opaque_init( ecs_world_t *world, const ecs_opaque_desc_t *desc) { ecs_poly_assert(world, ecs_world_t); ecs_assert(desc != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(desc->type.as_type != 0, ECS_INVALID_PARAMETER, NULL); ecs_entity_t t = desc->entity; if (!t) { t = ecs_new_low_id(world); } ecs_set_ptr(world, t, EcsOpaque, &desc->type); return t; } ecs_entity_t ecs_unit_init( ecs_world_t *world, const ecs_unit_desc_t *desc) { ecs_entity_t t = desc->entity; if (!t) { t = ecs_new_low_id(world); } ecs_entity_t quantity = desc->quantity; if (quantity) { if (!ecs_has_id(world, quantity, EcsQuantity)) { ecs_err("entity '%s' for unit '%s' is not a quantity", ecs_get_name(world, quantity), ecs_get_name(world, t)); goto error; } ecs_add_pair(world, t, EcsQuantity, desc->quantity); } else { ecs_remove_pair(world, t, EcsQuantity, EcsWildcard); } EcsUnit *value = ecs_get_mut(world, t, EcsUnit); value->base = desc->base; value->over = desc->over; value->translation = desc->translation; value->prefix = desc->prefix; ecs_os_strset(&value->symbol, desc->symbol); if (!flecs_unit_validate(world, t, value)) { goto error; } ecs_modified(world, t, EcsUnit); return t; error: if (t) { ecs_delete(world, t); } return 0; } ecs_entity_t ecs_unit_prefix_init( ecs_world_t *world, const ecs_unit_prefix_desc_t *desc) { ecs_entity_t t = desc->entity; if (!t) { t = ecs_new_low_id(world); } ecs_set(world, t, EcsUnitPrefix, { .symbol = (char*)desc->symbol, .translation = desc->translation }); return t; } ecs_entity_t ecs_quantity_init( ecs_world_t *world, const ecs_entity_desc_t *desc) { ecs_entity_t t = ecs_entity_init(world, desc); if (!t) { return 0; } ecs_add_id(world, t, EcsQuantity); return t; } #endif /** * @file meta/serialized.c * @brief Serialize type into flat operations array to speed up deserialization. */ #ifdef FLECS_META static int flecs_meta_serialize_type( ecs_world_t *world, ecs_entity_t type, ecs_size_t offset, ecs_vec_t *ops); static ecs_meta_type_op_kind_t flecs_meta_primitive_to_op_kind(ecs_primitive_kind_t kind) { return EcsOpPrimitive + kind; } static ecs_size_t flecs_meta_type_size(ecs_world_t *world, ecs_entity_t type) { const EcsComponent *comp = ecs_get(world, type, EcsComponent); ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL); return comp->size; } static ecs_meta_type_op_t* flecs_meta_ops_add(ecs_vec_t *ops, ecs_meta_type_op_kind_t kind) { ecs_meta_type_op_t *op = ecs_vec_append_t(NULL, ops, ecs_meta_type_op_t); op->kind = kind; op->offset = 0; op->count = 1; op->op_count = 1; op->size = 0; op->name = NULL; op->members = NULL; op->type = 0; op->unit = 0; return op; } static ecs_meta_type_op_t* flecs_meta_ops_get(ecs_vec_t *ops, int32_t index) { ecs_meta_type_op_t* op = ecs_vec_get_t(ops, ecs_meta_type_op_t, index); ecs_assert(op != NULL, ECS_INTERNAL_ERROR, NULL); return op; } static int flecs_meta_serialize_primitive( ecs_world_t *world, ecs_entity_t type, ecs_size_t offset, ecs_vec_t *ops) { const EcsPrimitive *ptr = ecs_get(world, type, EcsPrimitive); if (!ptr) { char *name = ecs_get_fullpath(world, type); ecs_err("entity '%s' is not a primitive type", name); ecs_os_free(name); return -1; } ecs_meta_type_op_t *op = flecs_meta_ops_add(ops, flecs_meta_primitive_to_op_kind(ptr->kind)); op->offset = offset, op->type = type; op->size = flecs_meta_type_size(world, type); return 0; } static int flecs_meta_serialize_enum( ecs_world_t *world, ecs_entity_t type, ecs_size_t offset, ecs_vec_t *ops) { (void)world; ecs_meta_type_op_t *op = flecs_meta_ops_add(ops, EcsOpEnum); op->offset = offset, op->type = type; op->size = ECS_SIZEOF(ecs_i32_t); return 0; } static int flecs_meta_serialize_bitmask( ecs_world_t *world, ecs_entity_t type, ecs_size_t offset, ecs_vec_t *ops) { (void)world; ecs_meta_type_op_t *op = flecs_meta_ops_add(ops, EcsOpBitmask); op->offset = offset, op->type = type; op->size = ECS_SIZEOF(ecs_u32_t); return 0; } static int flecs_meta_serialize_array( ecs_world_t *world, ecs_entity_t type, ecs_size_t offset, ecs_vec_t *ops) { (void)world; ecs_meta_type_op_t *op = flecs_meta_ops_add(ops, EcsOpArray); op->offset = offset; op->type = type; op->size = flecs_meta_type_size(world, type); return 0; } static int flecs_meta_serialize_array_component( ecs_world_t *world, ecs_entity_t type, ecs_vec_t *ops) { const EcsArray *ptr = ecs_get(world, type, EcsArray); if (!ptr) { return -1; /* Should never happen, will trigger internal error */ } flecs_meta_serialize_type(world, ptr->type, 0, ops); ecs_meta_type_op_t *first = ecs_vec_first(ops); first->count = ptr->count; return 0; } static int flecs_meta_serialize_vector( ecs_world_t *world, ecs_entity_t type, ecs_size_t offset, ecs_vec_t *ops) { (void)world; ecs_meta_type_op_t *op = flecs_meta_ops_add(ops, EcsOpVector); op->offset = offset; op->type = type; op->size = flecs_meta_type_size(world, type); return 0; } static int flecs_meta_serialize_custom_type( ecs_world_t *world, ecs_entity_t type, ecs_size_t offset, ecs_vec_t *ops) { (void)world; ecs_meta_type_op_t *op = flecs_meta_ops_add(ops, EcsOpOpaque); op->offset = offset; op->type = type; op->size = flecs_meta_type_size(world, type); return 0; } static int flecs_meta_serialize_struct( ecs_world_t *world, ecs_entity_t type, ecs_size_t offset, ecs_vec_t *ops) { const EcsStruct *ptr = ecs_get(world, type, EcsStruct); ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); int32_t cur, first = ecs_vec_count(ops); ecs_meta_type_op_t *op = flecs_meta_ops_add(ops, EcsOpPush); op->offset = offset; op->type = type; op->size = flecs_meta_type_size(world, type); ecs_member_t *members = ecs_vec_first(&ptr->members); int32_t i, count = ecs_vec_count(&ptr->members); ecs_hashmap_t *member_index = NULL; if (count) { op->members = member_index = flecs_name_index_new( world, &world->allocator); } for (i = 0; i < count; i ++) { ecs_member_t *member = &members[i]; cur = ecs_vec_count(ops); flecs_meta_serialize_type(world, member->type, offset + member->offset, ops); op = flecs_meta_ops_get(ops, cur); if (!op->type) { op->type = member->type; } if (op->count <= 1) { op->count = member->count; } const char *member_name = member->name; op->name = member_name; op->unit = member->unit; op->op_count = ecs_vec_count(ops) - cur; flecs_name_index_ensure( member_index, flecs_ito(uint64_t, cur - first - 1), member_name, 0, 0); } flecs_meta_ops_add(ops, EcsOpPop); flecs_meta_ops_get(ops, first)->op_count = ecs_vec_count(ops) - first; return 0; } static int flecs_meta_serialize_type( ecs_world_t *world, ecs_entity_t type, ecs_size_t offset, ecs_vec_t *ops) { const EcsMetaType *ptr = ecs_get(world, type, EcsMetaType); if (!ptr) { char *path = ecs_get_fullpath(world, type); ecs_err("missing EcsMetaType for type %s'", path); ecs_os_free(path); return -1; } switch(ptr->kind) { case EcsPrimitiveType: return flecs_meta_serialize_primitive(world, type, offset, ops); case EcsEnumType: return flecs_meta_serialize_enum(world, type, offset, ops); case EcsBitmaskType: return flecs_meta_serialize_bitmask(world, type, offset, ops); case EcsStructType: return flecs_meta_serialize_struct(world, type, offset, ops); case EcsArrayType: return flecs_meta_serialize_array(world, type, offset, ops); case EcsVectorType: return flecs_meta_serialize_vector(world, type, offset, ops); case EcsOpaqueType: return flecs_meta_serialize_custom_type(world, type, offset, ops); } return 0; } static int flecs_meta_serialize_component( ecs_world_t *world, ecs_entity_t type, ecs_vec_t *ops) { const EcsMetaType *ptr = ecs_get(world, type, EcsMetaType); if (!ptr) { char *path = ecs_get_fullpath(world, type); ecs_err("missing EcsMetaType for type %s'", path); ecs_os_free(path); return -1; } switch(ptr->kind) { case EcsArrayType: return flecs_meta_serialize_array_component(world, type, ops); break; default: return flecs_meta_serialize_type(world, type, 0, ops); break; } return 0; } void ecs_meta_type_serialized_init( ecs_iter_t *it) { ecs_world_t *world = it->world; int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; ecs_vec_t ops; ecs_vec_init_t(NULL, &ops, ecs_meta_type_op_t, 0); flecs_meta_serialize_component(world, e, &ops); EcsMetaTypeSerialized *ptr = ecs_get_mut( world, e, EcsMetaTypeSerialized); if (ptr->ops.array) { ecs_meta_dtor_serialized(ptr); } ptr->ops = ops; } } #endif /** * @file meta/meta.c * @brief Meta addon. */ #ifdef FLECS_META /* ecs_string_t lifecycle */ static ECS_COPY(ecs_string_t, dst, src, { ecs_os_free(*(ecs_string_t*)dst); *(ecs_string_t*)dst = ecs_os_strdup(*(ecs_string_t*)src); }) static ECS_MOVE(ecs_string_t, dst, src, { ecs_os_free(*(ecs_string_t*)dst); *(ecs_string_t*)dst = *(ecs_string_t*)src; *(ecs_string_t*)src = NULL; }) static ECS_DTOR(ecs_string_t, ptr, { ecs_os_free(*(ecs_string_t*)ptr); *(ecs_string_t*)ptr = NULL; }) /* EcsMetaTypeSerialized lifecycle */ void ecs_meta_dtor_serialized( EcsMetaTypeSerialized *ptr) { int32_t i, count = ecs_vec_count(&ptr->ops); ecs_meta_type_op_t *ops = ecs_vec_first(&ptr->ops); for (i = 0; i < count; i ++) { ecs_meta_type_op_t *op = &ops[i]; if (op->members) { flecs_name_index_free(op->members); } } ecs_vec_fini_t(NULL, &ptr->ops, ecs_meta_type_op_t); } static ECS_COPY(EcsMetaTypeSerialized, dst, src, { ecs_meta_dtor_serialized(dst); dst->ops = ecs_vec_copy_t(NULL, &src->ops, ecs_meta_type_op_t); int32_t o, count = ecs_vec_count(&dst->ops); ecs_meta_type_op_t *ops = ecs_vec_first_t(&dst->ops, ecs_meta_type_op_t); for (o = 0; o < count; o ++) { ecs_meta_type_op_t *op = &ops[o]; if (op->members) { op->members = flecs_name_index_copy(op->members); } } }) static ECS_MOVE(EcsMetaTypeSerialized, dst, src, { ecs_meta_dtor_serialized(dst); dst->ops = src->ops; src->ops = (ecs_vec_t){0}; }) static ECS_DTOR(EcsMetaTypeSerialized, ptr, { ecs_meta_dtor_serialized(ptr); }) /* EcsStruct lifecycle */ static void flecs_struct_dtor( EcsStruct *ptr) { ecs_member_t *members = ecs_vec_first_t(&ptr->members, ecs_member_t); int32_t i, count = ecs_vec_count(&ptr->members); for (i = 0; i < count; i ++) { ecs_os_free((char*)members[i].name); } ecs_vec_fini_t(NULL, &ptr->members, ecs_member_t); } static ECS_COPY(EcsStruct, dst, src, { flecs_struct_dtor(dst); dst->members = ecs_vec_copy_t(NULL, &src->members, ecs_member_t); ecs_member_t *members = ecs_vec_first_t(&dst->members, ecs_member_t); int32_t m, count = ecs_vec_count(&dst->members); for (m = 0; m < count; m ++) { members[m].name = ecs_os_strdup(members[m].name); } }) static ECS_MOVE(EcsStruct, dst, src, { flecs_struct_dtor(dst); dst->members = src->members; src->members = (ecs_vec_t){0}; }) static ECS_DTOR(EcsStruct, ptr, { flecs_struct_dtor(ptr); }) /* EcsEnum lifecycle */ static void flecs_constants_dtor( ecs_map_t *constants) { ecs_map_iter_t it = ecs_map_iter(constants); while (ecs_map_next(&it)) { ecs_enum_constant_t *c = ecs_map_ptr(&it); ecs_os_free((char*)c->name); ecs_os_free(c); } ecs_map_fini(constants); } static void flecs_constants_copy( ecs_map_t *dst, ecs_map_t *src) { ecs_map_copy(dst, src); ecs_map_iter_t it = ecs_map_iter(dst); while (ecs_map_next(&it)) { ecs_enum_constant_t **r = ecs_map_ref(&it, ecs_enum_constant_t); ecs_enum_constant_t *src_c = r[0]; ecs_enum_constant_t *dst_c = ecs_os_calloc_t(ecs_enum_constant_t); *dst_c = *src_c; dst_c->name = ecs_os_strdup(dst_c->name); r[0] = dst_c; } } static ECS_COPY(EcsEnum, dst, src, { flecs_constants_dtor(&dst->constants); flecs_constants_copy(&dst->constants, &src->constants); }) static ECS_MOVE(EcsEnum, dst, src, { flecs_constants_dtor(&dst->constants); dst->constants = src->constants; ecs_os_zeromem(&src->constants); }) static ECS_DTOR(EcsEnum, ptr, { flecs_constants_dtor(&ptr->constants); }) /* EcsBitmask lifecycle */ static ECS_COPY(EcsBitmask, dst, src, { /* bitmask constant & enum constant have the same layout */ flecs_constants_dtor(&dst->constants); flecs_constants_copy(&dst->constants, &src->constants); }) static ECS_MOVE(EcsBitmask, dst, src, { flecs_constants_dtor(&dst->constants); dst->constants = src->constants; ecs_os_zeromem(&src->constants); }) static ECS_DTOR(EcsBitmask, ptr, { flecs_constants_dtor(&ptr->constants); }) /* EcsUnit lifecycle */ static void dtor_unit( EcsUnit *ptr) { ecs_os_free(ptr->symbol); } static ECS_COPY(EcsUnit, dst, src, { dtor_unit(dst); dst->symbol = ecs_os_strdup(src->symbol); dst->base = src->base; dst->over = src->over; dst->prefix = src->prefix; dst->translation = src->translation; }) static ECS_MOVE(EcsUnit, dst, src, { dtor_unit(dst); dst->symbol = src->symbol; dst->base = src->base; dst->over = src->over; dst->prefix = src->prefix; dst->translation = src->translation; src->symbol = NULL; src->base = 0; src->over = 0; src->prefix = 0; src->translation = (ecs_unit_translation_t){0}; }) static ECS_DTOR(EcsUnit, ptr, { dtor_unit(ptr); }) /* EcsUnitPrefix lifecycle */ static void dtor_unit_prefix( EcsUnitPrefix *ptr) { ecs_os_free(ptr->symbol); } static ECS_COPY(EcsUnitPrefix, dst, src, { dtor_unit_prefix(dst); dst->symbol = ecs_os_strdup(src->symbol); dst->translation = src->translation; }) static ECS_MOVE(EcsUnitPrefix, dst, src, { dtor_unit_prefix(dst); dst->symbol = src->symbol; dst->translation = src->translation; src->symbol = NULL; src->translation = (ecs_unit_translation_t){0}; }) static ECS_DTOR(EcsUnitPrefix, ptr, { dtor_unit_prefix(ptr); }) /* Type initialization */ static int flecs_init_type( ecs_world_t *world, ecs_entity_t type, ecs_type_kind_t kind, ecs_size_t size, ecs_size_t alignment) { ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(type != 0, ECS_INTERNAL_ERROR, NULL); EcsMetaType *meta_type = ecs_get_mut(world, type, EcsMetaType); if (meta_type->kind == 0) { meta_type->existing = ecs_has(world, type, EcsComponent); /* Ensure that component has a default constructor, to prevent crashing * serializers on uninitialized values. */ ecs_type_info_t *ti = flecs_type_info_ensure(world, type); if (!ti->hooks.ctor) { ti->hooks.ctor = ecs_default_ctor; } } else { if (meta_type->kind != kind) { ecs_err("type '%s' reregistered with different kind", ecs_get_name(world, type)); return -1; } } if (!meta_type->existing) { EcsComponent *comp = ecs_get_mut(world, type, EcsComponent); comp->size = size; comp->alignment = alignment; ecs_modified(world, type, EcsComponent); } else { const EcsComponent *comp = ecs_get(world, type, EcsComponent); if (comp->size < size) { ecs_err("computed size (%d) for '%s' is larger than actual type (%d)", size, ecs_get_name(world, type), comp->size); return -1; } if (comp->alignment < alignment) { ecs_err("computed alignment (%d) for '%s' is larger than actual type (%d)", alignment, ecs_get_name(world, type), comp->alignment); return -1; } if (comp->size == size && comp->alignment != alignment) { ecs_err("computed size for '%s' matches with actual type but " "alignment is different (%d vs. %d)", ecs_get_name(world, type), alignment, comp->alignment); return -1; } meta_type->partial = comp->size != size; } meta_type->kind = kind; meta_type->size = size; meta_type->alignment = alignment; ecs_modified(world, type, EcsMetaType); return 0; } #define init_type_t(world, type, kind, T) \ flecs_init_type(world, type, kind, ECS_SIZEOF(T), ECS_ALIGNOF(T)) static void flecs_set_struct_member( ecs_member_t *member, ecs_entity_t entity, const char *name, ecs_entity_t type, int32_t count, int32_t offset, ecs_entity_t unit) { member->member = entity; member->type = type; member->count = count; member->unit = unit; member->offset = offset; if (!count) { member->count = 1; } ecs_os_strset((char**)&member->name, name); } static int flecs_add_member_to_struct( ecs_world_t *world, ecs_entity_t type, ecs_entity_t member, EcsMember *m) { ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(type != 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(member != 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(m != NULL, ECS_INTERNAL_ERROR, NULL); const char *name = ecs_get_name(world, member); if (!name) { char *path = ecs_get_fullpath(world, type); ecs_err("member for struct '%s' does not have a name", path); ecs_os_free(path); return -1; } if (!m->type) { char *path = ecs_get_fullpath(world, member); ecs_err("member '%s' does not have a type", path); ecs_os_free(path); return -1; } if (ecs_get_typeid(world, m->type) == 0) { char *path = ecs_get_fullpath(world, member); char *ent_path = ecs_get_fullpath(world, m->type); ecs_err("member '%s.type' is '%s' which is not a type", path, ent_path); ecs_os_free(path); ecs_os_free(ent_path); return -1; } ecs_entity_t unit = m->unit; if (unit) { if (!ecs_has(world, unit, EcsUnit)) { ecs_err("entity '%s' for member '%s' is not a unit", ecs_get_name(world, unit), name); return -1; } if (ecs_has(world, m->type, EcsUnit) && m->type != unit) { ecs_err("unit mismatch for type '%s' and unit '%s' for member '%s'", ecs_get_name(world, m->type), ecs_get_name(world, unit), name); return -1; } } else { if (ecs_has(world, m->type, EcsUnit)) { unit = m->type; m->unit = unit; } } EcsStruct *s = ecs_get_mut(world, type, EcsStruct); ecs_assert(s != NULL, ECS_INTERNAL_ERROR, NULL); /* First check if member is already added to struct */ ecs_member_t *members = ecs_vec_first_t(&s->members, ecs_member_t); int32_t i, count = ecs_vec_count(&s->members); for (i = 0; i < count; i ++) { if (members[i].member == member) { flecs_set_struct_member( &members[i], member, name, m->type, m->count, m->offset, unit); break; } } /* If member wasn't added yet, add a new element to vector */ if (i == count) { ecs_vec_init_if_t(&s->members, ecs_member_t); ecs_member_t *elem = ecs_vec_append_t(NULL, &s->members, ecs_member_t); elem->name = NULL; flecs_set_struct_member(elem, member, name, m->type, m->count, m->offset, unit); /* Reobtain members array in case it was reallocated */ members = ecs_vec_first_t(&s->members, ecs_member_t); count ++; } bool explicit_offset = false; if (m->offset) { explicit_offset = true; } /* Compute member offsets and size & alignment of struct */ ecs_size_t size = 0; ecs_size_t alignment = 0; if (!explicit_offset) { for (i = 0; i < count; i ++) { ecs_member_t *elem = &members[i]; ecs_assert(elem->name != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(elem->type != 0, ECS_INTERNAL_ERROR, NULL); /* Get component of member type to get its size & alignment */ const EcsComponent *mbr_comp = ecs_get(world, elem->type, EcsComponent); if (!mbr_comp) { char *path = ecs_get_fullpath(world, member); ecs_err("member '%s' is not a type", path); ecs_os_free(path); return -1; } ecs_size_t member_size = mbr_comp->size; ecs_size_t member_alignment = mbr_comp->alignment; if (!member_size || !member_alignment) { char *path = ecs_get_fullpath(world, member); ecs_err("member '%s' has 0 size/alignment"); ecs_os_free(path); return -1; } member_size *= elem->count; size = ECS_ALIGN(size, member_alignment); elem->size = member_size; elem->offset = size; /* Synchronize offset with Member component */ if (elem->member == member) { m->offset = elem->offset; } else { EcsMember *other = ecs_get_mut(world, elem->member, EcsMember); other->offset = elem->offset; } size += member_size; if (member_alignment > alignment) { alignment = member_alignment; } } } else { /* If members have explicit offsets, we can't rely on computed * size/alignment values. Grab size of just added member instead. It * doesn't matter if the size doesn't correspond to the actual struct * size. The flecs_init_type function compares computed size with actual * (component) size to determine if the type is partial. */ const EcsComponent *cptr = ecs_get(world, m->type, EcsComponent); ecs_assert(cptr != NULL, ECS_INTERNAL_ERROR, NULL); size = cptr->size; alignment = cptr->alignment; } if (size == 0) { ecs_err("struct '%s' has 0 size", ecs_get_name(world, type)); return -1; } if (alignment == 0) { ecs_err("struct '%s' has 0 alignment", ecs_get_name(world, type)); return -1; } /* Align struct size to struct alignment */ size = ECS_ALIGN(size, alignment); ecs_modified(world, type, EcsStruct); /* Do this last as it triggers the update of EcsMetaTypeSerialized */ if (flecs_init_type(world, type, EcsStructType, size, alignment)) { return -1; } /* If current struct is also a member, assign to itself */ if (ecs_has(world, type, EcsMember)) { EcsMember *type_mbr = ecs_get_mut(world, type, EcsMember); ecs_assert(type_mbr != NULL, ECS_INTERNAL_ERROR, NULL); type_mbr->type = type; type_mbr->count = 1; ecs_modified(world, type, EcsMember); } return 0; } static int flecs_add_constant_to_enum( ecs_world_t *world, ecs_entity_t type, ecs_entity_t e, ecs_id_t constant_id) { EcsEnum *ptr = ecs_get_mut(world, type, EcsEnum); /* Remove constant from map if it was already added */ ecs_map_iter_t it = ecs_map_iter(&ptr->constants); while (ecs_map_next(&it)) { ecs_enum_constant_t *c = ecs_map_ptr(&it); if (c->constant == e) { ecs_os_free((char*)c->name); ecs_map_remove_free(&ptr->constants, ecs_map_key(&it)); } } /* Check if constant sets explicit value */ int32_t value = 0; bool value_set = false; if (ecs_id_is_pair(constant_id)) { if (ecs_pair_second(world, constant_id) != ecs_id(ecs_i32_t)) { char *path = ecs_get_fullpath(world, e); ecs_err("expected i32 type for enum constant '%s'", path); ecs_os_free(path); return -1; } const int32_t *value_ptr = ecs_get_pair_object( world, e, EcsConstant, ecs_i32_t); ecs_assert(value_ptr != NULL, ECS_INTERNAL_ERROR, NULL); value = *value_ptr; value_set = true; } /* Make sure constant value doesn't conflict if set / find the next value */ it = ecs_map_iter(&ptr->constants); while (ecs_map_next(&it)) { ecs_enum_constant_t *c = ecs_map_ptr(&it); if (value_set) { if (c->value == value) { char *path = ecs_get_fullpath(world, e); ecs_err("conflicting constant value %d for '%s' (other is '%s')", value, path, c->name); ecs_os_free(path); return -1; } } else { if (c->value >= value) { value = c->value + 1; } } } ecs_map_init_if(&ptr->constants, &world->allocator); ecs_enum_constant_t *c = ecs_map_insert_alloc_t(&ptr->constants, ecs_enum_constant_t, (ecs_map_key_t)value); c->name = ecs_os_strdup(ecs_get_name(world, e)); c->value = value; c->constant = e; ecs_i32_t *cptr = ecs_get_mut_pair_object( world, e, EcsConstant, ecs_i32_t); ecs_assert(cptr != NULL, ECS_INTERNAL_ERROR, NULL); cptr[0] = value; return 0; } static int flecs_add_constant_to_bitmask( ecs_world_t *world, ecs_entity_t type, ecs_entity_t e, ecs_id_t constant_id) { EcsBitmask *ptr = ecs_get_mut(world, type, EcsBitmask); /* Remove constant from map if it was already added */ ecs_map_iter_t it = ecs_map_iter(&ptr->constants); while (ecs_map_next(&it)) { ecs_bitmask_constant_t *c = ecs_map_ptr(&it); if (c->constant == e) { ecs_os_free((char*)c->name); ecs_map_remove_free(&ptr->constants, ecs_map_key(&it)); } } /* Check if constant sets explicit value */ uint32_t value = 1; if (ecs_id_is_pair(constant_id)) { if (ecs_pair_second(world, constant_id) != ecs_id(ecs_u32_t)) { char *path = ecs_get_fullpath(world, e); ecs_err("expected u32 type for bitmask constant '%s'", path); ecs_os_free(path); return -1; } const uint32_t *value_ptr = ecs_get_pair_object( world, e, EcsConstant, ecs_u32_t); ecs_assert(value_ptr != NULL, ECS_INTERNAL_ERROR, NULL); value = *value_ptr; } else { value = 1u << (ecs_u32_t)ecs_map_count(&ptr->constants); } /* Make sure constant value doesn't conflict */ it = ecs_map_iter(&ptr->constants); while (ecs_map_next(&it)) { ecs_bitmask_constant_t *c = ecs_map_ptr(&it); if (c->value == value) { char *path = ecs_get_fullpath(world, e); ecs_err("conflicting constant value for '%s' (other is '%s')", path, c->name); ecs_os_free(path); return -1; } } ecs_map_init_if(&ptr->constants, &world->allocator); ecs_bitmask_constant_t *c = ecs_map_insert_alloc_t(&ptr->constants, ecs_bitmask_constant_t, value); c->name = ecs_os_strdup(ecs_get_name(world, e)); c->value = value; c->constant = e; ecs_u32_t *cptr = ecs_get_mut_pair_object( world, e, EcsConstant, ecs_u32_t); ecs_assert(cptr != NULL, ECS_INTERNAL_ERROR, NULL); cptr[0] = value; return 0; } static void flecs_set_primitive(ecs_iter_t *it) { ecs_world_t *world = it->world; EcsPrimitive *type = ecs_field(it, EcsPrimitive, 1); int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; switch(type->kind) { case EcsBool: init_type_t(world, e, EcsPrimitiveType, bool); break; case EcsChar: init_type_t(world, e, EcsPrimitiveType, char); break; case EcsByte: init_type_t(world, e, EcsPrimitiveType, bool); break; case EcsU8: init_type_t(world, e, EcsPrimitiveType, uint8_t); break; case EcsU16: init_type_t(world, e, EcsPrimitiveType, uint16_t); break; case EcsU32: init_type_t(world, e, EcsPrimitiveType, uint32_t); break; case EcsU64: init_type_t(world, e, EcsPrimitiveType, uint64_t); break; case EcsI8: init_type_t(world, e, EcsPrimitiveType, int8_t); break; case EcsI16: init_type_t(world, e, EcsPrimitiveType, int16_t); break; case EcsI32: init_type_t(world, e, EcsPrimitiveType, int32_t); break; case EcsI64: init_type_t(world, e, EcsPrimitiveType, int64_t); break; case EcsF32: init_type_t(world, e, EcsPrimitiveType, float); break; case EcsF64: init_type_t(world, e, EcsPrimitiveType, double); break; case EcsUPtr: init_type_t(world, e, EcsPrimitiveType, uintptr_t); break; case EcsIPtr: init_type_t(world, e, EcsPrimitiveType, intptr_t); break; case EcsString: init_type_t(world, e, EcsPrimitiveType, char*); break; case EcsEntity: init_type_t(world, e, EcsPrimitiveType, ecs_entity_t); break; } } } static void flecs_set_member(ecs_iter_t *it) { ecs_world_t *world = it->world; EcsMember *member = ecs_field(it, EcsMember, 1); int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; ecs_entity_t parent = ecs_get_target(world, e, EcsChildOf, 0); if (!parent) { ecs_err("missing parent for member '%s'", ecs_get_name(world, e)); continue; } flecs_add_member_to_struct(world, parent, e, &member[i]); } } static void flecs_add_enum(ecs_iter_t *it) { ecs_world_t *world = it->world; int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; if (init_type_t(world, e, EcsEnumType, ecs_i32_t)) { continue; } ecs_add_id(world, e, EcsExclusive); ecs_add_id(world, e, EcsOneOf); ecs_add_id(world, e, EcsTag); } } static void flecs_add_bitmask(ecs_iter_t *it) { ecs_world_t *world = it->world; int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; if (init_type_t(world, e, EcsBitmaskType, ecs_u32_t)) { continue; } } } static void flecs_add_constant(ecs_iter_t *it) { ecs_world_t *world = it->world; int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; ecs_entity_t parent = ecs_get_target(world, e, EcsChildOf, 0); if (!parent) { ecs_err("missing parent for constant '%s'", ecs_get_name(world, e)); continue; } if (ecs_has(world, parent, EcsEnum)) { flecs_add_constant_to_enum(world, parent, e, it->event_id); } else if (ecs_has(world, parent, EcsBitmask)) { flecs_add_constant_to_bitmask(world, parent, e, it->event_id); } } } static void flecs_set_array(ecs_iter_t *it) { ecs_world_t *world = it->world; EcsArray *array = ecs_field(it, EcsArray, 1); int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; ecs_entity_t elem_type = array[i].type; int32_t elem_count = array[i].count; if (!elem_type) { ecs_err("array '%s' has no element type", ecs_get_name(world, e)); continue; } if (!elem_count) { ecs_err("array '%s' has size 0", ecs_get_name(world, e)); continue; } const EcsComponent *elem_ptr = ecs_get(world, elem_type, EcsComponent); if (flecs_init_type(world, e, EcsArrayType, elem_ptr->size * elem_count, elem_ptr->alignment)) { continue; } } } static void flecs_set_vector(ecs_iter_t *it) { ecs_world_t *world = it->world; EcsVector *array = ecs_field(it, EcsVector, 1); int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; ecs_entity_t elem_type = array[i].type; if (!elem_type) { ecs_err("vector '%s' has no element type", ecs_get_name(world, e)); continue; } if (init_type_t(world, e, EcsVectorType, ecs_vec_t)) { continue; } } } static void flecs_set_custom_type(ecs_iter_t *it) { ecs_world_t *world = it->world; EcsOpaque *serialize = ecs_field(it, EcsOpaque, 1); int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; ecs_entity_t elem_type = serialize[i].as_type; if (!elem_type) { ecs_err("custom type '%s' has no mapping type", ecs_get_name(world, e)); continue; } const EcsComponent *comp = ecs_get(world, e, EcsComponent); if (!comp || !comp->size || !comp->alignment) { ecs_err("custom type '%s' has no size/alignment, register as component first", ecs_get_name(world, e)); continue; } if (flecs_init_type(world, e, EcsOpaqueType, comp->size, comp->alignment)) { continue; } } } bool flecs_unit_validate( ecs_world_t *world, ecs_entity_t t, EcsUnit *data) { char *derived_symbol = NULL; const char *symbol = data->symbol; ecs_entity_t base = data->base; ecs_entity_t over = data->over; ecs_entity_t prefix = data->prefix; ecs_unit_translation_t translation = data->translation; if (base) { if (!ecs_has(world, base, EcsUnit)) { ecs_err("entity '%s' for unit '%s' used as base is not a unit", ecs_get_name(world, base), ecs_get_name(world, t)); goto error; } } if (over) { if (!base) { ecs_err("invalid unit '%s': cannot specify over without base", ecs_get_name(world, t)); goto error; } if (!ecs_has(world, over, EcsUnit)) { ecs_err("entity '%s' for unit '%s' used as over is not a unit", ecs_get_name(world, over), ecs_get_name(world, t)); goto error; } } if (prefix) { if (!base) { ecs_err("invalid unit '%s': cannot specify prefix without base", ecs_get_name(world, t)); goto error; } const EcsUnitPrefix *prefix_ptr = ecs_get(world, prefix, EcsUnitPrefix); if (!prefix_ptr) { ecs_err("entity '%s' for unit '%s' used as prefix is not a prefix", ecs_get_name(world, over), ecs_get_name(world, t)); goto error; } if (translation.factor || translation.power) { if (prefix_ptr->translation.factor != translation.factor || prefix_ptr->translation.power != translation.power) { ecs_err( "factor for unit '%s' is inconsistent with prefix '%s'", ecs_get_name(world, t), ecs_get_name(world, prefix)); goto error; } } else { translation = prefix_ptr->translation; } } if (base) { bool must_match = false; /* Must base symbol match symbol? */ ecs_strbuf_t sbuf = ECS_STRBUF_INIT; if (prefix) { const EcsUnitPrefix *ptr = ecs_get(world, prefix, EcsUnitPrefix); ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); if (ptr->symbol) { ecs_strbuf_appendstr(&sbuf, ptr->symbol); must_match = true; } } const EcsUnit *uptr = ecs_get(world, base, EcsUnit); ecs_assert(uptr != NULL, ECS_INTERNAL_ERROR, NULL); if (uptr->symbol) { ecs_strbuf_appendstr(&sbuf, uptr->symbol); } if (over) { uptr = ecs_get(world, over, EcsUnit); ecs_assert(uptr != NULL, ECS_INTERNAL_ERROR, NULL); if (uptr->symbol) { ecs_strbuf_appendch(&sbuf, '/'); ecs_strbuf_appendstr(&sbuf, uptr->symbol); must_match = true; } } derived_symbol = ecs_strbuf_get(&sbuf); if (derived_symbol && !ecs_os_strlen(derived_symbol)) { ecs_os_free(derived_symbol); derived_symbol = NULL; } if (derived_symbol && symbol && ecs_os_strcmp(symbol, derived_symbol)) { if (must_match) { ecs_err("symbol '%s' for unit '%s' does not match base" " symbol '%s'", symbol, ecs_get_name(world, t), derived_symbol); goto error; } } if (!symbol && derived_symbol && (prefix || over)) { ecs_os_free(data->symbol); data->symbol = derived_symbol; } else { ecs_os_free(derived_symbol); } } data->base = base; data->over = over; data->prefix = prefix; data->translation = translation; return true; error: ecs_os_free(derived_symbol); return false; } static void flecs_set_unit(ecs_iter_t *it) { EcsUnit *u = ecs_field(it, EcsUnit, 1); ecs_world_t *world = it->world; int i, count = it->count; for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; flecs_unit_validate(world, e, &u[i]); } } static void flecs_unit_quantity_monitor(ecs_iter_t *it) { ecs_world_t *world = it->world; int i, count = it->count; if (it->event == EcsOnAdd) { for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; ecs_add_pair(world, e, EcsQuantity, e); } } else { for (i = 0; i < count; i ++) { ecs_entity_t e = it->entities[i]; ecs_remove_pair(world, e, EcsQuantity, e); } } } static void ecs_meta_type_init_default_ctor(ecs_iter_t *it) { ecs_world_t *world = it->world; EcsMetaType *type = ecs_field(it, EcsMetaType, 1); int i; for (i = 0; i < it->count; i ++) { /* If a component is defined from reflection data, configure it with the * default constructor. This ensures that a new component value does not * contain uninitialized memory, which could cause serializers to crash * when for example inspecting string fields. */ if (!type->existing) { ecs_set_hooks_id(world, it->entities[i], &(ecs_type_hooks_t){ .ctor = ecs_default_ctor }); } } } static void flecs_member_on_set(ecs_iter_t *it) { EcsMember *mbr = it->ptrs[0]; if (!mbr->count) { mbr->count = 1; } } void FlecsMetaImport( ecs_world_t *world) { ECS_MODULE(world, FlecsMeta); ecs_set_name_prefix(world, "Ecs"); flecs_bootstrap_component(world, EcsMetaType); flecs_bootstrap_component(world, EcsMetaTypeSerialized); flecs_bootstrap_component(world, EcsPrimitive); flecs_bootstrap_component(world, EcsEnum); flecs_bootstrap_component(world, EcsBitmask); flecs_bootstrap_component(world, EcsMember); flecs_bootstrap_component(world, EcsStruct); flecs_bootstrap_component(world, EcsArray); flecs_bootstrap_component(world, EcsVector); flecs_bootstrap_component(world, EcsOpaque); flecs_bootstrap_component(world, EcsUnit); flecs_bootstrap_component(world, EcsUnitPrefix); flecs_bootstrap_tag(world, EcsConstant); flecs_bootstrap_tag(world, EcsQuantity); ecs_set_hooks(world, EcsMetaType, { .ctor = ecs_default_ctor }); ecs_set_hooks(world, EcsMetaTypeSerialized, { .ctor = ecs_default_ctor, .move = ecs_move(EcsMetaTypeSerialized), .copy = ecs_copy(EcsMetaTypeSerialized), .dtor = ecs_dtor(EcsMetaTypeSerialized) }); ecs_set_hooks(world, EcsStruct, { .ctor = ecs_default_ctor, .move = ecs_move(EcsStruct), .copy = ecs_copy(EcsStruct), .dtor = ecs_dtor(EcsStruct) }); ecs_set_hooks(world, EcsMember, { .ctor = ecs_default_ctor, .on_set = flecs_member_on_set }); ecs_set_hooks(world, EcsEnum, { .ctor = ecs_default_ctor, .move = ecs_move(EcsEnum), .copy = ecs_copy(EcsEnum), .dtor = ecs_dtor(EcsEnum) }); ecs_set_hooks(world, EcsBitmask, { .ctor = ecs_default_ctor, .move = ecs_move(EcsBitmask), .copy = ecs_copy(EcsBitmask), .dtor = ecs_dtor(EcsBitmask) }); ecs_set_hooks(world, EcsUnit, { .ctor = ecs_default_ctor, .move = ecs_move(EcsUnit), .copy = ecs_copy(EcsUnit), .dtor = ecs_dtor(EcsUnit) }); ecs_set_hooks(world, EcsUnitPrefix, { .ctor = ecs_default_ctor, .move = ecs_move(EcsUnitPrefix), .copy = ecs_copy(EcsUnitPrefix), .dtor = ecs_dtor(EcsUnitPrefix) }); /* Register triggers to finalize type information from component data */ ecs_entity_t old_scope = ecs_set_scope( /* Keep meta scope clean */ world, EcsFlecsInternals); ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms[0] = { .id = ecs_id(EcsPrimitive), .src.flags = EcsSelf }, .events = {EcsOnSet}, .callback = flecs_set_primitive }); ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms[0] = { .id = ecs_id(EcsMember), .src.flags = EcsSelf }, .events = {EcsOnSet}, .callback = flecs_set_member }); ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms[0] = { .id = ecs_id(EcsEnum), .src.flags = EcsSelf }, .events = {EcsOnAdd}, .callback = flecs_add_enum }); ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms[0] = { .id = ecs_id(EcsBitmask), .src.flags = EcsSelf }, .events = {EcsOnAdd}, .callback = flecs_add_bitmask }); ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms[0] = { .id = EcsConstant, .src.flags = EcsSelf }, .events = {EcsOnAdd}, .callback = flecs_add_constant }); ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms[0] = { .id = ecs_pair(EcsConstant, EcsWildcard), .src.flags = EcsSelf }, .events = {EcsOnSet}, .callback = flecs_add_constant }); ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms[0] = { .id = ecs_id(EcsArray), .src.flags = EcsSelf }, .events = {EcsOnSet}, .callback = flecs_set_array }); ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms[0] = { .id = ecs_id(EcsVector), .src.flags = EcsSelf }, .events = {EcsOnSet}, .callback = flecs_set_vector }); ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms[0] = { .id = ecs_id(EcsOpaque), .src.flags = EcsSelf }, .events = {EcsOnSet}, .callback = flecs_set_custom_type }); ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms[0] = { .id = ecs_id(EcsUnit), .src.flags = EcsSelf }, .events = {EcsOnSet}, .callback = flecs_set_unit }); ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms[0] = { .id = ecs_id(EcsMetaType), .src.flags = EcsSelf }, .events = {EcsOnSet}, .callback = ecs_meta_type_serialized_init }); ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms[0] = { .id = ecs_id(EcsMetaType), .src.flags = EcsSelf }, .events = {EcsOnSet}, .callback = ecs_meta_type_init_default_ctor }); ecs_observer_init(world, &(ecs_observer_desc_t){ .filter.terms = { { .id = ecs_id(EcsUnit) }, { .id = EcsQuantity } }, .events = { EcsMonitor }, .callback = flecs_unit_quantity_monitor }); ecs_set_scope(world, old_scope); /* Initialize primitive types */ #define ECS_PRIMITIVE(world, type, primitive_kind)\ ecs_entity_init(world, &(ecs_entity_desc_t){\ .id = ecs_id(ecs_##type##_t),\ .name = #type,\ .symbol = #type });\ ecs_set(world, ecs_id(ecs_##type##_t), EcsPrimitive, {\ .kind = primitive_kind\ }); ECS_PRIMITIVE(world, bool, EcsBool); ECS_PRIMITIVE(world, char, EcsChar); ECS_PRIMITIVE(world, byte, EcsByte); ECS_PRIMITIVE(world, u8, EcsU8); ECS_PRIMITIVE(world, u16, EcsU16); ECS_PRIMITIVE(world, u32, EcsU32); ECS_PRIMITIVE(world, u64, EcsU64); ECS_PRIMITIVE(world, uptr, EcsUPtr); ECS_PRIMITIVE(world, i8, EcsI8); ECS_PRIMITIVE(world, i16, EcsI16); ECS_PRIMITIVE(world, i32, EcsI32); ECS_PRIMITIVE(world, i64, EcsI64); ECS_PRIMITIVE(world, iptr, EcsIPtr); ECS_PRIMITIVE(world, f32, EcsF32); ECS_PRIMITIVE(world, f64, EcsF64); ECS_PRIMITIVE(world, string, EcsString); ECS_PRIMITIVE(world, entity, EcsEntity); #undef ECS_PRIMITIVE ecs_set_hooks(world, ecs_string_t, { .ctor = ecs_default_ctor, .copy = ecs_copy(ecs_string_t), .move = ecs_move(ecs_string_t), .dtor = ecs_dtor(ecs_string_t) }); /* Set default child components */ ecs_add_pair(world, ecs_id(EcsStruct), EcsDefaultChildComponent, ecs_id(EcsMember)); ecs_add_pair(world, ecs_id(EcsMember), EcsDefaultChildComponent, ecs_id(EcsMember)); ecs_add_pair(world, ecs_id(EcsEnum), EcsDefaultChildComponent, EcsConstant); ecs_add_pair(world, ecs_id(EcsBitmask), EcsDefaultChildComponent, EcsConstant); /* Relationship properties */ ecs_add_id(world, EcsQuantity, EcsExclusive); ecs_add_id(world, EcsQuantity, EcsTag); /* Initialize reflection data for meta components */ ecs_entity_t type_kind = ecs_enum_init(world, &(ecs_enum_desc_t){ .entity = ecs_entity(world, { .name = "TypeKind" }), .constants = { {.name = "PrimitiveType"}, {.name = "BitmaskType"}, {.name = "EnumType"}, {.name = "StructType"}, {.name = "ArrayType"}, {.name = "VectorType"}, {.name = "OpaqueType"} } }); ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_id(EcsMetaType), .members = { {.name = (char*)"kind", .type = type_kind} } }); ecs_entity_t primitive_kind = ecs_enum_init(world, &(ecs_enum_desc_t){ .entity = ecs_entity(world, { .name = "PrimitiveKind" }), .constants = { {.name = "Bool", 1}, {.name = "Char"}, {.name = "Byte"}, {.name = "U8"}, {.name = "U16"}, {.name = "U32"}, {.name = "U64"}, {.name = "I8"}, {.name = "I16"}, {.name = "I32"}, {.name = "I64"}, {.name = "F32"}, {.name = "F64"}, {.name = "UPtr"}, {.name = "IPtr"}, {.name = "String"}, {.name = "Entity"} } }); ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_id(EcsPrimitive), .members = { {.name = (char*)"kind", .type = primitive_kind} } }); ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_id(EcsMember), .members = { {.name = (char*)"type", .type = ecs_id(ecs_entity_t)}, {.name = (char*)"count", .type = ecs_id(ecs_i32_t)}, {.name = (char*)"unit", .type = ecs_id(ecs_entity_t)}, {.name = (char*)"offset", .type = ecs_id(ecs_i32_t)} } }); ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_id(EcsArray), .members = { {.name = (char*)"type", .type = ecs_id(ecs_entity_t)}, {.name = (char*)"count", .type = ecs_id(ecs_i32_t)}, } }); ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_id(EcsVector), .members = { {.name = (char*)"type", .type = ecs_id(ecs_entity_t)} } }); ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_id(EcsOpaque), .members = { { .name = (char*)"as_type", .type = ecs_id(ecs_entity_t) } } }); ecs_entity_t ut = ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_entity(world, { .name = "unit_translation" }), .members = { {.name = (char*)"factor", .type = ecs_id(ecs_i32_t)}, {.name = (char*)"power", .type = ecs_id(ecs_i32_t)} } }); ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_id(EcsUnit), .members = { {.name = (char*)"symbol", .type = ecs_id(ecs_string_t)}, {.name = (char*)"prefix", .type = ecs_id(ecs_entity_t)}, {.name = (char*)"base", .type = ecs_id(ecs_entity_t)}, {.name = (char*)"over", .type = ecs_id(ecs_entity_t)}, {.name = (char*)"translation", .type = ut} } }); ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_id(EcsUnitPrefix), .members = { {.name = (char*)"symbol", .type = ecs_id(ecs_string_t)}, {.name = (char*)"translation", .type = ut} } }); } #endif /** * @file meta/api.c * @brief API for assigning values of runtime types with reflection. */ #include #ifdef FLECS_META static const char* flecs_meta_op_kind_str( ecs_meta_type_op_kind_t kind) { switch(kind) { case EcsOpEnum: return "Enum"; case EcsOpBitmask: return "Bitmask"; case EcsOpArray: return "Array"; case EcsOpVector: return "Vector"; case EcsOpOpaque: return "Opaque"; case EcsOpPush: return "Push"; case EcsOpPop: return "Pop"; case EcsOpPrimitive: return "Primitive"; case EcsOpBool: return "Bool"; case EcsOpChar: return "Char"; case EcsOpByte: return "Byte"; case EcsOpU8: return "U8"; case EcsOpU16: return "U16"; case EcsOpU32: return "U32"; case EcsOpU64: return "U64"; case EcsOpI8: return "I8"; case EcsOpI16: return "I16"; case EcsOpI32: return "I32"; case EcsOpI64: return "I64"; case EcsOpF32: return "F32"; case EcsOpF64: return "F64"; case EcsOpUPtr: return "UPtr"; case EcsOpIPtr: return "IPtr"; case EcsOpString: return "String"; case EcsOpEntity: return "Entity"; default: return "<< invalid kind >>"; } } /* Get current scope */ static ecs_meta_scope_t* flecs_meta_cursor_get_scope( const ecs_meta_cursor_t *cursor) { ecs_check(cursor != NULL, ECS_INVALID_PARAMETER, NULL); return (ecs_meta_scope_t*)&cursor->scope[cursor->depth]; error: return NULL; } /* Restore scope, if dotmember was used */ static ecs_meta_scope_t* flecs_meta_cursor_restore_scope( ecs_meta_cursor_t *cursor, const ecs_meta_scope_t* scope) { ecs_check(cursor != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(scope != NULL, ECS_INVALID_PARAMETER, NULL); if (scope->prev_depth) { cursor->depth = scope->prev_depth; } error: return (ecs_meta_scope_t*)&cursor->scope[cursor->depth]; } /* Get current operation for scope */ static ecs_meta_type_op_t* flecs_meta_cursor_get_op( ecs_meta_scope_t *scope) { ecs_assert(scope->ops != NULL, ECS_INVALID_OPERATION, NULL); return &scope->ops[scope->op_cur]; } /* Get component for type in current scope */ static const EcsComponent* get_ecs_component( const ecs_world_t *world, ecs_meta_scope_t *scope) { const EcsComponent *comp = scope->comp; if (!comp) { comp = scope->comp = ecs_get(world, scope->type, EcsComponent); ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL); } return comp; } /* Get size for type in current scope */ static ecs_size_t get_size( const ecs_world_t *world, ecs_meta_scope_t *scope) { return get_ecs_component(world, scope)->size; } static int32_t get_elem_count( ecs_meta_scope_t *scope) { const EcsOpaque *opaque = scope->opaque; if (scope->vector) { return ecs_vec_count(scope->vector); } else if (opaque && opaque->count) { return flecs_uto(int32_t, opaque->count(scope[-1].ptr)); } ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); return op->count; } /* Get pointer to current field/element */ static ecs_meta_type_op_t* flecs_meta_cursor_get_ptr( const ecs_world_t *world, ecs_meta_scope_t *scope) { ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); ecs_size_t size = get_size(world, scope); const EcsOpaque *opaque = scope->opaque; if (scope->vector) { ecs_vec_set_min_count(NULL, scope->vector, size, scope->elem_cur + 1); scope->ptr = ecs_vec_first(scope->vector); } else if (opaque) { if (scope->is_collection) { if (!opaque->ensure_element) { char *str = ecs_get_fullpath(world, scope->type); ecs_err("missing ensure_element for opaque type %s", str); ecs_os_free(str); return NULL; } scope->is_empty_scope = false; void *opaque_ptr = opaque->ensure_element( scope->ptr, flecs_ito(size_t, scope->elem_cur)); ecs_assert(opaque_ptr != NULL, ECS_INVALID_OPERATION, "ensure_element returned NULL"); return opaque_ptr; } else if (op->name) { if (!opaque->ensure_member) { char *str = ecs_get_fullpath(world, scope->type); ecs_err("missing ensure_member for opaque type %s", str); ecs_os_free(str); return NULL; } ecs_assert(scope->ptr != NULL, ECS_INTERNAL_ERROR, NULL); return opaque->ensure_member(scope->ptr, op->name); } else { ecs_err("invalid operation for opaque type"); return NULL; } } return ECS_OFFSET(scope->ptr, size * scope->elem_cur + op->offset); } static int flecs_meta_cursor_push_type( const ecs_world_t *world, ecs_meta_scope_t *scope, ecs_entity_t type, void *ptr) { const EcsMetaTypeSerialized *ser = ecs_get( world, type, EcsMetaTypeSerialized); if (ser == NULL) { char *str = ecs_id_str(world, type); ecs_err("cannot open scope for entity '%s' which is not a type", str); ecs_os_free(str); return -1; } scope[0] = (ecs_meta_scope_t) { .type = type, .ops = ecs_vec_first_t(&ser->ops, ecs_meta_type_op_t), .op_count = ecs_vec_count(&ser->ops), .ptr = ptr }; return 0; } ecs_meta_cursor_t ecs_meta_cursor( const ecs_world_t *world, ecs_entity_t type, void *ptr) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(type != 0, ECS_INVALID_PARAMETER, NULL); ecs_check(ptr != NULL, ECS_INVALID_PARAMETER, NULL); ecs_meta_cursor_t result = { .world = world, .valid = true }; if (flecs_meta_cursor_push_type(world, result.scope, type, ptr) != 0) { result.valid = false; } return result; error: return (ecs_meta_cursor_t){ 0 }; } void* ecs_meta_get_ptr( ecs_meta_cursor_t *cursor) { return flecs_meta_cursor_get_ptr(cursor->world, flecs_meta_cursor_get_scope(cursor)); } int ecs_meta_next( ecs_meta_cursor_t *cursor) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); scope = flecs_meta_cursor_restore_scope(cursor, scope); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); if (scope->is_collection) { scope->elem_cur ++; scope->op_cur = 0; if (scope->opaque) { return 0; } if (scope->elem_cur >= get_elem_count(scope)) { ecs_err("out of collection bounds (%d)", scope->elem_cur); return -1; } return 0; } scope->op_cur += op->op_count; if (scope->op_cur >= scope->op_count) { ecs_err("out of bounds"); return -1; } return 0; } int ecs_meta_elem( ecs_meta_cursor_t *cursor, int32_t elem) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); if (!scope->is_collection) { ecs_err("ecs_meta_elem can be used for collections only"); return -1; } scope->elem_cur = elem; scope->op_cur = 0; if (scope->elem_cur >= get_elem_count(scope) || (scope->elem_cur < 0)) { ecs_err("out of collection bounds (%d)", scope->elem_cur); return -1; } return 0; } int ecs_meta_member( ecs_meta_cursor_t *cursor, const char *name) { if (cursor->depth == 0) { ecs_err("cannot move to member in root scope"); return -1; } ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); scope = flecs_meta_cursor_restore_scope(cursor, scope); ecs_hashmap_t *members = scope->members; const ecs_world_t *world = cursor->world; if (!members) { ecs_err("cannot move to member '%s' for non-struct type", name); return -1; } const uint64_t *cur_ptr = flecs_name_index_find_ptr(members, name, 0, 0); if (!cur_ptr) { char *path = ecs_get_fullpath(world, scope->type); ecs_err("unknown member '%s' for type '%s'", name, path); ecs_os_free(path); return -1; } scope->op_cur = flecs_uto(int32_t, cur_ptr[0]); const EcsOpaque *opaque = scope->opaque; if (opaque) { if (!opaque->ensure_member) { char *str = ecs_get_fullpath(world, scope->type); ecs_err("missing ensure_member for opaque type %s", str); ecs_os_free(str); } } return 0; } int ecs_meta_dotmember( ecs_meta_cursor_t *cursor, const char *name) { #ifdef FLECS_PARSER ecs_meta_scope_t *cur_scope = flecs_meta_cursor_get_scope(cursor); flecs_meta_cursor_restore_scope(cursor, cur_scope); int32_t prev_depth = cursor->depth; int dotcount = 0; char token[ECS_MAX_TOKEN_SIZE]; const char *ptr = name; while ((ptr = ecs_parse_token(NULL, NULL, ptr, token, '.'))) { if (ptr[0] != '.' && ptr[0]) { ecs_parser_error(NULL, name, ptr - name, "expected '.' or end of string"); goto error; } if (dotcount) { ecs_meta_push(cursor); } if (ecs_meta_member(cursor, token)) { goto error; } if (!ptr[0]) { break; } ptr ++; /* Skip . */ dotcount ++; } cur_scope = flecs_meta_cursor_get_scope(cursor); if (dotcount) { cur_scope->prev_depth = prev_depth; } return 0; error: return -1; #else (void)cursor; (void)name; ecs_err("the FLECS_PARSER addon is required for ecs_meta_dotmember"); return -1; #endif } int ecs_meta_push( ecs_meta_cursor_t *cursor) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); const ecs_world_t *world = cursor->world; if (cursor->depth == 0) { if (!cursor->is_primitive_scope) { if (op->kind > EcsOpScope) { cursor->is_primitive_scope = true; return 0; } } } void *ptr = flecs_meta_cursor_get_ptr(world, scope); cursor->depth ++; ecs_check(cursor->depth < ECS_META_MAX_SCOPE_DEPTH, ECS_INVALID_PARAMETER, NULL); ecs_meta_scope_t *next_scope = flecs_meta_cursor_get_scope(cursor); /* If we're not already in an inline array and this operation is an inline * array, push a frame for the array. * Doing this first ensures that inline arrays take precedence over other * kinds of push operations, such as for a struct element type. */ if (!scope->is_inline_array && op->count > 1 && !scope->is_collection) { /* Push a frame just for the element type, with inline_array = true */ next_scope[0] = (ecs_meta_scope_t){ .ops = op, .op_count = op->op_count, .ptr = scope->ptr, .type = op->type, .is_collection = true, .is_inline_array = true }; /* With 'is_inline_array' set to true we ensure that we can never push * the same inline array twice */ return 0; } /* Operation-specific switch behavior */ switch(op->kind) { /* Struct push: this happens when pushing a struct member. */ case EcsOpPush: { const EcsOpaque *opaque = scope->opaque; if (opaque) { /* If this is a nested push for an opaque type, push the type of the * element instead of the next operation. This ensures that we won't * use flattened offsets for nested members. */ if (flecs_meta_cursor_push_type( world, next_scope, op->type, ptr) != 0) { goto error; } /* Strip the Push operation since we already pushed */ next_scope->members = next_scope->ops[0].members; next_scope->ops = &next_scope->ops[1]; next_scope->op_count --; break; } /* The ops array contains a flattened list for all members and nested * members of a struct, so we can use (ops + 1) to initialize the ops * array of the next scope. */ next_scope[0] = (ecs_meta_scope_t) { .ops = &op[1], /* op after push */ .op_count = op->op_count - 1, /* don't include pop */ .ptr = scope->ptr, .type = op->type, .members = op->members }; break; } /* Array push for an array type. Arrays can be encoded in 2 ways: either by * setting the EcsMember::count member to a value >1, or by specifying an * array type as member type. This is the latter case. */ case EcsOpArray: { if (flecs_meta_cursor_push_type(world, next_scope, op->type, ptr) != 0) { goto error; } const EcsArray *type_ptr = ecs_get(world, op->type, EcsArray); next_scope->type = type_ptr->type; next_scope->is_collection = true; break; } /* Vector push */ case EcsOpVector: { next_scope->vector = ptr; if (flecs_meta_cursor_push_type(world, next_scope, op->type, NULL) != 0) { goto error; } const EcsVector *type_ptr = ecs_get(world, op->type, EcsVector); next_scope->type = type_ptr->type; next_scope->is_collection = true; break; } /* Opaque type push. Depending on the type the opaque type represents the * scope will be pushed as a struct or collection type. The type information * of the as_type is retained, as this is important for type checking and * for nested opaque type support. */ case EcsOpOpaque: { const EcsOpaque *type_ptr = ecs_get(world, op->type, EcsOpaque); ecs_entity_t as_type = type_ptr->as_type; const EcsMetaType *mtype_ptr = ecs_get(world, as_type, EcsMetaType); /* Check what kind of type the opaque type represents */ switch(mtype_ptr->kind) { /* Opaque vector support */ case EcsVectorType: { const EcsVector *vt = ecs_get(world, type_ptr->as_type, EcsVector); next_scope->type = vt->type; /* Push the element type of the vector type */ if (flecs_meta_cursor_push_type( world, next_scope, vt->type, NULL) != 0) { goto error; } /* This tracks whether any data was assigned inside the scope. When * the scope is popped, and is_empty_scope is still true, the vector * will be resized to 0. */ next_scope->is_empty_scope = true; next_scope->is_collection = true; break; } /* Opaque array support */ case EcsArrayType: { const EcsArray *at = ecs_get(world, type_ptr->as_type, EcsArray); next_scope->type = at->type; /* Push the element type of the array type */ if (flecs_meta_cursor_push_type( world, next_scope, at->type, NULL) != 0) { goto error; } /* Arrays are always a fixed size */ next_scope->is_empty_scope = false; next_scope->is_collection = true; break; } /* Opaque struct support */ case EcsStructType: /* Push struct type that represents the opaque type. This ensures * that the deserializer retains information about members and * member types, which is necessary for nested opaque types, and * allows for error checking. */ if (flecs_meta_cursor_push_type( world, next_scope, as_type, NULL) != 0) { goto error; } /* Strip push op, since we already pushed */ next_scope->members = next_scope->ops[0].members; next_scope->ops = &next_scope->ops[1]; next_scope->op_count --; break; default: break; } next_scope->ptr = ptr; next_scope->opaque = type_ptr; break; } default: { char *path = ecs_get_fullpath(world, scope->type); ecs_err("invalid push for type '%s'", path); ecs_os_free(path); goto error; } } if (scope->is_collection && !scope->opaque) { next_scope->ptr = ECS_OFFSET(next_scope->ptr, scope->elem_cur * get_size(world, scope)); } return 0; error: return -1; } int ecs_meta_pop( ecs_meta_cursor_t *cursor) { if (cursor->is_primitive_scope) { cursor->is_primitive_scope = false; return 0; } ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); scope = flecs_meta_cursor_restore_scope(cursor, scope); cursor->depth --; if (cursor->depth < 0) { ecs_err("unexpected end of scope"); return -1; } ecs_meta_scope_t *next_scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(next_scope); if (!scope->is_inline_array) { if (op->kind == EcsOpPush) { next_scope->op_cur += op->op_count - 1; /* push + op_count should point to the operation after pop */ op = flecs_meta_cursor_get_op(next_scope); ecs_assert(op->kind == EcsOpPop, ECS_INTERNAL_ERROR, NULL); } else if (op->kind == EcsOpArray || op->kind == EcsOpVector) { /* Collection type, nothing else to do */ } else if (op->kind == EcsOpOpaque) { const EcsOpaque *opaque = scope->opaque; if (scope->is_collection) { const EcsMetaType *mtype = ecs_get(cursor->world, opaque->as_type, EcsMetaType); ecs_assert(mtype != NULL, ECS_INTERNAL_ERROR, NULL); /* When popping a opaque collection type, call resize to make * sure the vector isn't larger than the number of elements we * deserialized. * If the opaque type represents an array, don't call resize. */ if (mtype->kind != EcsArrayType) { ecs_assert(opaque != NULL, ECS_INTERNAL_ERROR, NULL); if (!opaque->resize) { char *str = ecs_get_fullpath(cursor->world, scope->type); ecs_err("missing resize for opaque type %s", str); ecs_os_free(str); return -1; } if (scope->is_empty_scope) { /* If no values were serialized for scope, resize * collection to 0 elements. */ ecs_assert(!scope->elem_cur, ECS_INTERNAL_ERROR, NULL); opaque->resize(scope->ptr, 0); } else { /* Otherwise resize collection to the index of the last * deserialized element + 1 */ opaque->resize(scope->ptr, flecs_ito(size_t, scope->elem_cur + 1)); } } } else { /* Opaque struct type, nothing to be done */ } } else { /* should not have been able to push if the previous scope was not * a complex or collection type */ ecs_assert(false, ECS_INTERNAL_ERROR, NULL); } } else { /* Make sure that this was an inline array */ ecs_assert(next_scope->op_count > 1, ECS_INTERNAL_ERROR, NULL); } return 0; } bool ecs_meta_is_collection( const ecs_meta_cursor_t *cursor) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); return scope->is_collection; } ecs_entity_t ecs_meta_get_type( const ecs_meta_cursor_t *cursor) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); return op->type; } ecs_entity_t ecs_meta_get_unit( const ecs_meta_cursor_t *cursor) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); return op->unit; } const char* ecs_meta_get_member( const ecs_meta_cursor_t *cursor) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); return op->name; } /* Utilities for type conversions and bounds checking */ struct { int64_t min, max; } ecs_meta_bounds_signed[EcsMetaTypeOpKindLast + 1] = { [EcsOpBool] = {false, true}, [EcsOpChar] = {INT8_MIN, INT8_MAX}, [EcsOpByte] = {0, UINT8_MAX}, [EcsOpU8] = {0, UINT8_MAX}, [EcsOpU16] = {0, UINT16_MAX}, [EcsOpU32] = {0, UINT32_MAX}, [EcsOpU64] = {0, INT64_MAX}, [EcsOpI8] = {INT8_MIN, INT8_MAX}, [EcsOpI16] = {INT16_MIN, INT16_MAX}, [EcsOpI32] = {INT32_MIN, INT32_MAX}, [EcsOpI64] = {INT64_MIN, INT64_MAX}, [EcsOpUPtr] = {0, ((sizeof(void*) == 4) ? UINT32_MAX : INT64_MAX)}, [EcsOpIPtr] = { ((sizeof(void*) == 4) ? INT32_MIN : INT64_MIN), ((sizeof(void*) == 4) ? INT32_MAX : INT64_MAX) }, [EcsOpEntity] = {0, INT64_MAX}, [EcsOpEnum] = {INT32_MIN, INT32_MAX}, [EcsOpBitmask] = {0, INT32_MAX} }; struct { uint64_t min, max; } ecs_meta_bounds_unsigned[EcsMetaTypeOpKindLast + 1] = { [EcsOpBool] = {false, true}, [EcsOpChar] = {0, INT8_MAX}, [EcsOpByte] = {0, UINT8_MAX}, [EcsOpU8] = {0, UINT8_MAX}, [EcsOpU16] = {0, UINT16_MAX}, [EcsOpU32] = {0, UINT32_MAX}, [EcsOpU64] = {0, UINT64_MAX}, [EcsOpI8] = {0, INT8_MAX}, [EcsOpI16] = {0, INT16_MAX}, [EcsOpI32] = {0, INT32_MAX}, [EcsOpI64] = {0, INT64_MAX}, [EcsOpUPtr] = {0, ((sizeof(void*) == 4) ? UINT32_MAX : UINT64_MAX)}, [EcsOpIPtr] = {0, ((sizeof(void*) == 4) ? INT32_MAX : INT64_MAX)}, [EcsOpEntity] = {0, UINT64_MAX}, [EcsOpEnum] = {0, INT32_MAX}, [EcsOpBitmask] = {0, UINT32_MAX} }; struct { double min, max; } ecs_meta_bounds_float[EcsMetaTypeOpKindLast + 1] = { [EcsOpBool] = {false, true}, [EcsOpChar] = {INT8_MIN, INT8_MAX}, [EcsOpByte] = {0, UINT8_MAX}, [EcsOpU8] = {0, UINT8_MAX}, [EcsOpU16] = {0, UINT16_MAX}, [EcsOpU32] = {0, UINT32_MAX}, [EcsOpU64] = {0, (double)UINT64_MAX}, [EcsOpI8] = {INT8_MIN, INT8_MAX}, [EcsOpI16] = {INT16_MIN, INT16_MAX}, [EcsOpI32] = {INT32_MIN, INT32_MAX}, [EcsOpI64] = {INT64_MIN, (double)INT64_MAX}, [EcsOpUPtr] = {0, ((sizeof(void*) == 4) ? UINT32_MAX : (double)UINT64_MAX)}, [EcsOpIPtr] = { ((sizeof(void*) == 4) ? INT32_MIN : (double)INT64_MIN), ((sizeof(void*) == 4) ? INT32_MAX : (double)INT64_MAX) }, [EcsOpEntity] = {0, (double)UINT64_MAX}, [EcsOpEnum] = {INT32_MIN, INT32_MAX}, [EcsOpBitmask] = {0, UINT32_MAX} }; #define set_T(T, ptr, value)\ ((T*)ptr)[0] = ((T)value) #define case_T(kind, T, dst, src)\ case kind:\ set_T(T, dst, src);\ break #define case_T_checked(kind, T, dst, src, bounds)\ case kind:\ if ((src < bounds[kind].min) || (src > bounds[kind].max)){\ ecs_err("value %.0f is out of bounds for type %s", (double)src,\ flecs_meta_op_kind_str(kind));\ return -1;\ }\ set_T(T, dst, src);\ break #define cases_T_float(dst, src)\ case_T(EcsOpF32, ecs_f32_t, dst, src);\ case_T(EcsOpF64, ecs_f64_t, dst, src) #define cases_T_signed(dst, src, bounds)\ case_T_checked(EcsOpChar, ecs_char_t, dst, src, bounds);\ case_T_checked(EcsOpI8, ecs_i8_t, dst, src, bounds);\ case_T_checked(EcsOpI16, ecs_i16_t, dst, src, bounds);\ case_T_checked(EcsOpI32, ecs_i32_t, dst, src, bounds);\ case_T_checked(EcsOpI64, ecs_i64_t, dst, src, bounds);\ case_T_checked(EcsOpIPtr, ecs_iptr_t, dst, src, bounds);\ case_T_checked(EcsOpEnum, ecs_i32_t, dst, src, bounds) #define cases_T_unsigned(dst, src, bounds)\ case_T_checked(EcsOpByte, ecs_byte_t, dst, src, bounds);\ case_T_checked(EcsOpU8, ecs_u8_t, dst, src, bounds);\ case_T_checked(EcsOpU16, ecs_u16_t, dst, src, bounds);\ case_T_checked(EcsOpU32, ecs_u32_t, dst, src, bounds);\ case_T_checked(EcsOpU64, ecs_u64_t, dst, src, bounds);\ case_T_checked(EcsOpUPtr, ecs_uptr_t, dst, src, bounds);\ case_T_checked(EcsOpEntity, ecs_u64_t, dst, src, bounds);\ case_T_checked(EcsOpBitmask, ecs_u32_t, dst, src, bounds) #define cases_T_bool(dst, src)\ case EcsOpBool:\ set_T(ecs_bool_t, dst, value != 0);\ break static void flecs_meta_conversion_error( ecs_meta_cursor_t *cursor, ecs_meta_type_op_t *op, const char *from) { char *path = ecs_get_fullpath(cursor->world, op->type); ecs_err("unsupported conversion from %s to '%s'", from, path); ecs_os_free(path); } int ecs_meta_set_bool( ecs_meta_cursor_t *cursor, bool value) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); switch(op->kind) { cases_T_bool(ptr, value); cases_T_signed(ptr, value, ecs_meta_bounds_signed); cases_T_unsigned(ptr, value, ecs_meta_bounds_unsigned); case EcsOpOpaque: { const EcsOpaque *ot = ecs_get(cursor->world, op->type, EcsOpaque); if (ot && ot->assign_bool) { ot->assign_bool(ptr, value); break; } } /* fall through */ default: flecs_meta_conversion_error(cursor, op, "bool"); return -1; } return 0; } int ecs_meta_set_char( ecs_meta_cursor_t *cursor, char value) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); switch(op->kind) { cases_T_bool(ptr, value); cases_T_signed(ptr, value, ecs_meta_bounds_signed); case EcsOpOpaque: { const EcsOpaque *opaque = ecs_get(cursor->world, op->type, EcsOpaque); ecs_assert(opaque != NULL, ECS_INVALID_OPERATION, NULL); if (opaque->assign_char) { /* preferred operation */ opaque->assign_char(ptr, value); break; } else if (opaque->assign_uint) { opaque->assign_uint(ptr, (uint64_t)value); break; } else if (opaque->assign_int) { opaque->assign_int(ptr, value); break; } } /* fall through */ default: flecs_meta_conversion_error(cursor, op, "char"); return -1; } return 0; } int ecs_meta_set_int( ecs_meta_cursor_t *cursor, int64_t value) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); switch(op->kind) { cases_T_bool(ptr, value); cases_T_signed(ptr, value, ecs_meta_bounds_signed); cases_T_unsigned(ptr, value, ecs_meta_bounds_signed); cases_T_float(ptr, value); case EcsOpOpaque: { const EcsOpaque *opaque = ecs_get(cursor->world, op->type, EcsOpaque); ecs_assert(opaque != NULL, ECS_INVALID_OPERATION, NULL); if (opaque->assign_int) { /* preferred operation */ opaque->assign_int(ptr, value); break; } else if (opaque->assign_float) { /* most expressive */ opaque->assign_float(ptr, (double)value); break; } else if (opaque->assign_uint && (value > 0)) { opaque->assign_uint(ptr, flecs_ito(uint64_t, value)); break; } else if (opaque->assign_char && (value > 0) && (value < 256)) { opaque->assign_char(ptr, flecs_ito(char, value)); break; } } /* fall through */ default: { if(!value) return ecs_meta_set_null(cursor); flecs_meta_conversion_error(cursor, op, "int"); return -1; } } return 0; } int ecs_meta_set_uint( ecs_meta_cursor_t *cursor, uint64_t value) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); switch(op->kind) { cases_T_bool(ptr, value); cases_T_signed(ptr, value, ecs_meta_bounds_unsigned); cases_T_unsigned(ptr, value, ecs_meta_bounds_unsigned); cases_T_float(ptr, value); case EcsOpOpaque: { const EcsOpaque *opaque = ecs_get(cursor->world, op->type, EcsOpaque); ecs_assert(opaque != NULL, ECS_INVALID_OPERATION, NULL); if (opaque->assign_uint) { /* preferred operation */ opaque->assign_uint(ptr, value); break; } else if (opaque->assign_float) { /* most expressive */ opaque->assign_float(ptr, (double)value); break; } else if (opaque->assign_int && (value < INT64_MAX)) { opaque->assign_int(ptr, flecs_uto(int64_t, value)); break; } else if (opaque->assign_char && (value < 256)) { opaque->assign_char(ptr, flecs_uto(char, value)); break; } } /* fall through */ default: if(!value) return ecs_meta_set_null(cursor); flecs_meta_conversion_error(cursor, op, "uint"); return -1; } return 0; } int ecs_meta_set_float( ecs_meta_cursor_t *cursor, double value) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); switch(op->kind) { cases_T_bool(ptr, value); cases_T_signed(ptr, value, ecs_meta_bounds_float); cases_T_unsigned(ptr, value, ecs_meta_bounds_float); cases_T_float(ptr, value); case EcsOpOpaque: { const EcsOpaque *opaque = ecs_get(cursor->world, op->type, EcsOpaque); ecs_assert(opaque != NULL, ECS_INVALID_OPERATION, NULL); if (opaque->assign_float) { /* preferred operation */ opaque->assign_float(ptr, value); break; } else if (opaque->assign_int && /* most expressive */ (value <= (double)INT64_MAX) && (value >= (double)INT64_MIN)) { opaque->assign_int(ptr, (int64_t)value); break; } else if (opaque->assign_uint && (value >= 0)) { opaque->assign_uint(ptr, (uint64_t)value); break; } else if (opaque->assign_entity && (value >= 0)) { opaque->assign_entity( ptr, (ecs_world_t*)cursor->world, (ecs_entity_t)value); break; } } /* fall through */ default: flecs_meta_conversion_error(cursor, op, "float"); return -1; } return 0; } int ecs_meta_set_value( ecs_meta_cursor_t *cursor, const ecs_value_t *value) { ecs_check(value != NULL, ECS_INVALID_PARAMETER, NULL); ecs_entity_t type = value->type; ecs_check(type != 0, ECS_INVALID_PARAMETER, NULL); const EcsMetaType *mt = ecs_get(cursor->world, type, EcsMetaType); if (!mt) { ecs_err("type of value does not have reflection data"); return -1; } if (mt->kind == EcsPrimitiveType) { const EcsPrimitive *prim = ecs_get(cursor->world, type, EcsPrimitive); ecs_check(prim != NULL, ECS_INTERNAL_ERROR, NULL); switch(prim->kind) { case EcsBool: return ecs_meta_set_bool(cursor, *(bool*)value->ptr); case EcsChar: return ecs_meta_set_char(cursor, *(char*)value->ptr); case EcsByte: return ecs_meta_set_uint(cursor, *(uint8_t*)value->ptr); case EcsU8: return ecs_meta_set_uint(cursor, *(uint8_t*)value->ptr); case EcsU16: return ecs_meta_set_uint(cursor, *(uint16_t*)value->ptr); case EcsU32: return ecs_meta_set_uint(cursor, *(uint32_t*)value->ptr); case EcsU64: return ecs_meta_set_uint(cursor, *(uint64_t*)value->ptr); case EcsI8: return ecs_meta_set_int(cursor, *(int8_t*)value->ptr); case EcsI16: return ecs_meta_set_int(cursor, *(int16_t*)value->ptr); case EcsI32: return ecs_meta_set_int(cursor, *(int32_t*)value->ptr); case EcsI64: return ecs_meta_set_int(cursor, *(int64_t*)value->ptr); case EcsF32: return ecs_meta_set_float(cursor, (double)*(float*)value->ptr); case EcsF64: return ecs_meta_set_float(cursor, *(double*)value->ptr); case EcsUPtr: return ecs_meta_set_uint(cursor, *(uintptr_t*)value->ptr); case EcsIPtr: return ecs_meta_set_int(cursor, *(intptr_t*)value->ptr); case EcsString: return ecs_meta_set_string(cursor, *(char**)value->ptr); case EcsEntity: return ecs_meta_set_entity(cursor, *(ecs_entity_t*)value->ptr); default: ecs_throw(ECS_INTERNAL_ERROR, "invalid type kind"); goto error; } } else if (mt->kind == EcsEnumType) { return ecs_meta_set_int(cursor, *(int32_t*)value->ptr); } else if (mt->kind == EcsBitmaskType) { return ecs_meta_set_int(cursor, *(uint32_t*)value->ptr); } else { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); if (op->type != value->type) { char *type_str = ecs_get_fullpath(cursor->world, value->type); flecs_meta_conversion_error(cursor, op, type_str); ecs_os_free(type_str); goto error; } return ecs_value_copy(cursor->world, value->type, ptr, value->ptr); } error: return -1; } static int flecs_meta_add_bitmask_constant( ecs_meta_cursor_t *cursor, ecs_meta_type_op_t *op, void *out, const char *value) { ecs_assert(op->type != 0, ECS_INTERNAL_ERROR, NULL); if (!ecs_os_strcmp(value, "0")) { return 0; } ecs_entity_t c = ecs_lookup_child(cursor->world, op->type, value); if (!c) { char *path = ecs_get_fullpath(cursor->world, op->type); ecs_err("unresolved bitmask constant '%s' for type '%s'", value, path); ecs_os_free(path); return -1; } const ecs_u32_t *v = ecs_get_pair_object( cursor->world, c, EcsConstant, ecs_u32_t); if (v == NULL) { char *path = ecs_get_fullpath(cursor->world, op->type); ecs_err("'%s' is not an bitmask constant for type '%s'", value, path); ecs_os_free(path); return -1; } *(ecs_u32_t*)out |= v[0]; return 0; } static int flecs_meta_parse_bitmask( ecs_meta_cursor_t *cursor, ecs_meta_type_op_t *op, void *out, const char *value) { char token[ECS_MAX_TOKEN_SIZE]; const char *prev = value, *ptr = value; *(ecs_u32_t*)out = 0; while ((ptr = strchr(ptr, '|'))) { ecs_os_memcpy(token, prev, ptr - prev); token[ptr - prev] = '\0'; if (flecs_meta_add_bitmask_constant(cursor, op, out, token) != 0) { return -1; } ptr ++; prev = ptr; } if (flecs_meta_add_bitmask_constant(cursor, op, out, prev) != 0) { return -1; } return 0; } static int flecs_meta_cursor_lookup( ecs_meta_cursor_t *cursor, const char *value, ecs_entity_t *out) { if (ecs_os_strcmp(value, "0")) { if (cursor->lookup_action) { *out = cursor->lookup_action( cursor->world, value, cursor->lookup_ctx); } else { *out = ecs_lookup_path(cursor->world, 0, value); } if (!*out) { ecs_err("unresolved entity identifier '%s'", value); return -1; } } return 0; } int ecs_meta_set_string( ecs_meta_cursor_t *cursor, const char *value) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); switch(op->kind) { case EcsOpBool: if (!ecs_os_strcmp(value, "true")) { set_T(ecs_bool_t, ptr, true); } else if (!ecs_os_strcmp(value, "false")) { set_T(ecs_bool_t, ptr, false); } else if (isdigit(value[0])) { if (!ecs_os_strcmp(value, "0")) { set_T(ecs_bool_t, ptr, false); } else { set_T(ecs_bool_t, ptr, true); } } else { ecs_err("invalid value for boolean '%s'", value); return -1; } break; case EcsOpI8: case EcsOpU8: case EcsOpByte: set_T(ecs_i8_t, ptr, atol(value)); break; case EcsOpChar: set_T(char, ptr, value[0]); break; case EcsOpI16: case EcsOpU16: set_T(ecs_i16_t, ptr, atol(value)); break; case EcsOpI32: case EcsOpU32: set_T(ecs_i32_t, ptr, atol(value)); break; case EcsOpI64: case EcsOpU64: set_T(ecs_i64_t, ptr, atol(value)); break; case EcsOpIPtr: case EcsOpUPtr: set_T(ecs_iptr_t, ptr, atol(value)); break; case EcsOpF32: set_T(ecs_f32_t, ptr, atof(value)); break; case EcsOpF64: set_T(ecs_f64_t, ptr, atof(value)); break; case EcsOpString: { ecs_assert(*(ecs_string_t*)ptr != value, ECS_INVALID_PARAMETER, NULL); ecs_os_free(*(ecs_string_t*)ptr); char *result = ecs_os_strdup(value); set_T(ecs_string_t, ptr, result); break; } case EcsOpEnum: { ecs_assert(op->type != 0, ECS_INTERNAL_ERROR, NULL); ecs_entity_t c = ecs_lookup_child(cursor->world, op->type, value); if (!c) { char *path = ecs_get_fullpath(cursor->world, op->type); ecs_err("unresolved enum constant '%s' for type '%s'", value, path); ecs_os_free(path); return -1; } const ecs_i32_t *v = ecs_get_pair_object( cursor->world, c, EcsConstant, ecs_i32_t); if (v == NULL) { char *path = ecs_get_fullpath(cursor->world, op->type); ecs_err("'%s' is not an enum constant for type '%s'", value, path); ecs_os_free(path); return -1; } set_T(ecs_i32_t, ptr, v[0]); break; } case EcsOpBitmask: if (flecs_meta_parse_bitmask(cursor, op, ptr, value) != 0) { return -1; } break; case EcsOpEntity: { ecs_entity_t e = 0; if (flecs_meta_cursor_lookup(cursor, value, &e)) { return -1; } set_T(ecs_entity_t, ptr, e); break; } case EcsOpPop: ecs_err("excess element '%s' in scope", value); return -1; case EcsOpOpaque: { const EcsOpaque *opaque = ecs_get(cursor->world, op->type, EcsOpaque); ecs_assert(opaque != NULL, ECS_INVALID_OPERATION, NULL); if (opaque->assign_string) { /* preferred */ opaque->assign_string(ptr, value); break; } else if (opaque->assign_char && value[0] && !value[1]) { opaque->assign_char(ptr, value[0]); break; } else if (opaque->assign_entity) { ecs_entity_t e = 0; if (flecs_meta_cursor_lookup(cursor, value, &e)) { return -1; } opaque->assign_entity(ptr, (ecs_world_t*)cursor->world, e); break; } } /* fall through */ default: ecs_err("unsupported conversion from string '%s' to '%s'", value, flecs_meta_op_kind_str(op->kind)); return -1; } return 0; } int ecs_meta_set_string_literal( ecs_meta_cursor_t *cursor, const char *value) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); ecs_size_t len = ecs_os_strlen(value); if (value[0] != '\"' || value[len - 1] != '\"') { ecs_err("invalid string literal '%s'", value); return -1; } switch(op->kind) { case EcsOpChar: set_T(ecs_char_t, ptr, value[1]); break; default: case EcsOpEntity: case EcsOpString: case EcsOpOpaque: len -= 2; char *result = ecs_os_malloc(len + 1); ecs_os_memcpy(result, value + 1, len); result[len] = '\0'; if (ecs_meta_set_string(cursor, result)) { ecs_os_free(result); return -1; } ecs_os_free(result); break; } return 0; } int ecs_meta_set_entity( ecs_meta_cursor_t *cursor, ecs_entity_t value) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); switch(op->kind) { case EcsOpEntity: set_T(ecs_entity_t, ptr, value); break; case EcsOpOpaque: { const EcsOpaque *opaque = ecs_get(cursor->world, op->type, EcsOpaque); if (opaque && opaque->assign_entity) { opaque->assign_entity(ptr, (ecs_world_t*)cursor->world, value); break; } } /* fall through */ default: flecs_meta_conversion_error(cursor, op, "entity"); return -1; } return 0; } int ecs_meta_set_null( ecs_meta_cursor_t *cursor) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); switch (op->kind) { case EcsOpString: ecs_os_free(*(char**)ptr); set_T(ecs_string_t, ptr, NULL); break; case EcsOpOpaque: { const EcsOpaque *ot = ecs_get(cursor->world, op->type, EcsOpaque); if (ot && ot->assign_null) { ot->assign_null(ptr); break; } } /* fall through */ default: flecs_meta_conversion_error(cursor, op, "null"); return -1; } return 0; } bool ecs_meta_get_bool( const ecs_meta_cursor_t *cursor) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); switch(op->kind) { case EcsOpBool: return *(ecs_bool_t*)ptr; case EcsOpI8: return *(ecs_i8_t*)ptr != 0; case EcsOpU8: return *(ecs_u8_t*)ptr != 0; case EcsOpChar: return *(ecs_char_t*)ptr != 0; case EcsOpByte: return *(ecs_u8_t*)ptr != 0; case EcsOpI16: return *(ecs_i16_t*)ptr != 0; case EcsOpU16: return *(ecs_u16_t*)ptr != 0; case EcsOpI32: return *(ecs_i32_t*)ptr != 0; case EcsOpU32: return *(ecs_u32_t*)ptr != 0; case EcsOpI64: return *(ecs_i64_t*)ptr != 0; case EcsOpU64: return *(ecs_u64_t*)ptr != 0; case EcsOpIPtr: return *(ecs_iptr_t*)ptr != 0; case EcsOpUPtr: return *(ecs_uptr_t*)ptr != 0; case EcsOpF32: return *(ecs_f32_t*)ptr != 0; case EcsOpF64: return *(ecs_f64_t*)ptr != 0; case EcsOpString: return *(const char**)ptr != NULL; case EcsOpEnum: return *(ecs_i32_t*)ptr != 0; case EcsOpBitmask: return *(ecs_u32_t*)ptr != 0; case EcsOpEntity: return *(ecs_entity_t*)ptr != 0; default: ecs_throw(ECS_INVALID_PARAMETER, "invalid element for bool"); } error: return 0; } char ecs_meta_get_char( const ecs_meta_cursor_t *cursor) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); switch(op->kind) { case EcsOpChar: return *(ecs_char_t*)ptr != 0; default: ecs_throw(ECS_INVALID_PARAMETER, "invalid element for char"); } error: return 0; } int64_t ecs_meta_get_int( const ecs_meta_cursor_t *cursor) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); switch(op->kind) { case EcsOpBool: return *(ecs_bool_t*)ptr; case EcsOpI8: return *(ecs_i8_t*)ptr; case EcsOpU8: return *(ecs_u8_t*)ptr; case EcsOpChar: return *(ecs_char_t*)ptr; case EcsOpByte: return *(ecs_u8_t*)ptr; case EcsOpI16: return *(ecs_i16_t*)ptr; case EcsOpU16: return *(ecs_u16_t*)ptr; case EcsOpI32: return *(ecs_i32_t*)ptr; case EcsOpU32: return *(ecs_u32_t*)ptr; case EcsOpI64: return *(ecs_i64_t*)ptr; case EcsOpU64: return flecs_uto(int64_t, *(ecs_u64_t*)ptr); case EcsOpIPtr: return *(ecs_iptr_t*)ptr; case EcsOpUPtr: return flecs_uto(int64_t, *(ecs_uptr_t*)ptr); case EcsOpF32: return (int64_t)*(ecs_f32_t*)ptr; case EcsOpF64: return (int64_t)*(ecs_f64_t*)ptr; case EcsOpString: return atoi(*(const char**)ptr); case EcsOpEnum: return *(ecs_i32_t*)ptr; case EcsOpBitmask: return *(ecs_u32_t*)ptr; case EcsOpEntity: ecs_throw(ECS_INVALID_PARAMETER, "invalid conversion from entity to int"); break; default: ecs_throw(ECS_INVALID_PARAMETER, "invalid element for int"); } error: return 0; } uint64_t ecs_meta_get_uint( const ecs_meta_cursor_t *cursor) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); switch(op->kind) { case EcsOpBool: return *(ecs_bool_t*)ptr; case EcsOpI8: return flecs_ito(uint64_t, *(ecs_i8_t*)ptr); case EcsOpU8: return *(ecs_u8_t*)ptr; case EcsOpChar: return flecs_ito(uint64_t, *(ecs_char_t*)ptr); case EcsOpByte: return flecs_ito(uint64_t, *(ecs_u8_t*)ptr); case EcsOpI16: return flecs_ito(uint64_t, *(ecs_i16_t*)ptr); case EcsOpU16: return *(ecs_u16_t*)ptr; case EcsOpI32: return flecs_ito(uint64_t, *(ecs_i32_t*)ptr); case EcsOpU32: return *(ecs_u32_t*)ptr; case EcsOpI64: return flecs_ito(uint64_t, *(ecs_i64_t*)ptr); case EcsOpU64: return *(ecs_u64_t*)ptr; case EcsOpIPtr: return flecs_ito(uint64_t, *(ecs_i64_t*)ptr); case EcsOpUPtr: return *(ecs_uptr_t*)ptr; case EcsOpF32: return flecs_ito(uint64_t, *(ecs_f32_t*)ptr); case EcsOpF64: return flecs_ito(uint64_t, *(ecs_f64_t*)ptr); case EcsOpString: return flecs_ito(uint64_t, atoi(*(const char**)ptr)); case EcsOpEnum: return flecs_ito(uint64_t, *(ecs_i32_t*)ptr); case EcsOpBitmask: return *(ecs_u32_t*)ptr; case EcsOpEntity: return *(ecs_entity_t*)ptr; default: ecs_throw(ECS_INVALID_PARAMETER, "invalid element for uint"); } error: return 0; } double ecs_meta_get_float( const ecs_meta_cursor_t *cursor) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); switch(op->kind) { case EcsOpBool: return *(ecs_bool_t*)ptr; case EcsOpI8: return *(ecs_i8_t*)ptr; case EcsOpU8: return *(ecs_u8_t*)ptr; case EcsOpChar: return *(ecs_char_t*)ptr; case EcsOpByte: return *(ecs_u8_t*)ptr; case EcsOpI16: return *(ecs_i16_t*)ptr; case EcsOpU16: return *(ecs_u16_t*)ptr; case EcsOpI32: return *(ecs_i32_t*)ptr; case EcsOpU32: return *(ecs_u32_t*)ptr; case EcsOpI64: return (double)*(ecs_i64_t*)ptr; case EcsOpU64: return (double)*(ecs_u64_t*)ptr; case EcsOpIPtr: return (double)*(ecs_iptr_t*)ptr; case EcsOpUPtr: return (double)*(ecs_uptr_t*)ptr; case EcsOpF32: return (double)*(ecs_f32_t*)ptr; case EcsOpF64: return *(ecs_f64_t*)ptr; case EcsOpString: return atof(*(const char**)ptr); case EcsOpEnum: return *(ecs_i32_t*)ptr; case EcsOpBitmask: return *(ecs_u32_t*)ptr; case EcsOpEntity: ecs_throw(ECS_INVALID_PARAMETER, "invalid conversion from entity to float"); break; default: ecs_throw(ECS_INVALID_PARAMETER, "invalid element for float"); } error: return 0; } const char* ecs_meta_get_string( const ecs_meta_cursor_t *cursor) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); switch(op->kind) { case EcsOpString: return *(const char**)ptr; default: ecs_throw(ECS_INVALID_PARAMETER, "invalid element for string"); } error: return 0; } ecs_entity_t ecs_meta_get_entity( const ecs_meta_cursor_t *cursor) { ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); switch(op->kind) { case EcsOpEntity: return *(ecs_entity_t*)ptr; default: ecs_throw(ECS_INVALID_PARAMETER, "invalid element for entity"); } error: return 0; } #endif /** * @file expr/serialize.c * @brief Serialize (component) values to flecs string format. */ #ifdef FLECS_EXPR static int flecs_expr_ser_type( const ecs_world_t *world, const ecs_vec_t *ser, const void *base, ecs_strbuf_t *str); static int flecs_expr_ser_type_ops( const ecs_world_t *world, ecs_meta_type_op_t *ops, int32_t op_count, const void *base, ecs_strbuf_t *str, int32_t in_array); static int flecs_expr_ser_type_op( const ecs_world_t *world, ecs_meta_type_op_t *op, const void *base, ecs_strbuf_t *str); static ecs_primitive_kind_t flecs_expr_op_to_primitive_kind(ecs_meta_type_op_kind_t kind) { return kind - EcsOpPrimitive; } /* Serialize a primitive value */ static int flecs_expr_ser_primitive( const ecs_world_t *world, ecs_primitive_kind_t kind, const void *base, ecs_strbuf_t *str) { switch(kind) { case EcsBool: if (*(bool*)base) { ecs_strbuf_appendlit(str, "true"); } else { ecs_strbuf_appendlit(str, "false"); } break; case EcsChar: { char chbuf[3]; char ch = *(char*)base; if (ch) { ecs_chresc(chbuf, *(char*)base, '"'); ecs_strbuf_appendch(str, '"'); ecs_strbuf_appendstr(str, chbuf); ecs_strbuf_appendch(str, '"'); } else { ecs_strbuf_appendch(str, '0'); } break; } case EcsByte: ecs_strbuf_appendint(str, flecs_uto(int64_t, *(uint8_t*)base)); break; case EcsU8: ecs_strbuf_appendint(str, flecs_uto(int64_t, *(uint8_t*)base)); break; case EcsU16: ecs_strbuf_appendint(str, flecs_uto(int64_t, *(uint16_t*)base)); break; case EcsU32: ecs_strbuf_appendint(str, flecs_uto(int64_t, *(uint32_t*)base)); break; case EcsU64: ecs_strbuf_append(str, "%llu", *(uint64_t*)base); break; case EcsI8: ecs_strbuf_appendint(str, flecs_ito(int64_t, *(int8_t*)base)); break; case EcsI16: ecs_strbuf_appendint(str, flecs_ito(int64_t, *(int16_t*)base)); break; case EcsI32: ecs_strbuf_appendint(str, flecs_ito(int64_t, *(int32_t*)base)); break; case EcsI64: ecs_strbuf_appendint(str, *(int64_t*)base); break; case EcsF32: ecs_strbuf_appendflt(str, (double)*(float*)base, 0); break; case EcsF64: ecs_strbuf_appendflt(str, *(double*)base, 0); break; case EcsIPtr: ecs_strbuf_appendint(str, flecs_ito(int64_t, *(intptr_t*)base)); break; case EcsUPtr: ecs_strbuf_append(str, "%u", *(uintptr_t*)base); break; case EcsString: { char *value = *(char**)base; if (value) { ecs_size_t length = ecs_stresc(NULL, 0, '"', value); if (length == ecs_os_strlen(value)) { ecs_strbuf_appendch(str, '"'); ecs_strbuf_appendstrn(str, value, length); ecs_strbuf_appendch(str, '"'); } else { char *out = ecs_os_malloc(length + 3); ecs_stresc(out + 1, length, '"', value); out[0] = '"'; out[length + 1] = '"'; out[length + 2] = '\0'; ecs_strbuf_appendstr_zerocpy(str, out); } } else { ecs_strbuf_appendlit(str, "null"); } break; } case EcsEntity: { ecs_entity_t e = *(ecs_entity_t*)base; if (!e) { ecs_strbuf_appendch(str, '0'); } else { ecs_get_path_w_sep_buf(world, 0, e, ".", NULL, str); } break; } default: ecs_err("invalid primitive kind"); return -1; } return 0; } /* Serialize enumeration */ static int flecs_expr_ser_enum( const ecs_world_t *world, ecs_meta_type_op_t *op, const void *base, ecs_strbuf_t *str) { const EcsEnum *enum_type = ecs_get(world, op->type, EcsEnum); ecs_check(enum_type != NULL, ECS_INVALID_PARAMETER, NULL); int32_t val = *(int32_t*)base; /* Enumeration constants are stored in a map that is keyed on the * enumeration value. */ ecs_enum_constant_t *c = ecs_map_get_deref(&enum_type->constants, ecs_enum_constant_t, (ecs_map_key_t)val); if (!c) { char *path = ecs_get_fullpath(world, op->type); ecs_err("value %d is not valid for enum type '%s'", val, path); ecs_os_free(path); goto error; } ecs_strbuf_appendstr(str, ecs_get_name(world, c->constant)); return 0; error: return -1; } /* Serialize bitmask */ static int flecs_expr_ser_bitmask( const ecs_world_t *world, ecs_meta_type_op_t *op, const void *ptr, ecs_strbuf_t *str) { const EcsBitmask *bitmask_type = ecs_get(world, op->type, EcsBitmask); ecs_check(bitmask_type != NULL, ECS_INVALID_PARAMETER, NULL); uint32_t value = *(uint32_t*)ptr; ecs_strbuf_list_push(str, "", "|"); /* Multiple flags can be set at a given time. Iterate through all the flags * and append the ones that are set. */ ecs_map_iter_t it = ecs_map_iter(&bitmask_type->constants); int count = 0; while (ecs_map_next(&it)) { ecs_bitmask_constant_t *c = ecs_map_ptr(&it); ecs_map_key_t key = ecs_map_key(&it); if ((value & key) == key) { ecs_strbuf_list_appendstr(str, ecs_get_name(world, c->constant)); count ++; value -= (uint32_t)key; } } if (value != 0) { /* All bits must have been matched by a constant */ char *path = ecs_get_fullpath(world, op->type); ecs_err( "value for bitmask %s contains bits (%u) that cannot be mapped to constant", path, value); ecs_os_free(path); goto error; } if (!count) { ecs_strbuf_list_appendstr(str, "0"); } ecs_strbuf_list_pop(str, ""); return 0; error: return -1; } /* Serialize elements of a contiguous array */ static int expr_ser_elements( const ecs_world_t *world, ecs_meta_type_op_t *ops, int32_t op_count, const void *base, int32_t elem_count, int32_t elem_size, ecs_strbuf_t *str, bool is_array) { ecs_strbuf_list_push(str, "[", ", "); const void *ptr = base; int i; for (i = 0; i < elem_count; i ++) { ecs_strbuf_list_next(str); if (flecs_expr_ser_type_ops(world, ops, op_count, ptr, str, is_array)) { return -1; } ptr = ECS_OFFSET(ptr, elem_size); } ecs_strbuf_list_pop(str, "]"); return 0; } static int expr_ser_type_elements( const ecs_world_t *world, ecs_entity_t type, const void *base, int32_t elem_count, ecs_strbuf_t *str, bool is_array) { const EcsMetaTypeSerialized *ser = ecs_get( world, type, EcsMetaTypeSerialized); ecs_assert(ser != NULL, ECS_INTERNAL_ERROR, NULL); const EcsComponent *comp = ecs_get(world, type, EcsComponent); ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL); ecs_meta_type_op_t *ops = ecs_vec_first_t(&ser->ops, ecs_meta_type_op_t); int32_t op_count = ecs_vec_count(&ser->ops); return expr_ser_elements( world, ops, op_count, base, elem_count, comp->size, str, is_array); } /* Serialize array */ static int expr_ser_array( const ecs_world_t *world, ecs_meta_type_op_t *op, const void *ptr, ecs_strbuf_t *str) { const EcsArray *a = ecs_get(world, op->type, EcsArray); ecs_assert(a != NULL, ECS_INTERNAL_ERROR, NULL); return expr_ser_type_elements( world, a->type, ptr, a->count, str, true); } /* Serialize vector */ static int expr_ser_vector( const ecs_world_t *world, ecs_meta_type_op_t *op, const void *base, ecs_strbuf_t *str) { const ecs_vec_t *value = base; const EcsVector *v = ecs_get(world, op->type, EcsVector); ecs_assert(v != NULL, ECS_INTERNAL_ERROR, NULL); int32_t count = ecs_vec_count(value); void *array = ecs_vec_first(value); /* Serialize contiguous buffer of vector */ return expr_ser_type_elements(world, v->type, array, count, str, false); } /* Forward serialization to the different type kinds */ static int flecs_expr_ser_type_op( const ecs_world_t *world, ecs_meta_type_op_t *op, const void *ptr, ecs_strbuf_t *str) { switch(op->kind) { case EcsOpPush: case EcsOpPop: /* Should not be parsed as single op */ ecs_throw(ECS_INVALID_PARAMETER, NULL); break; case EcsOpEnum: if (flecs_expr_ser_enum(world, op, ECS_OFFSET(ptr, op->offset), str)) { goto error; } break; case EcsOpBitmask: if (flecs_expr_ser_bitmask(world, op, ECS_OFFSET(ptr, op->offset), str)) { goto error; } break; case EcsOpArray: if (expr_ser_array(world, op, ECS_OFFSET(ptr, op->offset), str)) { goto error; } break; case EcsOpVector: if (expr_ser_vector(world, op, ECS_OFFSET(ptr, op->offset), str)) { goto error; } break; default: if (flecs_expr_ser_primitive(world, flecs_expr_op_to_primitive_kind(op->kind), ECS_OFFSET(ptr, op->offset), str)) { /* Unknown operation */ ecs_err("unknown serializer operation kind (%d)", op->kind); goto error; } break; } return 0; error: return -1; } /* Iterate over a slice of the type ops array */ static int flecs_expr_ser_type_ops( const ecs_world_t *world, ecs_meta_type_op_t *ops, int32_t op_count, const void *base, ecs_strbuf_t *str, int32_t in_array) { for (int i = 0; i < op_count; i ++) { ecs_meta_type_op_t *op = &ops[i]; if (in_array <= 0) { if (op->name) { ecs_strbuf_list_next(str); ecs_strbuf_append(str, "%s: ", op->name); } int32_t elem_count = op->count; if (elem_count > 1) { /* Serialize inline array */ if (expr_ser_elements(world, op, op->op_count, base, elem_count, op->size, str, true)) { return -1; } i += op->op_count - 1; continue; } } switch(op->kind) { case EcsOpPush: ecs_strbuf_list_push(str, "{", ", "); in_array --; break; case EcsOpPop: ecs_strbuf_list_pop(str, "}"); in_array ++; break; default: if (flecs_expr_ser_type_op(world, op, base, str)) { goto error; } break; } } return 0; error: return -1; } /* Iterate over the type ops of a type */ static int flecs_expr_ser_type( const ecs_world_t *world, const ecs_vec_t *v_ops, const void *base, ecs_strbuf_t *str) { ecs_meta_type_op_t *ops = ecs_vec_first_t(v_ops, ecs_meta_type_op_t); int32_t count = ecs_vec_count(v_ops); return flecs_expr_ser_type_ops(world, ops, count, base, str, 0); } int ecs_ptr_to_expr_buf( const ecs_world_t *world, ecs_entity_t type, const void *ptr, ecs_strbuf_t *buf_out) { const EcsMetaTypeSerialized *ser = ecs_get( world, type, EcsMetaTypeSerialized); if (ser == NULL) { char *path = ecs_get_fullpath(world, type); ecs_err("cannot serialize value for type '%s'", path); ecs_os_free(path); goto error; } if (flecs_expr_ser_type(world, &ser->ops, ptr, buf_out)) { goto error; } return 0; error: return -1; } char* ecs_ptr_to_expr( const ecs_world_t *world, ecs_entity_t type, const void* ptr) { ecs_strbuf_t str = ECS_STRBUF_INIT; if (ecs_ptr_to_expr_buf(world, type, ptr, &str) != 0) { ecs_strbuf_reset(&str); return NULL; } return ecs_strbuf_get(&str); } int ecs_primitive_to_expr_buf( const ecs_world_t *world, ecs_primitive_kind_t kind, const void *base, ecs_strbuf_t *str) { return flecs_expr_ser_primitive(world, kind, base, str); } #endif /** * @file expr/vars.c * @brief Utilities for variable substitution in flecs string expressions. */ #ifdef FLECS_EXPR static void flecs_expr_var_scope_init( ecs_world_t *world, ecs_expr_var_scope_t *scope, ecs_expr_var_scope_t *parent) { flecs_name_index_init(&scope->var_index, &world->allocator); ecs_vec_init_t(&world->allocator, &scope->vars, ecs_expr_var_t, 0); scope->parent = parent; } static void flecs_expr_var_scope_fini( ecs_world_t *world, ecs_expr_var_scope_t *scope) { ecs_vec_t *vars = &scope->vars; int32_t i, count = vars->count; for (i = 0; i < count; i++) { ecs_expr_var_t *var = ecs_vec_get_t(vars, ecs_expr_var_t, i); if (var->owned) { ecs_value_free(world, var->value.type, var->value.ptr); } flecs_strfree(&world->allocator, var->name); } ecs_vec_fini_t(&world->allocator, &scope->vars, ecs_expr_var_t); flecs_name_index_fini(&scope->var_index); } void ecs_vars_init( ecs_world_t *world, ecs_vars_t *vars) { flecs_expr_var_scope_init(world, &vars->root, NULL); vars->world = world; vars->cur = &vars->root; } void ecs_vars_fini( ecs_vars_t *vars) { ecs_expr_var_scope_t *cur = vars->cur, *next; do { next = cur->parent; flecs_expr_var_scope_fini(vars->world, cur); if (cur != &vars->root) { flecs_free_t(&vars->world->allocator, ecs_expr_var_scope_t, cur); } else { break; } } while ((cur = next)); } void ecs_vars_push( ecs_vars_t *vars) { ecs_expr_var_scope_t *scope = flecs_calloc_t(&vars->world->allocator, ecs_expr_var_scope_t); flecs_expr_var_scope_init(vars->world, scope, vars->cur); vars->cur = scope; } int ecs_vars_pop( ecs_vars_t *vars) { ecs_expr_var_scope_t *scope = vars->cur; ecs_check(scope != &vars->root, ECS_INVALID_OPERATION, NULL); vars->cur = scope->parent; flecs_expr_var_scope_fini(vars->world, scope); flecs_free_t(&vars->world->allocator, ecs_expr_var_scope_t, scope); return 0; error: return 1; } ecs_expr_var_t* ecs_vars_declare( ecs_vars_t *vars, const char *name, ecs_entity_t type) { ecs_assert(vars != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(name != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(type != 0, ECS_INVALID_PARAMETER, NULL); ecs_expr_var_scope_t *scope = vars->cur; ecs_hashmap_t *var_index = &scope->var_index; if (flecs_name_index_find(var_index, name, 0, 0) != 0) { ecs_err("variable %s redeclared", name); goto error; } ecs_expr_var_t *var = ecs_vec_append_t(&vars->world->allocator, &scope->vars, ecs_expr_var_t); var->value.ptr = ecs_value_new(vars->world, type); if (!var->value.ptr) { goto error; } var->value.type = type; var->name = flecs_strdup(&vars->world->allocator, name); var->owned = true; flecs_name_index_ensure(var_index, flecs_ito(uint64_t, ecs_vec_count(&scope->vars)), var->name, 0, 0); return var; error: return NULL; } ecs_expr_var_t* ecs_vars_declare_w_value( ecs_vars_t *vars, const char *name, ecs_value_t *value) { ecs_assert(vars != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(name != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(value != NULL, ECS_INVALID_PARAMETER, NULL); ecs_expr_var_scope_t *scope = vars->cur; ecs_hashmap_t *var_index = &scope->var_index; if (flecs_name_index_find(var_index, name, 0, 0) != 0) { ecs_err("variable %s redeclared", name); ecs_value_free(vars->world, value->type, value->ptr); goto error; } ecs_expr_var_t *var = ecs_vec_append_t(&vars->world->allocator, &scope->vars, ecs_expr_var_t); var->value = *value; var->name = flecs_strdup(&vars->world->allocator, name); var->owned = true; value->ptr = NULL; /* Take ownership, prevent double free */ flecs_name_index_ensure(var_index, flecs_ito(uint64_t, ecs_vec_count(&scope->vars)), var->name, 0, 0); return var; error: return NULL; } static ecs_expr_var_t* flecs_vars_scope_lookup( ecs_expr_var_scope_t *scope, const char *name) { uint64_t var_id = flecs_name_index_find(&scope->var_index, name, 0, 0); if (var_id == 0) { if (scope->parent) { return flecs_vars_scope_lookup(scope->parent, name); } return NULL; } return ecs_vec_get_t(&scope->vars, ecs_expr_var_t, flecs_uto(int32_t, var_id - 1)); } ecs_expr_var_t* ecs_vars_lookup( ecs_vars_t *vars, const char *name) { return flecs_vars_scope_lookup(vars->cur, name); } #endif /** * @file expr/strutil.c * @brief String parsing utilities. */ #ifdef FLECS_EXPR char* ecs_chresc( char *out, char in, char delimiter) { char *bptr = out; switch(in) { case '\a': *bptr++ = '\\'; *bptr = 'a'; break; case '\b': *bptr++ = '\\'; *bptr = 'b'; break; case '\f': *bptr++ = '\\'; *bptr = 'f'; break; case '\n': *bptr++ = '\\'; *bptr = 'n'; break; case '\r': *bptr++ = '\\'; *bptr = 'r'; break; case '\t': *bptr++ = '\\'; *bptr = 't'; break; case '\v': *bptr++ = '\\'; *bptr = 'v'; break; case '\\': *bptr++ = '\\'; *bptr = '\\'; break; default: if (in == delimiter) { *bptr++ = '\\'; *bptr = delimiter; } else { *bptr = in; } break; } *(++bptr) = '\0'; return bptr; } const char* ecs_chrparse( const char *in, char *out) { const char *result = in + 1; char ch; if (in[0] == '\\') { result ++; switch(in[1]) { case 'a': ch = '\a'; break; case 'b': ch = '\b'; break; case 'f': ch = '\f'; break; case 'n': ch = '\n'; break; case 'r': ch = '\r'; break; case 't': ch = '\t'; break; case 'v': ch = '\v'; break; case '\\': ch = '\\'; break; case '"': ch = '"'; break; case '0': ch = '\0'; break; case ' ': ch = ' '; break; case '$': ch = '$'; break; default: goto error; } } else { ch = in[0]; } if (out) { *out = ch; } return result; error: return NULL; } ecs_size_t ecs_stresc( char *out, ecs_size_t n, char delimiter, const char *in) { const char *ptr = in; char ch, *bptr = out, buff[3]; ecs_size_t written = 0; while ((ch = *ptr++)) { if ((written += (ecs_size_t)(ecs_chresc( buff, ch, delimiter) - buff)) <= n) { /* If size != 0, an out buffer must be provided. */ ecs_check(out != NULL, ECS_INVALID_PARAMETER, NULL); *bptr++ = buff[0]; if ((ch = buff[1])) { *bptr = ch; bptr++; } } } if (bptr) { while (written < n) { *bptr = '\0'; bptr++; written++; } } return written; error: return 0; } char* ecs_astresc( char delimiter, const char *in) { if (!in) { return NULL; } ecs_size_t len = ecs_stresc(NULL, 0, delimiter, in); char *out = ecs_os_malloc_n(char, len + 1); ecs_stresc(out, len, delimiter, in); out[len] = '\0'; return out; } #endif /** * @file expr/deserialize.c * @brief Deserialize flecs string format into (component) values. */ #include #ifdef FLECS_EXPR /* String deserializer for values & simple expressions */ /* Order in enumeration is important, as it is used for precedence */ typedef enum ecs_expr_oper_t { EcsExprOperUnknown, EcsLeftParen, EcsCondAnd, EcsCondOr, EcsCondEq, EcsCondNeq, EcsCondGt, EcsCondGtEq, EcsCondLt, EcsCondLtEq, EcsShiftLeft, EcsShiftRight, EcsAdd, EcsSub, EcsMul, EcsDiv, EcsMin } ecs_expr_oper_t; /* Used to track temporary values */ #define EXPR_MAX_STACK_SIZE (256) typedef struct ecs_expr_value_t { const ecs_type_info_t *ti; void *ptr; } ecs_expr_value_t; typedef struct ecs_value_stack_t { ecs_expr_value_t values[EXPR_MAX_STACK_SIZE]; ecs_stack_cursor_t cursor; ecs_stack_t *stack; ecs_stage_t *stage; int32_t count; } ecs_value_stack_t; static const char* flecs_parse_expr( ecs_world_t *world, ecs_value_stack_t *stack, const char *ptr, ecs_value_t *value, ecs_expr_oper_t op, const ecs_parse_expr_desc_t *desc); static void* flecs_expr_value_new( ecs_value_stack_t *stack, ecs_entity_t type) { ecs_stage_t *stage = stack->stage; ecs_world_t *world = stage->world; ecs_id_record_t *idr = flecs_id_record_get(world, type); if (!idr) { return NULL; } const ecs_type_info_t *ti = idr->type_info; if (!ti) { return NULL; } ecs_assert(ti->size != 0, ECS_INTERNAL_ERROR, NULL); void *result = flecs_stack_alloc(stack->stack, ti->size, ti->alignment); if (ti->hooks.ctor) { ti->hooks.ctor(result, 1, ti); } else { ecs_os_memset(result, 0, ti->size); } if (ti->hooks.dtor) { /* Track values that have destructors */ stack->values[stack->count].ti = ti; stack->values[stack->count].ptr = result; stack->count ++; } return result; } static const char* flecs_str_to_expr_oper( const char *str, ecs_expr_oper_t *op) { if (!ecs_os_strncmp(str, "+", 1)) { *op = EcsAdd; return str + 1; } else if (!ecs_os_strncmp(str, "-", 1)) { *op = EcsSub; return str + 1; } else if (!ecs_os_strncmp(str, "*", 1)) { *op = EcsMul; return str + 1; } else if (!ecs_os_strncmp(str, "/", 1)) { *op = EcsDiv; return str + 1; } else if (!ecs_os_strncmp(str, "&&", 2)) { *op = EcsCondAnd; return str + 2; } else if (!ecs_os_strncmp(str, "||", 2)) { *op = EcsCondOr; return str + 2; } else if (!ecs_os_strncmp(str, "==", 2)) { *op = EcsCondEq; return str + 2; } else if (!ecs_os_strncmp(str, "!=", 2)) { *op = EcsCondNeq; return str + 2; } else if (!ecs_os_strncmp(str, ">=", 2)) { *op = EcsCondGtEq; return str + 2; } else if (!ecs_os_strncmp(str, "<=", 2)) { *op = EcsCondLtEq; return str + 2; } else if (!ecs_os_strncmp(str, ">>", 2)) { *op = EcsShiftRight; return str + 2; } else if (!ecs_os_strncmp(str, "<<", 2)) { *op = EcsShiftLeft; return str + 2; } else if (!ecs_os_strncmp(str, ">", 1)) { *op = EcsCondGt; return str + 1; } else if (!ecs_os_strncmp(str, "<", 1)) { *op = EcsCondLt; return str + 1; } *op = EcsExprOperUnknown; return NULL; } const char *ecs_parse_expr_token( const char *name, const char *expr, const char *ptr, char *token) { const char *start = ptr; char *token_ptr = token; if (ptr[0] == '/') { char ch; if (ptr[1] == '/') { // Single line comment for (ptr = &ptr[2]; (ch = ptr[0]) && (ch != '\n'); ptr ++) {} return ptr; } else if (ptr[1] == '*') { // Multi line comment for (ptr = &ptr[2]; (ch = ptr[0]); ptr ++) { if (ch == '*' && ptr[1] == '/') { return ptr + 2; } } ecs_parser_error(name, expr, ptr - expr, "missing */ for multiline comment"); return NULL; } } ecs_expr_oper_t op; if (ptr[0] == '(') { token[0] = '('; token[1] = 0; return ptr + 1; } else if (ptr[0] != '-') { const char *tptr = flecs_str_to_expr_oper(ptr, &op); if (tptr) { ecs_os_strncpy(token, ptr, tptr - ptr); return tptr; } } while ((ptr = ecs_parse_token(name, expr, ptr, token_ptr, 0))) { if (ptr[0] == '|' && ptr[1] != '|') { token_ptr = &token_ptr[ptr - start]; token_ptr[0] = '|'; token_ptr[1] = '\0'; token_ptr ++; ptr ++; start = ptr; } else { break; } } return ptr; } static const char* flecs_parse_multiline_string( ecs_meta_cursor_t *cur, const char *name, const char *expr, const char *ptr) { /* Multiline string */ ecs_strbuf_t str = ECS_STRBUF_INIT; char ch; while ((ch = ptr[0]) && (ch != '`')) { if (ch == '\\' && ptr[1] == '`') { ch = '`'; ptr ++; } ecs_strbuf_appendch(&str, ch); ptr ++; } if (ch != '`') { ecs_parser_error(name, expr, ptr - expr, "missing '`' to close multiline string"); goto error; } char *strval = ecs_strbuf_get(&str); if (ecs_meta_set_string(cur, strval) != 0) { goto error; } ecs_os_free(strval); return ptr + 1; error: return NULL; } static bool flecs_parse_is_float( const char *ptr) { ecs_assert(isdigit(ptr[0]), ECS_INTERNAL_ERROR, NULL); char ch; while ((ch = (++ptr)[0])) { if (ch == '.' || ch == 'e') { return true; } if (!isdigit(ch)) { return false; } } return false; } /* Determine the type of an expression from the first character(s). This allows * us to initialize a storage for a type if none was provided. */ static ecs_entity_t flecs_parse_discover_type( const char *name, const char *expr, const char *ptr, ecs_entity_t input_type, const ecs_parse_expr_desc_t *desc) { /* String literal */ if (ptr[0] == '"' || ptr[0] == '`') { if (input_type == ecs_id(ecs_char_t)) { return input_type; } return ecs_id(ecs_string_t); } /* Negative number literal */ if (ptr[0] == '-') { if (!isdigit(ptr[1])) { ecs_parser_error(name, expr, ptr - expr, "invalid literal"); return 0; } if (flecs_parse_is_float(ptr + 1)) { return ecs_id(ecs_f64_t); } else { return ecs_id(ecs_i64_t); } } /* Positive number literal */ if (isdigit(ptr[0])) { if (flecs_parse_is_float(ptr)) { return ecs_id(ecs_f64_t); } else { return ecs_id(ecs_u64_t); } } /* Variable */ if (ptr[0] == '$') { if (!desc || !desc->vars) { ecs_parser_error(name, expr, ptr - expr, "unresolved variable (no variable scope)"); return 0; } char token[ECS_MAX_TOKEN_SIZE]; if (ecs_parse_expr_token(name, expr, &ptr[1], token) == NULL) { return 0; } const ecs_expr_var_t *var = ecs_vars_lookup(desc->vars, token); if (!var) { ecs_parser_error(name, expr, ptr - expr, "unresolved variable '%s'", token); return 0; } return var->value.type; } /* Boolean */ if (ptr[0] == 't' && !ecs_os_strncmp(ptr, "true", 4)) { if (!isalpha(ptr[4]) && ptr[4] != '_') { return ecs_id(ecs_bool_t); } } if (ptr[0] == 'f' && !ecs_os_strncmp(ptr, "false", 5)) { if (!isalpha(ptr[5]) && ptr[5] != '_') { return ecs_id(ecs_bool_t); } } /* Entity identifier */ if (isalpha(ptr[0])) { if (!input_type) { /* Identifier could also be enum/bitmask constant */ return ecs_id(ecs_entity_t); } } /* If no default type was provided we can't automatically deduce the type of * composite/collection expressions. */ if (!input_type) { if (ptr[0] == '{') { ecs_parser_error(name, expr, ptr - expr, "unknown type for composite literal"); return 0; } if (ptr[0] == '[') { ecs_parser_error(name, expr, ptr - expr, "unknown type for collection literal"); return 0; } ecs_parser_error(name, expr, ptr - expr, "invalid expression"); } return input_type; } /* Normalize types to their largest representation. * Rather than taking the original type of a value, use the largest * representation of the type so we don't have to worry about overflowing the * original type in the operation. */ static ecs_entity_t flecs_largest_type( const EcsPrimitive *type) { switch(type->kind) { case EcsBool: return ecs_id(ecs_bool_t); case EcsChar: return ecs_id(ecs_char_t); case EcsByte: return ecs_id(ecs_u8_t); case EcsU8: return ecs_id(ecs_u64_t); case EcsU16: return ecs_id(ecs_u64_t); case EcsU32: return ecs_id(ecs_u64_t); case EcsU64: return ecs_id(ecs_u64_t); case EcsI8: return ecs_id(ecs_i64_t); case EcsI16: return ecs_id(ecs_i64_t); case EcsI32: return ecs_id(ecs_i64_t); case EcsI64: return ecs_id(ecs_i64_t); case EcsF32: return ecs_id(ecs_f64_t); case EcsF64: return ecs_id(ecs_f64_t); case EcsUPtr: return ecs_id(ecs_u64_t); case EcsIPtr: return ecs_id(ecs_i64_t); case EcsString: return ecs_id(ecs_string_t); case EcsEntity: return ecs_id(ecs_entity_t); default: ecs_abort(ECS_INTERNAL_ERROR, NULL); } } /** Test if a normalized type can promote to another type in an expression */ static bool flecs_is_type_number( ecs_entity_t type) { if (type == ecs_id(ecs_bool_t)) return false; else if (type == ecs_id(ecs_char_t)) return false; else if (type == ecs_id(ecs_u8_t)) return false; else if (type == ecs_id(ecs_u64_t)) return true; else if (type == ecs_id(ecs_i64_t)) return true; else if (type == ecs_id(ecs_f64_t)) return true; else if (type == ecs_id(ecs_string_t)) return false; else if (type == ecs_id(ecs_entity_t)) return false; else return false; } static bool flecs_oper_valid_for_type( ecs_entity_t type, ecs_expr_oper_t op) { switch(op) { case EcsAdd: case EcsSub: case EcsMul: case EcsDiv: return flecs_is_type_number(type); case EcsCondEq: case EcsCondNeq: case EcsCondAnd: case EcsCondOr: case EcsCondGt: case EcsCondGtEq: case EcsCondLt: case EcsCondLtEq: return flecs_is_type_number(type) || (type == ecs_id(ecs_bool_t)) || (type == ecs_id(ecs_char_t)) || (type == ecs_id(ecs_entity_t)); case EcsShiftLeft: case EcsShiftRight: return (type == ecs_id(ecs_u64_t)); default: return false; } } /** Promote type to most expressive (f64 > i64 > u64) */ static ecs_entity_t flecs_promote_type( ecs_entity_t type, ecs_entity_t promote_to) { if (type == ecs_id(ecs_u64_t)) { return promote_to; } if (promote_to == ecs_id(ecs_u64_t)) { return type; } if (type == ecs_id(ecs_f64_t)) { return type; } if (promote_to == ecs_id(ecs_f64_t)) { return promote_to; } return ecs_id(ecs_i64_t); } static int flecs_oper_precedence( ecs_expr_oper_t left, ecs_expr_oper_t right) { return (left > right) - (left < right); } static void flecs_value_cast( ecs_world_t *world, ecs_value_stack_t *stack, ecs_value_t *value, ecs_entity_t type) { if (value->type == type) { return; } ecs_value_t result; result.type = type; result.ptr = flecs_expr_value_new(stack, type); if (value->ptr) { ecs_meta_cursor_t cur = ecs_meta_cursor(world, type, result.ptr); ecs_meta_set_value(&cur, value); } *value = result; } static bool flecs_expr_op_is_equality( ecs_expr_oper_t op) { switch(op) { case EcsCondEq: case EcsCondNeq: case EcsCondGt: case EcsCondGtEq: case EcsCondLt: case EcsCondLtEq: return true; default: return false; } } static ecs_entity_t flecs_binary_expr_type( ecs_world_t *world, const char *name, const char *expr, const char *ptr, ecs_value_t *lvalue, ecs_value_t *rvalue, ecs_expr_oper_t op, ecs_entity_t *operand_type_out) { ecs_entity_t result_type = 0, operand_type = 0; switch(op) { case EcsDiv: /* Result type of a division is always a float */ *operand_type_out = ecs_id(ecs_f64_t); return ecs_id(ecs_f64_t); case EcsCondAnd: case EcsCondOr: /* Result type of a condition operator is always a bool */ *operand_type_out = ecs_id(ecs_bool_t); return ecs_id(ecs_bool_t); case EcsCondEq: case EcsCondNeq: case EcsCondGt: case EcsCondGtEq: case EcsCondLt: case EcsCondLtEq: /* Result type of equality operator is always bool, but operand types * should not be casted to bool */ result_type = ecs_id(ecs_bool_t); break; default: break; } /* Result type for arithmetic operators is determined by operands */ const EcsPrimitive *ltype_ptr = ecs_get(world, lvalue->type, EcsPrimitive); const EcsPrimitive *rtype_ptr = ecs_get(world, rvalue->type, EcsPrimitive); if (!ltype_ptr || !rtype_ptr) { char *lname = ecs_get_fullpath(world, lvalue->type); char *rname = ecs_get_fullpath(world, rvalue->type); ecs_parser_error(name, expr, ptr - expr, "invalid non-primitive type in binary expression (%s, %s)", lname, rname); ecs_os_free(lname); ecs_os_free(rname); return 0; } ecs_entity_t ltype = flecs_largest_type(ltype_ptr); ecs_entity_t rtype = flecs_largest_type(rtype_ptr); if (ltype == rtype) { operand_type = ltype; goto done; } if (flecs_expr_op_is_equality(op)) { ecs_parser_error(name, expr, ptr - expr, "mismatching types in equality expression"); return 0; } if (!flecs_is_type_number(ltype) || !flecs_is_type_number(rtype)) { ecs_parser_error(name, expr, ptr - expr, "incompatible types in binary expression"); return 0; } operand_type = flecs_promote_type(ltype, rtype); done: if (op == EcsSub && operand_type == ecs_id(ecs_u64_t)) { /* Result of subtracting two unsigned ints can be negative */ operand_type = ecs_id(ecs_i64_t); } if (!result_type) { result_type = operand_type; } *operand_type_out = operand_type; return result_type; } /* Macro's to let the compiler do the operations & conversion work for us */ #define ECS_VALUE_GET(value, T) (*(T*)value->ptr) #define ECS_BINARY_OP_T(left, right, result, op, R, T)\ ECS_VALUE_GET(result, R) = ECS_VALUE_GET(left, T) op ECS_VALUE_GET(right, T) #define ECS_BINARY_OP(left, right, result, op)\ if (left->type == ecs_id(ecs_u64_t)) { \ ECS_BINARY_OP_T(left, right, result, op, ecs_u64_t, ecs_u64_t);\ } else if (left->type == ecs_id(ecs_i64_t)) { \ ECS_BINARY_OP_T(left, right, result, op, ecs_i64_t, ecs_i64_t);\ } else if (left->type == ecs_id(ecs_f64_t)) { \ ECS_BINARY_OP_T(left, right, result, op, ecs_f64_t, ecs_f64_t);\ } else {\ ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ } #define ECS_BINARY_COND_OP(left, right, result, op)\ if (left->type == ecs_id(ecs_u64_t)) { \ ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u64_t);\ } else if (left->type == ecs_id(ecs_i64_t)) { \ ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_i64_t);\ } else if (left->type == ecs_id(ecs_f64_t)) { \ ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_f64_t);\ } else if (left->type == ecs_id(ecs_u8_t)) { \ ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_u8_t);\ } else if (left->type == ecs_id(ecs_char_t)) { \ ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_char_t);\ } else if (left->type == ecs_id(ecs_bool_t)) { \ ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_bool_t);\ } else {\ ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ } #define ECS_BINARY_BOOL_OP(left, right, result, op)\ if (left->type == ecs_id(ecs_bool_t)) { \ ECS_BINARY_OP_T(left, right, result, op, ecs_bool_t, ecs_bool_t);\ } else {\ ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ } #define ECS_BINARY_UINT_OP(left, right, result, op)\ if (left->type == ecs_id(ecs_u64_t)) { \ ECS_BINARY_OP_T(left, right, result, op, ecs_u64_t, ecs_u64_t);\ } else {\ ecs_abort(ECS_INTERNAL_ERROR, "unexpected type in binary expression");\ } static int flecs_binary_expr_do( ecs_world_t *world, ecs_value_stack_t *stack, const char *name, const char *expr, const char *ptr, ecs_value_t *lvalue, ecs_value_t *rvalue, ecs_value_t *result, ecs_expr_oper_t op) { /* Find expression type */ ecs_entity_t operand_type, type = flecs_binary_expr_type( world, name, expr, ptr, lvalue, rvalue, op, &operand_type); if (!type) { return -1; } if (!flecs_oper_valid_for_type(type, op)) { ecs_parser_error(name, expr, ptr - expr, "invalid operator for type"); return -1; } flecs_value_cast(world, stack, lvalue, operand_type); flecs_value_cast(world, stack, rvalue, operand_type); ecs_value_t *storage = result; ecs_value_t tmp_storage = {0}; if (result->type != type) { storage = &tmp_storage; storage->type = type; storage->ptr = flecs_expr_value_new(stack, type); } switch(op) { case EcsAdd: ECS_BINARY_OP(lvalue, rvalue, storage, +); break; case EcsSub: ECS_BINARY_OP(lvalue, rvalue, storage, -); break; case EcsMul: ECS_BINARY_OP(lvalue, rvalue, storage, *); break; case EcsDiv: ECS_BINARY_OP(lvalue, rvalue, storage, /); break; case EcsCondEq: ECS_BINARY_COND_OP(lvalue, rvalue, storage, ==); break; case EcsCondNeq: ECS_BINARY_COND_OP(lvalue, rvalue, storage, !=); break; case EcsCondGt: ECS_BINARY_COND_OP(lvalue, rvalue, storage, >); break; case EcsCondGtEq: ECS_BINARY_COND_OP(lvalue, rvalue, storage, >=); break; case EcsCondLt: ECS_BINARY_COND_OP(lvalue, rvalue, storage, <); break; case EcsCondLtEq: ECS_BINARY_COND_OP(lvalue, rvalue, storage, <=); break; case EcsCondAnd: ECS_BINARY_BOOL_OP(lvalue, rvalue, storage, &&); break; case EcsCondOr: ECS_BINARY_BOOL_OP(lvalue, rvalue, storage, ||); break; case EcsShiftLeft: ECS_BINARY_UINT_OP(lvalue, rvalue, storage, <<); break; case EcsShiftRight: ECS_BINARY_UINT_OP(lvalue, rvalue, storage, >>); break; default: ecs_parser_error(name, expr, ptr - expr, "unsupported operator"); return -1; } if (storage->ptr != result->ptr) { if (!result->ptr) { *result = *storage; } else { ecs_meta_cursor_t cur = ecs_meta_cursor(world, result->type, result->ptr); ecs_meta_set_value(&cur, storage); } } return 0; } static const char* flecs_binary_expr_parse( ecs_world_t *world, ecs_value_stack_t *stack, const char *name, const char *expr, const char *ptr, ecs_value_t *lvalue, ecs_value_t *result, ecs_expr_oper_t left_op, const ecs_parse_expr_desc_t *desc) { ecs_entity_t result_type = result->type; do { ecs_expr_oper_t op; ptr = flecs_str_to_expr_oper(ptr, &op); if (!ptr) { ecs_parser_error(name, expr, ptr - expr, "invalid operator"); return NULL; } ptr = ecs_parse_ws_eol(ptr); ecs_value_t rvalue = {0}; const char *rptr = flecs_parse_expr(world, stack, ptr, &rvalue, op, desc); if (!rptr) { return NULL; } if (flecs_binary_expr_do(world, stack, name, expr, ptr, lvalue, &rvalue, result, op)) { return NULL; } ptr = rptr; ecs_expr_oper_t right_op; flecs_str_to_expr_oper(rptr, &right_op); if (right_op > left_op) { if (result_type) { /* If result was initialized, preserve its value */ lvalue->type = result->type; lvalue->ptr = flecs_expr_value_new(stack, lvalue->type); ecs_value_copy(world, lvalue->type, lvalue->ptr, result->ptr); continue; } else { /* Otherwise move result to lvalue */ *lvalue = *result; ecs_os_zeromem(result); continue; } } break; } while (true); return ptr; } static const char* flecs_parse_expr( ecs_world_t *world, ecs_value_stack_t *stack, const char *ptr, ecs_value_t *value, ecs_expr_oper_t left_op, const ecs_parse_expr_desc_t *desc) { ecs_assert(value != NULL, ECS_INTERNAL_ERROR, NULL); char token[ECS_MAX_TOKEN_SIZE]; int depth = 0; ecs_value_t result = {0}; ecs_meta_cursor_t cur = {0}; const char *name = desc ? desc->name : NULL; const char *expr = desc ? desc->expr : NULL; token[0] = '\0'; expr = expr ? expr : ptr; ptr = ecs_parse_ws_eol(ptr); /* Check for postfix operators */ ecs_expr_oper_t unary_op = EcsExprOperUnknown; if (ptr[0] == '-' && !isdigit(ptr[1])) { unary_op = EcsMin; ptr = ecs_parse_ws_eol(ptr + 1); } /* Initialize storage and cursor. If expression starts with a '(' storage * will be initialized by a nested expression */ if (ptr[0] != '(') { ecs_entity_t type = flecs_parse_discover_type( name, expr, ptr, value->type, desc); if (!type) { return NULL; } result.type = type; if (type != value->type) { result.ptr = flecs_expr_value_new(stack, type); } else { result.ptr = value->ptr; } cur = ecs_meta_cursor(world, result.type, result.ptr); if (!cur.valid) { return NULL; } cur.lookup_action = desc ? desc->lookup_action : NULL; cur.lookup_ctx = desc ? desc->lookup_ctx : NULL; } /* Loop that parses all values in a value scope */ while ((ptr = ecs_parse_expr_token(name, expr, ptr, token))) { /* Used to track of the result of the parsed token can be used as the * lvalue for a binary expression */ bool is_lvalue = false; bool newline = false; if (!ecs_os_strcmp(token, "(")) { ecs_value_t temp_result, *out; if (!depth) { out = &result; } else { temp_result.type = ecs_meta_get_type(&cur); temp_result.ptr = ecs_meta_get_ptr(&cur); out = &temp_result; } /* Parenthesis, parse nested expression */ ptr = flecs_parse_expr(world, stack, ptr, out, EcsLeftParen, desc); if (ptr[0] != ')') { ecs_parser_error(name, expr, ptr - expr, "missing closing parenthesis"); return NULL; } ptr = ecs_parse_ws(ptr + 1); is_lvalue = true; } else if (!ecs_os_strcmp(token, "{")) { /* Parse nested value scope */ ecs_entity_t scope_type = ecs_meta_get_type(&cur); depth ++; /* Keep track of depth so we know when parsing is done */ if (ecs_meta_push(&cur) != 0) { goto error; } if (ecs_meta_is_collection(&cur)) { char *path = ecs_get_fullpath(world, scope_type); ecs_parser_error(name, expr, ptr - expr, "expected '[' for collection type '%s'", path); ecs_os_free(path); return NULL; } } else if (!ecs_os_strcmp(token, "}")) { depth --; if (ecs_meta_is_collection(&cur)) { ecs_parser_error(name, expr, ptr - expr, "expected ']'"); return NULL; } if (ecs_meta_pop(&cur) != 0) { goto error; } } else if (!ecs_os_strcmp(token, "[")) { /* Open collection value scope */ depth ++; if (ecs_meta_push(&cur) != 0) { goto error; } if (!ecs_meta_is_collection(&cur)) { ecs_parser_error(name, expr, ptr - expr, "expected '{'"); return NULL; } } else if (!ecs_os_strcmp(token, "]")) { depth --; if (!ecs_meta_is_collection(&cur)) { ecs_parser_error(name, expr, ptr - expr, "expected '}'"); return NULL; } if (ecs_meta_pop(&cur) != 0) { goto error; } } else if (!ecs_os_strcmp(token, "-")) { if (unary_op != EcsExprOperUnknown) { ecs_parser_error(name, expr, ptr - expr, "unexpected unary operator"); return NULL; } unary_op = EcsMin; } else if (!ecs_os_strcmp(token, ",")) { /* Move to next field */ if (ecs_meta_next(&cur) != 0) { goto error; } } else if (!ecs_os_strcmp(token, "null")) { if (ecs_meta_set_null(&cur) != 0) { goto error; } is_lvalue = true; } else if (token[0] == '\"') { /* Regular string */ if (ecs_meta_set_string_literal(&cur, token) != 0) { goto error; } is_lvalue = true; } else if (!ecs_os_strcmp(token, "`")) { /* Multiline string */ if (!(ptr = flecs_parse_multiline_string(&cur, name, expr, ptr))) { goto error; } is_lvalue = true; } else if (token[0] == '$') { /* Variable */ if (!desc || !desc->vars) { ecs_parser_error(name, expr, ptr - expr, "unresolved variable '%s' (no variable scope)", token); return NULL; } if (!token[1]) { /* Empty name means default to assigned member */ const char *member = ecs_meta_get_member(&cur); if (!member) { ecs_parser_error(name, expr, ptr - expr, "invalid default variable outside member assignment"); return NULL; } ecs_os_strcpy(&token[1], member); } const ecs_expr_var_t *var = ecs_vars_lookup(desc->vars, &token[1]); if (!var) { ecs_parser_error(name, expr, ptr - expr, "unresolved variable '%s'", token); return NULL; } ecs_meta_set_value(&cur, &var->value); is_lvalue = true; } else { const char *tptr = ecs_parse_ws(ptr); for (; ptr != tptr; ptr ++) { if (ptr[0] == '\n') { newline = true; } } if (ptr[0] == ':') { /* Member assignment */ ptr ++; if (ecs_meta_dotmember(&cur, token) != 0) { goto error; } } else { if (ecs_meta_set_string(&cur, token) != 0) { goto error; } } is_lvalue = true; } /* If lvalue was parsed, apply operators. Expressions cannot start * directly after a newline character. */ if (is_lvalue && !newline) { if (unary_op != EcsExprOperUnknown) { if (unary_op == EcsMin) { int64_t v = -1; ecs_value_t lvalue = {.type = ecs_id(ecs_i64_t), .ptr = &v}; ecs_value_t *out, rvalue, temp_out = {0}; if (!depth) { rvalue = result; ecs_os_zeromem(&result); out = &result; } else { ecs_entity_t cur_type = ecs_meta_get_type(&cur); void *cur_ptr = ecs_meta_get_ptr(&cur); rvalue.type = cur_type; rvalue.ptr = cur_ptr; temp_out.type = cur_type; temp_out.ptr = cur_ptr; out = &temp_out; } flecs_binary_expr_do(world, stack, name, expr, ptr, &lvalue, &rvalue, out, EcsMul); } unary_op = 0; } ecs_expr_oper_t right_op; flecs_str_to_expr_oper(ptr, &right_op); if (right_op) { /* This is a binary expression, test precedence to determine if * it should be evaluated here */ if (flecs_oper_precedence(left_op, right_op) < 0) { ecs_value_t lvalue; ecs_value_t *op_result = &result; ecs_value_t temp_storage; if (!depth) { /* Root level value, move result to lvalue storage */ lvalue = result; ecs_os_zeromem(&result); } else { /* Not a root level value. Move the parsed lvalue to a * temporary storage, and initialize the result value * for the binary operation with the current cursor */ ecs_entity_t cur_type = ecs_meta_get_type(&cur); void *cur_ptr = ecs_meta_get_ptr(&cur); lvalue.type = cur_type; lvalue.ptr = flecs_expr_value_new(stack, cur_type); ecs_value_copy(world, cur_type, lvalue.ptr, cur_ptr); temp_storage.type = cur_type; temp_storage.ptr = cur_ptr; op_result = &temp_storage; } /* Do the binary expression */ ptr = flecs_binary_expr_parse(world, stack, name, expr, ptr, &lvalue, op_result, left_op, desc); if (!ptr) { return NULL; } } } } if (!depth) { /* Reached the end of the root scope */ break; } ptr = ecs_parse_ws_eol(ptr); } if (!value->ptr) { value->type = result.type; value->ptr = flecs_expr_value_new(stack, result.type); } if (value->ptr != result.ptr) { cur = ecs_meta_cursor(world, value->type, value->ptr); ecs_meta_set_value(&cur, &result); } ecs_assert(value->type != 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(value->ptr != NULL, ECS_INTERNAL_ERROR, NULL); return ptr; error: return NULL; } const char* ecs_parse_expr( ecs_world_t *world, const char *ptr, ecs_value_t *value, const ecs_parse_expr_desc_t *desc) { /* Prepare storage for temporary values */ ecs_stage_t *stage = flecs_stage_from_world(&world); ecs_value_stack_t stack; stack.count = 0; stack.stage = stage; stack.stack = &stage->allocators.deser_stack; stack.cursor = flecs_stack_get_cursor(stack.stack); /* Parse expression */ bool storage_provided = value->ptr != NULL; ptr = flecs_parse_expr(world, &stack, ptr, value, EcsExprOperUnknown, desc); /* If no result value was provided, allocate one as we can't return a * pointer to a temporary storage */ if (!storage_provided && value->ptr) { ecs_assert(value->type != 0, ECS_INTERNAL_ERROR, NULL); void *temp_storage = value->ptr; value->ptr = ecs_value_new(world, value->type); ecs_value_move_ctor(world, value->type, value->ptr, temp_storage); } /* Cleanup temporary values */ int i; for (i = 0; i < stack.count; i ++) { const ecs_type_info_t *ti = stack.values[i].ti; ecs_assert(ti->hooks.dtor != NULL, ECS_INTERNAL_ERROR, NULL); ti->hooks.dtor(stack.values[i].ptr, 1, ti); } flecs_stack_restore_cursor(stack.stack, &stack.cursor); return ptr; } #endif /** * @file addons/stats.c * @brief Stats addon. */ #ifdef FLECS_SYSTEM #endif #ifdef FLECS_PIPELINE #endif #ifdef FLECS_STATS #define ECS_GAUGE_RECORD(m, t, value)\ flecs_gauge_record(m, t, (ecs_float_t)(value)) #define ECS_COUNTER_RECORD(m, t, value)\ flecs_counter_record(m, t, (double)(value)) #define ECS_METRIC_FIRST(stats)\ ECS_CAST(ecs_metric_t*, ECS_OFFSET(&stats->first_, ECS_SIZEOF(int64_t))) #define ECS_METRIC_LAST(stats)\ ECS_CAST(ecs_metric_t*, ECS_OFFSET(&stats->last_, -ECS_SIZEOF(ecs_metric_t))) static int32_t t_next( int32_t t) { return (t + 1) % ECS_STAT_WINDOW; } static int32_t t_prev( int32_t t) { return (t - 1 + ECS_STAT_WINDOW) % ECS_STAT_WINDOW; } static void flecs_gauge_record( ecs_metric_t *m, int32_t t, ecs_float_t value) { m->gauge.avg[t] = value; m->gauge.min[t] = value; m->gauge.max[t] = value; } static double flecs_counter_record( ecs_metric_t *m, int32_t t, double value) { int32_t tp = t_prev(t); double prev = m->counter.value[tp]; m->counter.value[t] = value; double gauge_value = value - prev; if (gauge_value < 0) { gauge_value = 0; /* Counters are monotonically increasing */ } flecs_gauge_record(m, t, (ecs_float_t)gauge_value); return gauge_value; } static void flecs_metric_print( const char *name, ecs_float_t value) { ecs_size_t len = ecs_os_strlen(name); ecs_trace("%s: %*s %.2f", name, 32 - len, "", (double)value); } static void flecs_gauge_print( const char *name, int32_t t, const ecs_metric_t *m) { flecs_metric_print(name, m->gauge.avg[t]); } static void flecs_counter_print( const char *name, int32_t t, const ecs_metric_t *m) { flecs_metric_print(name, m->counter.rate.avg[t]); } void ecs_metric_reduce( ecs_metric_t *dst, const ecs_metric_t *src, int32_t t_dst, int32_t t_src) { ecs_check(dst != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(src != NULL, ECS_INVALID_PARAMETER, NULL); bool min_set = false; dst->gauge.avg[t_dst] = 0; dst->gauge.min[t_dst] = 0; dst->gauge.max[t_dst] = 0; ecs_float_t fwindow = (ecs_float_t)ECS_STAT_WINDOW; int32_t i; for (i = 0; i < ECS_STAT_WINDOW; i ++) { int32_t t = (t_src + i) % ECS_STAT_WINDOW; dst->gauge.avg[t_dst] += src->gauge.avg[t] / fwindow; if (!min_set || (src->gauge.min[t] < dst->gauge.min[t_dst])) { dst->gauge.min[t_dst] = src->gauge.min[t]; min_set = true; } if ((src->gauge.max[t] > dst->gauge.max[t_dst])) { dst->gauge.max[t_dst] = src->gauge.max[t]; } } dst->counter.value[t_dst] = src->counter.value[t_src]; error: return; } void ecs_metric_reduce_last( ecs_metric_t *m, int32_t prev, int32_t count) { ecs_check(m != NULL, ECS_INVALID_PARAMETER, NULL); int32_t t = t_next(prev); if (m->gauge.min[t] < m->gauge.min[prev]) { m->gauge.min[prev] = m->gauge.min[t]; } if (m->gauge.max[t] > m->gauge.max[prev]) { m->gauge.max[prev] = m->gauge.max[t]; } ecs_float_t fcount = (ecs_float_t)(count + 1); ecs_float_t cur = m->gauge.avg[prev]; ecs_float_t next = m->gauge.avg[t]; cur *= ((fcount - 1) / fcount); next *= 1 / fcount; m->gauge.avg[prev] = cur + next; m->counter.value[prev] = m->counter.value[t]; error: return; } void ecs_metric_copy( ecs_metric_t *m, int32_t dst, int32_t src) { ecs_check(m != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(dst != src, ECS_INVALID_PARAMETER, NULL); m->gauge.avg[dst] = m->gauge.avg[src]; m->gauge.min[dst] = m->gauge.min[src]; m->gauge.max[dst] = m->gauge.max[src]; m->counter.value[dst] = m->counter.value[src]; error: return; } static void flecs_stats_reduce( ecs_metric_t *dst_cur, ecs_metric_t *dst_last, ecs_metric_t *src_cur, int32_t t_dst, int32_t t_src) { for (; dst_cur <= dst_last; dst_cur ++, src_cur ++) { ecs_metric_reduce(dst_cur, src_cur, t_dst, t_src); } } static void flecs_stats_reduce_last( ecs_metric_t *dst_cur, ecs_metric_t *dst_last, ecs_metric_t *src_cur, int32_t t_dst, int32_t t_src, int32_t count) { int32_t t_dst_next = t_next(t_dst); for (; dst_cur <= dst_last; dst_cur ++, src_cur ++) { /* Reduce into previous value */ ecs_metric_reduce_last(dst_cur, t_dst, count); /* Restore old value */ dst_cur->gauge.avg[t_dst_next] = src_cur->gauge.avg[t_src]; dst_cur->gauge.min[t_dst_next] = src_cur->gauge.min[t_src]; dst_cur->gauge.max[t_dst_next] = src_cur->gauge.max[t_src]; dst_cur->counter.value[t_dst_next] = src_cur->counter.value[t_src]; } } static void flecs_stats_repeat_last( ecs_metric_t *cur, ecs_metric_t *last, int32_t t) { int32_t prev = t_prev(t); for (; cur <= last; cur ++) { ecs_metric_copy(cur, t, prev); } } static void flecs_stats_copy_last( ecs_metric_t *dst_cur, ecs_metric_t *dst_last, ecs_metric_t *src_cur, int32_t t_dst, int32_t t_src) { for (; dst_cur <= dst_last; dst_cur ++, src_cur ++) { dst_cur->gauge.avg[t_dst] = src_cur->gauge.avg[t_src]; dst_cur->gauge.min[t_dst] = src_cur->gauge.min[t_src]; dst_cur->gauge.max[t_dst] = src_cur->gauge.max[t_src]; dst_cur->counter.value[t_dst] = src_cur->counter.value[t_src]; } } void ecs_world_stats_get( const ecs_world_t *world, ecs_world_stats_t *s) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); int32_t t = s->t = t_next(s->t); double delta_frame_count = ECS_COUNTER_RECORD(&s->frame.frame_count, t, world->info.frame_count_total); ECS_COUNTER_RECORD(&s->frame.merge_count, t, world->info.merge_count_total); ECS_COUNTER_RECORD(&s->frame.rematch_count, t, world->info.rematch_count_total); ECS_COUNTER_RECORD(&s->frame.pipeline_build_count, t, world->info.pipeline_build_count_total); ECS_COUNTER_RECORD(&s->frame.systems_ran, t, world->info.systems_ran_frame); ECS_COUNTER_RECORD(&s->frame.observers_ran, t, world->info.observers_ran_frame); ECS_COUNTER_RECORD(&s->frame.event_emit_count, t, world->event_id); double delta_world_time = ECS_COUNTER_RECORD(&s->performance.world_time_raw, t, world->info.world_time_total_raw); ECS_COUNTER_RECORD(&s->performance.world_time, t, world->info.world_time_total); ECS_COUNTER_RECORD(&s->performance.frame_time, t, world->info.frame_time_total); ECS_COUNTER_RECORD(&s->performance.system_time, t, world->info.system_time_total); ECS_COUNTER_RECORD(&s->performance.emit_time, t, world->info.emit_time_total); ECS_COUNTER_RECORD(&s->performance.merge_time, t, world->info.merge_time_total); ECS_COUNTER_RECORD(&s->performance.rematch_time, t, world->info.rematch_time_total); ECS_GAUGE_RECORD(&s->performance.delta_time, t, delta_world_time); if (delta_world_time != 0 && delta_frame_count != 0) { ECS_GAUGE_RECORD(&s->performance.fps, t, (double)1 / (delta_world_time / (double)delta_frame_count)); } else { ECS_GAUGE_RECORD(&s->performance.fps, t, 0); } ECS_GAUGE_RECORD(&s->entities.count, t, flecs_sparse_count(ecs_eis(world))); ECS_GAUGE_RECORD(&s->entities.not_alive_count, t, flecs_sparse_not_alive_count(ecs_eis(world))); ECS_GAUGE_RECORD(&s->ids.count, t, world->info.id_count); ECS_GAUGE_RECORD(&s->ids.tag_count, t, world->info.tag_id_count); ECS_GAUGE_RECORD(&s->ids.component_count, t, world->info.component_id_count); ECS_GAUGE_RECORD(&s->ids.pair_count, t, world->info.pair_id_count); ECS_GAUGE_RECORD(&s->ids.wildcard_count, t, world->info.wildcard_id_count); ECS_GAUGE_RECORD(&s->ids.type_count, t, ecs_sparse_count(&world->type_info)); ECS_COUNTER_RECORD(&s->ids.create_count, t, world->info.id_create_total); ECS_COUNTER_RECORD(&s->ids.delete_count, t, world->info.id_delete_total); ECS_GAUGE_RECORD(&s->queries.query_count, t, ecs_count_id(world, EcsQuery)); ECS_GAUGE_RECORD(&s->queries.observer_count, t, ecs_count_id(world, EcsObserver)); if (ecs_is_alive(world, EcsSystem)) { ECS_GAUGE_RECORD(&s->queries.system_count, t, ecs_count_id(world, EcsSystem)); } ECS_COUNTER_RECORD(&s->tables.create_count, t, world->info.table_create_total); ECS_COUNTER_RECORD(&s->tables.delete_count, t, world->info.table_delete_total); ECS_GAUGE_RECORD(&s->tables.count, t, world->info.table_count); ECS_GAUGE_RECORD(&s->tables.empty_count, t, world->info.empty_table_count); ECS_GAUGE_RECORD(&s->tables.tag_only_count, t, world->info.tag_table_count); ECS_GAUGE_RECORD(&s->tables.trivial_only_count, t, world->info.trivial_table_count); ECS_GAUGE_RECORD(&s->tables.storage_count, t, world->info.table_storage_count); ECS_GAUGE_RECORD(&s->tables.record_count, t, world->info.table_record_count); ECS_COUNTER_RECORD(&s->commands.add_count, t, world->info.cmd.add_count); ECS_COUNTER_RECORD(&s->commands.remove_count, t, world->info.cmd.remove_count); ECS_COUNTER_RECORD(&s->commands.delete_count, t, world->info.cmd.delete_count); ECS_COUNTER_RECORD(&s->commands.clear_count, t, world->info.cmd.clear_count); ECS_COUNTER_RECORD(&s->commands.set_count, t, world->info.cmd.set_count); ECS_COUNTER_RECORD(&s->commands.get_mut_count, t, world->info.cmd.get_mut_count); ECS_COUNTER_RECORD(&s->commands.modified_count, t, world->info.cmd.modified_count); ECS_COUNTER_RECORD(&s->commands.other_count, t, world->info.cmd.other_count); ECS_COUNTER_RECORD(&s->commands.discard_count, t, world->info.cmd.discard_count); ECS_COUNTER_RECORD(&s->commands.batched_entity_count, t, world->info.cmd.batched_entity_count); ECS_COUNTER_RECORD(&s->commands.batched_count, t, world->info.cmd.batched_command_count); int64_t outstanding_allocs = ecs_os_api_malloc_count + ecs_os_api_calloc_count - ecs_os_api_free_count; ECS_COUNTER_RECORD(&s->memory.alloc_count, t, ecs_os_api_malloc_count + ecs_os_api_calloc_count); ECS_COUNTER_RECORD(&s->memory.realloc_count, t, ecs_os_api_realloc_count); ECS_COUNTER_RECORD(&s->memory.free_count, t, ecs_os_api_free_count); ECS_GAUGE_RECORD(&s->memory.outstanding_alloc_count, t, outstanding_allocs); outstanding_allocs = ecs_block_allocator_alloc_count - ecs_block_allocator_free_count; ECS_COUNTER_RECORD(&s->memory.block_alloc_count, t, ecs_block_allocator_alloc_count); ECS_COUNTER_RECORD(&s->memory.block_free_count, t, ecs_block_allocator_free_count); ECS_GAUGE_RECORD(&s->memory.block_outstanding_alloc_count, t, outstanding_allocs); outstanding_allocs = ecs_stack_allocator_alloc_count - ecs_stack_allocator_free_count; ECS_COUNTER_RECORD(&s->memory.stack_alloc_count, t, ecs_stack_allocator_alloc_count); ECS_COUNTER_RECORD(&s->memory.stack_free_count, t, ecs_stack_allocator_free_count); ECS_GAUGE_RECORD(&s->memory.stack_outstanding_alloc_count, t, outstanding_allocs); #ifdef FLECS_REST ECS_COUNTER_RECORD(&s->rest.request_count, t, ecs_rest_request_count); ECS_COUNTER_RECORD(&s->rest.entity_count, t, ecs_rest_entity_count); ECS_COUNTER_RECORD(&s->rest.entity_error_count, t, ecs_rest_entity_error_count); ECS_COUNTER_RECORD(&s->rest.query_count, t, ecs_rest_query_count); ECS_COUNTER_RECORD(&s->rest.query_error_count, t, ecs_rest_query_error_count); ECS_COUNTER_RECORD(&s->rest.query_name_count, t, ecs_rest_query_name_count); ECS_COUNTER_RECORD(&s->rest.query_name_error_count, t, ecs_rest_query_name_error_count); ECS_COUNTER_RECORD(&s->rest.query_name_from_cache_count, t, ecs_rest_query_name_from_cache_count); ECS_COUNTER_RECORD(&s->rest.enable_count, t, ecs_rest_enable_count); ECS_COUNTER_RECORD(&s->rest.enable_error_count, t, ecs_rest_enable_error_count); ECS_COUNTER_RECORD(&s->rest.world_stats_count, t, ecs_rest_world_stats_count); ECS_COUNTER_RECORD(&s->rest.pipeline_stats_count, t, ecs_rest_pipeline_stats_count); ECS_COUNTER_RECORD(&s->rest.stats_error_count, t, ecs_rest_stats_error_count); #endif #ifdef FLECS_HTTP ECS_COUNTER_RECORD(&s->http.request_received_count, t, ecs_http_request_received_count); ECS_COUNTER_RECORD(&s->http.request_invalid_count, t, ecs_http_request_invalid_count); ECS_COUNTER_RECORD(&s->http.request_handled_ok_count, t, ecs_http_request_handled_ok_count); ECS_COUNTER_RECORD(&s->http.request_handled_error_count, t, ecs_http_request_handled_error_count); ECS_COUNTER_RECORD(&s->http.request_not_handled_count, t, ecs_http_request_not_handled_count); ECS_COUNTER_RECORD(&s->http.request_preflight_count, t, ecs_http_request_preflight_count); ECS_COUNTER_RECORD(&s->http.send_ok_count, t, ecs_http_send_ok_count); ECS_COUNTER_RECORD(&s->http.send_error_count, t, ecs_http_send_error_count); ECS_COUNTER_RECORD(&s->http.busy_count, t, ecs_http_busy_count); #endif error: return; } void ecs_world_stats_reduce( ecs_world_stats_t *dst, const ecs_world_stats_t *src) { flecs_stats_reduce(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), ECS_METRIC_FIRST(src), (dst->t = t_next(dst->t)), src->t); } void ecs_world_stats_reduce_last( ecs_world_stats_t *dst, const ecs_world_stats_t *src, int32_t count) { flecs_stats_reduce_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), ECS_METRIC_FIRST(src), (dst->t = t_prev(dst->t)), src->t, count); } void ecs_world_stats_repeat_last( ecs_world_stats_t *stats) { flecs_stats_repeat_last(ECS_METRIC_FIRST(stats), ECS_METRIC_LAST(stats), (stats->t = t_next(stats->t))); } void ecs_world_stats_copy_last( ecs_world_stats_t *dst, const ecs_world_stats_t *src) { flecs_stats_copy_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), ECS_METRIC_FIRST(src), dst->t, t_next(src->t)); } void ecs_query_stats_get( const ecs_world_t *world, const ecs_query_t *query, ecs_query_stats_t *s) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(query != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); (void)world; int32_t t = s->t = t_next(s->t); if (query->filter.flags & EcsFilterMatchThis) { ECS_GAUGE_RECORD(&s->matched_entity_count, t, ecs_query_entity_count(query)); ECS_GAUGE_RECORD(&s->matched_table_count, t, ecs_query_table_count(query)); ECS_GAUGE_RECORD(&s->matched_empty_table_count, t, ecs_query_empty_table_count(query)); } else { ECS_GAUGE_RECORD(&s->matched_entity_count, t, 0); ECS_GAUGE_RECORD(&s->matched_table_count, t, 0); ECS_GAUGE_RECORD(&s->matched_empty_table_count, t, 0); } error: return; } void ecs_query_stats_reduce( ecs_query_stats_t *dst, const ecs_query_stats_t *src) { flecs_stats_reduce(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), ECS_METRIC_FIRST(src), (dst->t = t_next(dst->t)), src->t); } void ecs_query_stats_reduce_last( ecs_query_stats_t *dst, const ecs_query_stats_t *src, int32_t count) { flecs_stats_reduce_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), ECS_METRIC_FIRST(src), (dst->t = t_prev(dst->t)), src->t, count); } void ecs_query_stats_repeat_last( ecs_query_stats_t *stats) { flecs_stats_repeat_last(ECS_METRIC_FIRST(stats), ECS_METRIC_LAST(stats), (stats->t = t_next(stats->t))); } void ecs_query_stats_copy_last( ecs_query_stats_t *dst, const ecs_query_stats_t *src) { flecs_stats_copy_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), ECS_METRIC_FIRST(src), dst->t, t_next(src->t)); } #ifdef FLECS_SYSTEM bool ecs_system_stats_get( const ecs_world_t *world, ecs_entity_t system, ecs_system_stats_t *s) { ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(system != 0, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); const ecs_system_t *ptr = ecs_poly_get(world, system, ecs_system_t); if (!ptr) { return false; } ecs_query_stats_get(world, ptr->query, &s->query); int32_t t = s->query.t; ECS_COUNTER_RECORD(&s->time_spent, t, ptr->time_spent); ECS_COUNTER_RECORD(&s->invoke_count, t, ptr->invoke_count); ECS_GAUGE_RECORD(&s->active, t, !ecs_has_id(world, system, EcsEmpty)); ECS_GAUGE_RECORD(&s->enabled, t, !ecs_has_id(world, system, EcsDisabled)); s->task = !(ptr->query->filter.flags & EcsFilterMatchThis); return true; error: return false; } void ecs_system_stats_reduce( ecs_system_stats_t *dst, const ecs_system_stats_t *src) { ecs_query_stats_reduce(&dst->query, &src->query); dst->task = src->task; flecs_stats_reduce(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), ECS_METRIC_FIRST(src), dst->query.t, src->query.t); } void ecs_system_stats_reduce_last( ecs_system_stats_t *dst, const ecs_system_stats_t *src, int32_t count) { ecs_query_stats_reduce_last(&dst->query, &src->query, count); dst->task = src->task; flecs_stats_reduce_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), ECS_METRIC_FIRST(src), dst->query.t, src->query.t, count); } void ecs_system_stats_repeat_last( ecs_system_stats_t *stats) { ecs_query_stats_repeat_last(&stats->query); flecs_stats_repeat_last(ECS_METRIC_FIRST(stats), ECS_METRIC_LAST(stats), (stats->query.t)); } void ecs_system_stats_copy_last( ecs_system_stats_t *dst, const ecs_system_stats_t *src) { ecs_query_stats_copy_last(&dst->query, &src->query); dst->task = src->task; flecs_stats_copy_last(ECS_METRIC_FIRST(dst), ECS_METRIC_LAST(dst), ECS_METRIC_FIRST(src), dst->query.t, t_next(src->query.t)); } #endif #ifdef FLECS_PIPELINE bool ecs_pipeline_stats_get( ecs_world_t *stage, ecs_entity_t pipeline, ecs_pipeline_stats_t *s) { ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(pipeline != 0, ECS_INVALID_PARAMETER, NULL); const ecs_world_t *world = ecs_get_world(stage); const EcsPipeline *pqc = ecs_get(world, pipeline, EcsPipeline); if (!pqc) { return false; } ecs_pipeline_state_t *pq = pqc->state; ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); int32_t sys_count = 0, active_sys_count = 0; /* Count number of active systems */ ecs_iter_t it = ecs_query_iter(stage, pq->query); while (ecs_query_next(&it)) { if (flecs_id_record_get_table(pq->idr_inactive, it.table) != NULL) { continue; } active_sys_count += it.count; } /* Count total number of systems in pipeline */ it = ecs_query_iter(stage, pq->query); while (ecs_query_next(&it)) { sys_count += it.count; } /* Also count synchronization points */ ecs_vec_t *ops = &pq->ops; ecs_pipeline_op_t *op = ecs_vec_first_t(ops, ecs_pipeline_op_t); ecs_pipeline_op_t *op_last = ecs_vec_last_t(ops, ecs_pipeline_op_t); int32_t pip_count = active_sys_count + ecs_vec_count(ops); if (!sys_count) { return false; } if (ecs_map_is_init(&s->system_stats) && !sys_count) { ecs_map_fini(&s->system_stats); } ecs_map_init_if(&s->system_stats, NULL); /* Make sure vector is large enough to store all systems & sync points */ ecs_entity_t *systems = NULL; if (pip_count) { ecs_vec_init_if_t(&s->systems, ecs_entity_t); ecs_vec_set_count_t(NULL, &s->systems, ecs_entity_t, pip_count); systems = ecs_vec_first_t(&s->systems, ecs_entity_t); /* Populate systems vector, keep track of sync points */ it = ecs_query_iter(stage, pq->query); int32_t i, i_system = 0, ran_since_merge = 0; while (ecs_query_next(&it)) { if (flecs_id_record_get_table(pq->idr_inactive, it.table) != NULL) { continue; } for (i = 0; i < it.count; i ++) { systems[i_system ++] = it.entities[i]; ran_since_merge ++; if (op != op_last && ran_since_merge == op->count) { ran_since_merge = 0; op++; systems[i_system ++] = 0; /* 0 indicates a merge point */ } } } systems[i_system ++] = 0; /* Last merge */ ecs_assert(pip_count == i_system, ECS_INTERNAL_ERROR, NULL); } else { ecs_vec_fini_t(NULL, &s->systems, ecs_entity_t); } /* Separately populate system stats map from build query, which includes * systems that aren't currently active */ it = ecs_query_iter(stage, pq->query); while (ecs_query_next(&it)) { int i; for (i = 0; i < it.count; i ++) { ecs_system_stats_t *stats = ecs_map_ensure_alloc_t(&s->system_stats, ecs_system_stats_t, it.entities[i]); stats->query.t = s->t; ecs_system_stats_get(world, it.entities[i], stats); } } s->t = t_next(s->t); return true; error: return false; } void ecs_pipeline_stats_fini( ecs_pipeline_stats_t *stats) { ecs_map_iter_t it = ecs_map_iter(&stats->system_stats); while (ecs_map_next(&it)) { ecs_system_stats_t *elem = ecs_map_ptr(&it); ecs_os_free(elem); } ecs_map_fini(&stats->system_stats); ecs_vec_fini_t(NULL, &stats->systems, ecs_entity_t); } void ecs_pipeline_stats_reduce( ecs_pipeline_stats_t *dst, const ecs_pipeline_stats_t *src) { int32_t system_count = ecs_vec_count(&src->systems); ecs_vec_init_if_t(&dst->systems, ecs_entity_t); ecs_vec_set_count_t(NULL, &dst->systems, ecs_entity_t, system_count); ecs_entity_t *dst_systems = ecs_vec_first_t(&dst->systems, ecs_entity_t); ecs_entity_t *src_systems = ecs_vec_first_t(&src->systems, ecs_entity_t); ecs_os_memcpy_n(dst_systems, src_systems, ecs_entity_t, system_count); ecs_map_init_if(&dst->system_stats, NULL); ecs_map_iter_t it = ecs_map_iter(&src->system_stats); while (ecs_map_next(&it)) { ecs_system_stats_t *sys_src = ecs_map_ptr(&it); ecs_system_stats_t *sys_dst = ecs_map_ensure_alloc_t(&dst->system_stats, ecs_system_stats_t, ecs_map_key(&it)); sys_dst->query.t = dst->t; ecs_system_stats_reduce(sys_dst, sys_src); } dst->t = t_next(dst->t); } void ecs_pipeline_stats_reduce_last( ecs_pipeline_stats_t *dst, const ecs_pipeline_stats_t *src, int32_t count) { ecs_map_init_if(&dst->system_stats, NULL); ecs_map_iter_t it = ecs_map_iter(&src->system_stats); while (ecs_map_next(&it)) { ecs_system_stats_t *sys_src = ecs_map_ptr(&it); ecs_system_stats_t *sys_dst = ecs_map_ensure_alloc_t(&dst->system_stats, ecs_system_stats_t, ecs_map_key(&it)); sys_dst->query.t = dst->t; ecs_system_stats_reduce_last(sys_dst, sys_src, count); } dst->t = t_prev(dst->t); } void ecs_pipeline_stats_repeat_last( ecs_pipeline_stats_t *stats) { ecs_map_iter_t it = ecs_map_iter(&stats->system_stats); while (ecs_map_next(&it)) { ecs_system_stats_t *sys = ecs_map_ptr(&it); sys->query.t = stats->t; ecs_system_stats_repeat_last(sys); } stats->t = t_next(stats->t); } void ecs_pipeline_stats_copy_last( ecs_pipeline_stats_t *dst, const ecs_pipeline_stats_t *src) { ecs_map_init_if(&dst->system_stats, NULL); ecs_map_iter_t it = ecs_map_iter(&src->system_stats); while (ecs_map_next(&it)) { ecs_system_stats_t *sys_src = ecs_map_ptr(&it); ecs_system_stats_t *sys_dst = ecs_map_ensure_alloc_t(&dst->system_stats, ecs_system_stats_t, ecs_map_key(&it)); sys_dst->query.t = dst->t; ecs_system_stats_copy_last(sys_dst, sys_src); } } #endif void ecs_world_stats_log( const ecs_world_t *world, const ecs_world_stats_t *s) { int32_t t = s->t; ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(s != NULL, ECS_INVALID_PARAMETER, NULL); world = ecs_get_world(world); flecs_counter_print("Frame", t, &s->frame.frame_count); ecs_trace("-------------------------------------"); flecs_counter_print("pipeline rebuilds", t, &s->frame.pipeline_build_count); flecs_counter_print("systems ran", t, &s->frame.systems_ran); ecs_trace(""); flecs_metric_print("target FPS", world->info.target_fps); flecs_metric_print("time scale", world->info.time_scale); ecs_trace(""); flecs_gauge_print("actual FPS", t, &s->performance.fps); flecs_counter_print("frame time", t, &s->performance.frame_time); flecs_counter_print("system time", t, &s->performance.system_time); flecs_counter_print("merge time", t, &s->performance.merge_time); flecs_counter_print("simulation time elapsed", t, &s->performance.world_time); ecs_trace(""); flecs_gauge_print("id count", t, &s->ids.count); flecs_gauge_print("tag id count", t, &s->ids.tag_count); flecs_gauge_print("component id count", t, &s->ids.component_count); flecs_gauge_print("pair id count", t, &s->ids.pair_count); flecs_gauge_print("wildcard id count", t, &s->ids.wildcard_count); flecs_gauge_print("type count", t, &s->ids.type_count); flecs_counter_print("id create count", t, &s->ids.create_count); flecs_counter_print("id delete count", t, &s->ids.delete_count); ecs_trace(""); flecs_gauge_print("alive entity count", t, &s->entities.count); flecs_gauge_print("not alive entity count", t, &s->entities.not_alive_count); ecs_trace(""); flecs_gauge_print("query count", t, &s->queries.query_count); flecs_gauge_print("observer count", t, &s->queries.observer_count); flecs_gauge_print("system count", t, &s->queries.system_count); ecs_trace(""); flecs_gauge_print("table count", t, &s->tables.count); flecs_gauge_print("empty table count", t, &s->tables.empty_count); flecs_gauge_print("tag table count", t, &s->tables.tag_only_count); flecs_gauge_print("trivial table count", t, &s->tables.trivial_only_count); flecs_gauge_print("table storage count", t, &s->tables.storage_count); flecs_gauge_print("table cache record count", t, &s->tables.record_count); flecs_counter_print("table create count", t, &s->tables.create_count); flecs_counter_print("table delete count", t, &s->tables.delete_count); ecs_trace(""); flecs_counter_print("add commands", t, &s->commands.add_count); flecs_counter_print("remove commands", t, &s->commands.remove_count); flecs_counter_print("delete commands", t, &s->commands.delete_count); flecs_counter_print("clear commands", t, &s->commands.clear_count); flecs_counter_print("set commands", t, &s->commands.set_count); flecs_counter_print("get_mut commands", t, &s->commands.get_mut_count); flecs_counter_print("modified commands", t, &s->commands.modified_count); flecs_counter_print("other commands", t, &s->commands.other_count); flecs_counter_print("discarded commands", t, &s->commands.discard_count); flecs_counter_print("batched entities", t, &s->commands.batched_entity_count); flecs_counter_print("batched commands", t, &s->commands.batched_count); ecs_trace(""); error: return; } #endif /** * @file addons/units.c * @brief Units addon. */ #ifdef FLECS_UNITS ECS_DECLARE(EcsUnitPrefixes); ECS_DECLARE(EcsYocto); ECS_DECLARE(EcsZepto); ECS_DECLARE(EcsAtto); ECS_DECLARE(EcsFemto); ECS_DECLARE(EcsPico); ECS_DECLARE(EcsNano); ECS_DECLARE(EcsMicro); ECS_DECLARE(EcsMilli); ECS_DECLARE(EcsCenti); ECS_DECLARE(EcsDeci); ECS_DECLARE(EcsDeca); ECS_DECLARE(EcsHecto); ECS_DECLARE(EcsKilo); ECS_DECLARE(EcsMega); ECS_DECLARE(EcsGiga); ECS_DECLARE(EcsTera); ECS_DECLARE(EcsPeta); ECS_DECLARE(EcsExa); ECS_DECLARE(EcsZetta); ECS_DECLARE(EcsYotta); ECS_DECLARE(EcsKibi); ECS_DECLARE(EcsMebi); ECS_DECLARE(EcsGibi); ECS_DECLARE(EcsTebi); ECS_DECLARE(EcsPebi); ECS_DECLARE(EcsExbi); ECS_DECLARE(EcsZebi); ECS_DECLARE(EcsYobi); ECS_DECLARE(EcsDuration); ECS_DECLARE(EcsPicoSeconds); ECS_DECLARE(EcsNanoSeconds); ECS_DECLARE(EcsMicroSeconds); ECS_DECLARE(EcsMilliSeconds); ECS_DECLARE(EcsSeconds); ECS_DECLARE(EcsMinutes); ECS_DECLARE(EcsHours); ECS_DECLARE(EcsDays); ECS_DECLARE(EcsTime); ECS_DECLARE(EcsDate); ECS_DECLARE(EcsMass); ECS_DECLARE(EcsGrams); ECS_DECLARE(EcsKiloGrams); ECS_DECLARE(EcsElectricCurrent); ECS_DECLARE(EcsAmpere); ECS_DECLARE(EcsAmount); ECS_DECLARE(EcsMole); ECS_DECLARE(EcsLuminousIntensity); ECS_DECLARE(EcsCandela); ECS_DECLARE(EcsForce); ECS_DECLARE(EcsNewton); ECS_DECLARE(EcsLength); ECS_DECLARE(EcsMeters); ECS_DECLARE(EcsPicoMeters); ECS_DECLARE(EcsNanoMeters); ECS_DECLARE(EcsMicroMeters); ECS_DECLARE(EcsMilliMeters); ECS_DECLARE(EcsCentiMeters); ECS_DECLARE(EcsKiloMeters); ECS_DECLARE(EcsMiles); ECS_DECLARE(EcsPixels); ECS_DECLARE(EcsPressure); ECS_DECLARE(EcsPascal); ECS_DECLARE(EcsBar); ECS_DECLARE(EcsSpeed); ECS_DECLARE(EcsMetersPerSecond); ECS_DECLARE(EcsKiloMetersPerSecond); ECS_DECLARE(EcsKiloMetersPerHour); ECS_DECLARE(EcsMilesPerHour); ECS_DECLARE(EcsAcceleration); ECS_DECLARE(EcsTemperature); ECS_DECLARE(EcsKelvin); ECS_DECLARE(EcsCelsius); ECS_DECLARE(EcsFahrenheit); ECS_DECLARE(EcsData); ECS_DECLARE(EcsBits); ECS_DECLARE(EcsKiloBits); ECS_DECLARE(EcsMegaBits); ECS_DECLARE(EcsGigaBits); ECS_DECLARE(EcsBytes); ECS_DECLARE(EcsKiloBytes); ECS_DECLARE(EcsMegaBytes); ECS_DECLARE(EcsGigaBytes); ECS_DECLARE(EcsKibiBytes); ECS_DECLARE(EcsGibiBytes); ECS_DECLARE(EcsMebiBytes); ECS_DECLARE(EcsDataRate); ECS_DECLARE(EcsBitsPerSecond); ECS_DECLARE(EcsKiloBitsPerSecond); ECS_DECLARE(EcsMegaBitsPerSecond); ECS_DECLARE(EcsGigaBitsPerSecond); ECS_DECLARE(EcsBytesPerSecond); ECS_DECLARE(EcsKiloBytesPerSecond); ECS_DECLARE(EcsMegaBytesPerSecond); ECS_DECLARE(EcsGigaBytesPerSecond); ECS_DECLARE(EcsPercentage); ECS_DECLARE(EcsAngle); ECS_DECLARE(EcsRadians); ECS_DECLARE(EcsDegrees); ECS_DECLARE(EcsBel); ECS_DECLARE(EcsDeciBel); ECS_DECLARE(EcsFrequency); ECS_DECLARE(EcsHertz); ECS_DECLARE(EcsKiloHertz); ECS_DECLARE(EcsMegaHertz); ECS_DECLARE(EcsGigaHertz); ECS_DECLARE(EcsUri); ECS_DECLARE(EcsUriHyperlink); ECS_DECLARE(EcsUriImage); ECS_DECLARE(EcsUriFile); void FlecsUnitsImport( ecs_world_t *world) { ECS_MODULE(world, FlecsUnits); ecs_set_name_prefix(world, "Ecs"); EcsUnitPrefixes = ecs_entity_init(world, &(ecs_entity_desc_t){ .name = "prefixes", .add = { EcsModule } }); /* Initialize unit prefixes */ ecs_entity_t prev_scope = ecs_set_scope(world, EcsUnitPrefixes); EcsYocto = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ .entity = ecs_entity(world, { .name = "Yocto" }), .symbol = "y", .translation = { .factor = 10, .power = -24 } }); EcsZepto = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ .entity = ecs_entity(world, { .name = "Zepto" }), .symbol = "z", .translation = { .factor = 10, .power = -21 } }); EcsAtto = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ .entity = ecs_entity(world, { .name = "Atto" }), .symbol = "a", .translation = { .factor = 10, .power = -18 } }); EcsFemto = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ .entity = ecs_entity(world, { .name = "Femto" }), .symbol = "a", .translation = { .factor = 10, .power = -15 } }); EcsPico = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ .entity = ecs_entity(world, { .name = "Pico" }), .symbol = "p", .translation = { .factor = 10, .power = -12 } }); EcsNano = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ .entity = ecs_entity(world, { .name = "Nano" }), .symbol = "n", .translation = { .factor = 10, .power = -9 } }); EcsMicro = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ .entity = ecs_entity(world, { .name = "Micro" }), .symbol = "μ", .translation = { .factor = 10, .power = -6 } }); EcsMilli = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ .entity = ecs_entity(world, { .name = "Milli" }), .symbol = "m", .translation = { .factor = 10, .power = -3 } }); EcsCenti = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ .entity = ecs_entity(world, { .name = "Centi" }), .symbol = "c", .translation = { .factor = 10, .power = -2 } }); EcsDeci = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ .entity = ecs_entity(world, { .name = "Deci" }), .symbol = "d", .translation = { .factor = 10, .power = -1 } }); EcsDeca = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ .entity = ecs_entity(world, { .name = "Deca" }), .symbol = "da", .translation = { .factor = 10, .power = 1 } }); EcsHecto = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ .entity = ecs_entity(world, { .name = "Hecto" }), .symbol = "h", .translation = { .factor = 10, .power = 2 } }); EcsKilo = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ .entity = ecs_entity(world, { .name = "Kilo" }), .symbol = "k", .translation = { .factor = 10, .power = 3 } }); EcsMega = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ .entity = ecs_entity(world, { .name = "Mega" }), .symbol = "M", .translation = { .factor = 10, .power = 6 } }); EcsGiga = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ .entity = ecs_entity(world, { .name = "Giga" }), .symbol = "G", .translation = { .factor = 10, .power = 9 } }); EcsTera = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ .entity = ecs_entity(world, { .name = "Tera" }), .symbol = "T", .translation = { .factor = 10, .power = 12 } }); EcsPeta = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ .entity = ecs_entity(world, { .name = "Peta" }), .symbol = "P", .translation = { .factor = 10, .power = 15 } }); EcsExa = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ .entity = ecs_entity(world, { .name = "Exa" }), .symbol = "E", .translation = { .factor = 10, .power = 18 } }); EcsZetta = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ .entity = ecs_entity(world, { .name = "Zetta" }), .symbol = "Z", .translation = { .factor = 10, .power = 21 } }); EcsYotta = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ .entity = ecs_entity(world, { .name = "Yotta" }), .symbol = "Y", .translation = { .factor = 10, .power = 24 } }); EcsKibi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ .entity = ecs_entity(world, { .name = "Kibi" }), .symbol = "Ki", .translation = { .factor = 1024, .power = 1 } }); EcsMebi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ .entity = ecs_entity(world, { .name = "Mebi" }), .symbol = "Mi", .translation = { .factor = 1024, .power = 2 } }); EcsGibi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ .entity = ecs_entity(world, { .name = "Gibi" }), .symbol = "Gi", .translation = { .factor = 1024, .power = 3 } }); EcsTebi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ .entity = ecs_entity(world, { .name = "Tebi" }), .symbol = "Ti", .translation = { .factor = 1024, .power = 4 } }); EcsPebi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ .entity = ecs_entity(world, { .name = "Pebi" }), .symbol = "Pi", .translation = { .factor = 1024, .power = 5 } }); EcsExbi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ .entity = ecs_entity(world, { .name = "Exbi" }), .symbol = "Ei", .translation = { .factor = 1024, .power = 6 } }); EcsZebi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ .entity = ecs_entity(world, { .name = "Zebi" }), .symbol = "Zi", .translation = { .factor = 1024, .power = 7 } }); EcsYobi = ecs_unit_prefix_init(world, &(ecs_unit_prefix_desc_t){ .entity = ecs_entity(world, { .name = "Yobi" }), .symbol = "Yi", .translation = { .factor = 1024, .power = 8 } }); ecs_set_scope(world, prev_scope); /* Duration units */ EcsDuration = ecs_quantity_init(world, &(ecs_entity_desc_t){ .name = "Duration" }); prev_scope = ecs_set_scope(world, EcsDuration); EcsSeconds = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "Seconds" }), .quantity = EcsDuration, .symbol = "s" }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsSeconds, .kind = EcsF32 }); EcsPicoSeconds = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "PicoSeconds" }), .quantity = EcsDuration, .base = EcsSeconds, .prefix = EcsPico }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsPicoSeconds, .kind = EcsF32 }); EcsNanoSeconds = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "NanoSeconds" }), .quantity = EcsDuration, .base = EcsSeconds, .prefix = EcsNano }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsNanoSeconds, .kind = EcsF32 }); EcsMicroSeconds = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "MicroSeconds" }), .quantity = EcsDuration, .base = EcsSeconds, .prefix = EcsMicro }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsMicroSeconds, .kind = EcsF32 }); EcsMilliSeconds = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "MilliSeconds" }), .quantity = EcsDuration, .base = EcsSeconds, .prefix = EcsMilli }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsMilliSeconds, .kind = EcsF32 }); EcsMinutes = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "Minutes" }), .quantity = EcsDuration, .base = EcsSeconds, .symbol = "min", .translation = { .factor = 60, .power = 1 } }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsMinutes, .kind = EcsU32 }); EcsHours = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "Hours" }), .quantity = EcsDuration, .base = EcsMinutes, .symbol = "h", .translation = { .factor = 60, .power = 1 } }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsHours, .kind = EcsU32 }); EcsDays = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "Days" }), .quantity = EcsDuration, .base = EcsHours, .symbol = "d", .translation = { .factor = 24, .power = 1 } }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsDays, .kind = EcsU32 }); ecs_set_scope(world, prev_scope); /* Time units */ EcsTime = ecs_quantity_init(world, &(ecs_entity_desc_t){ .name = "Time" }); prev_scope = ecs_set_scope(world, EcsTime); EcsDate = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "Date" }), .quantity = EcsTime }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsDate, .kind = EcsU32 }); ecs_set_scope(world, prev_scope); /* Mass units */ EcsMass = ecs_quantity_init(world, &(ecs_entity_desc_t){ .name = "Mass" }); prev_scope = ecs_set_scope(world, EcsMass); EcsGrams = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "Grams" }), .quantity = EcsMass, .symbol = "g" }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsGrams, .kind = EcsF32 }); EcsKiloGrams = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "KiloGrams" }), .quantity = EcsMass, .prefix = EcsKilo, .base = EcsGrams }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsKiloGrams, .kind = EcsF32 }); ecs_set_scope(world, prev_scope); /* Electric current units */ EcsElectricCurrent = ecs_quantity_init(world, &(ecs_entity_desc_t){ .name = "ElectricCurrent" }); prev_scope = ecs_set_scope(world, EcsElectricCurrent); EcsAmpere = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "Ampere" }), .quantity = EcsElectricCurrent, .symbol = "A" }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsAmpere, .kind = EcsF32 }); ecs_set_scope(world, prev_scope); /* Amount of substance units */ EcsAmount = ecs_quantity_init(world, &(ecs_entity_desc_t){ .name = "Amount" }); prev_scope = ecs_set_scope(world, EcsAmount); EcsMole = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "Mole" }), .quantity = EcsAmount, .symbol = "mol" }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsMole, .kind = EcsF32 }); ecs_set_scope(world, prev_scope); /* Luminous intensity units */ EcsLuminousIntensity = ecs_quantity_init(world, &(ecs_entity_desc_t){ .name = "LuminousIntensity" }); prev_scope = ecs_set_scope(world, EcsLuminousIntensity); EcsCandela = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "Candela" }), .quantity = EcsLuminousIntensity, .symbol = "cd" }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsCandela, .kind = EcsF32 }); ecs_set_scope(world, prev_scope); /* Force units */ EcsForce = ecs_quantity_init(world, &(ecs_entity_desc_t){ .name = "Force" }); prev_scope = ecs_set_scope(world, EcsForce); EcsNewton = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "Newton" }), .quantity = EcsForce, .symbol = "N" }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsNewton, .kind = EcsF32 }); ecs_set_scope(world, prev_scope); /* Length units */ EcsLength = ecs_quantity_init(world, &(ecs_entity_desc_t){ .name = "Length" }); prev_scope = ecs_set_scope(world, EcsLength); EcsMeters = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "Meters" }), .quantity = EcsLength, .symbol = "m" }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsMeters, .kind = EcsF32 }); EcsPicoMeters = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "PicoMeters" }), .quantity = EcsLength, .base = EcsMeters, .prefix = EcsPico }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsPicoMeters, .kind = EcsF32 }); EcsNanoMeters = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "NanoMeters" }), .quantity = EcsLength, .base = EcsMeters, .prefix = EcsNano }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsNanoMeters, .kind = EcsF32 }); EcsMicroMeters = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "MicroMeters" }), .quantity = EcsLength, .base = EcsMeters, .prefix = EcsMicro }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsMicroMeters, .kind = EcsF32 }); EcsMilliMeters = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "MilliMeters" }), .quantity = EcsLength, .base = EcsMeters, .prefix = EcsMilli }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsMilliMeters, .kind = EcsF32 }); EcsCentiMeters = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "CentiMeters" }), .quantity = EcsLength, .base = EcsMeters, .prefix = EcsCenti }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsCentiMeters, .kind = EcsF32 }); EcsKiloMeters = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "KiloMeters" }), .quantity = EcsLength, .base = EcsMeters, .prefix = EcsKilo }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsKiloMeters, .kind = EcsF32 }); EcsMiles = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "Miles" }), .quantity = EcsLength, .symbol = "mi" }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsMiles, .kind = EcsF32 }); EcsPixels = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "Pixels" }), .quantity = EcsLength, .symbol = "px" }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsPixels, .kind = EcsF32 }); ecs_set_scope(world, prev_scope); /* Pressure units */ EcsPressure = ecs_quantity_init(world, &(ecs_entity_desc_t){ .name = "Pressure" }); prev_scope = ecs_set_scope(world, EcsPressure); EcsPascal = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "Pascal" }), .quantity = EcsPressure, .symbol = "Pa" }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsPascal, .kind = EcsF32 }); EcsBar = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "Bar" }), .quantity = EcsPressure, .symbol = "bar" }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsBar, .kind = EcsF32 }); ecs_set_scope(world, prev_scope); /* Speed units */ EcsSpeed = ecs_quantity_init(world, &(ecs_entity_desc_t){ .name = "Speed" }); prev_scope = ecs_set_scope(world, EcsSpeed); EcsMetersPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "MetersPerSecond" }), .quantity = EcsSpeed, .base = EcsMeters, .over = EcsSeconds }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsMetersPerSecond, .kind = EcsF32 }); EcsKiloMetersPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "KiloMetersPerSecond" }), .quantity = EcsSpeed, .base = EcsKiloMeters, .over = EcsSeconds }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsKiloMetersPerSecond, .kind = EcsF32 }); EcsKiloMetersPerHour = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "KiloMetersPerHour" }), .quantity = EcsSpeed, .base = EcsKiloMeters, .over = EcsHours }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsKiloMetersPerHour, .kind = EcsF32 }); EcsMilesPerHour = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "MilesPerHour" }), .quantity = EcsSpeed, .base = EcsMiles, .over = EcsHours }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsMilesPerHour, .kind = EcsF32 }); ecs_set_scope(world, prev_scope); /* Acceleration */ EcsAcceleration = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "Acceleration" }), .base = EcsMetersPerSecond, .over = EcsSeconds }); ecs_quantity_init(world, &(ecs_entity_desc_t){ .id = EcsAcceleration }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsAcceleration, .kind = EcsF32 }); /* Temperature units */ EcsTemperature = ecs_quantity_init(world, &(ecs_entity_desc_t){ .name = "Temperature" }); prev_scope = ecs_set_scope(world, EcsTemperature); EcsKelvin = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "Kelvin" }), .quantity = EcsTemperature, .symbol = "K" }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsKelvin, .kind = EcsF32 }); EcsCelsius = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "Celsius" }), .quantity = EcsTemperature, .symbol = "°C" }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsCelsius, .kind = EcsF32 }); EcsFahrenheit = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "Fahrenheit" }), .quantity = EcsTemperature, .symbol = "F" }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsFahrenheit, .kind = EcsF32 }); ecs_set_scope(world, prev_scope); /* Data units */ EcsData = ecs_quantity_init(world, &(ecs_entity_desc_t){ .name = "Data" }); prev_scope = ecs_set_scope(world, EcsData); EcsBits = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "Bits" }), .quantity = EcsData, .symbol = "bit" }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsBits, .kind = EcsU64 }); EcsKiloBits = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "KiloBits" }), .quantity = EcsData, .base = EcsBits, .prefix = EcsKilo }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsKiloBits, .kind = EcsU64 }); EcsMegaBits = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "MegaBits" }), .quantity = EcsData, .base = EcsBits, .prefix = EcsMega }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsMegaBits, .kind = EcsU64 }); EcsGigaBits = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "GigaBits" }), .quantity = EcsData, .base = EcsBits, .prefix = EcsGiga }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsGigaBits, .kind = EcsU64 }); EcsBytes = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "Bytes" }), .quantity = EcsData, .symbol = "B", .base = EcsBits, .translation = { .factor = 8, .power = 1 } }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsBytes, .kind = EcsU64 }); EcsKiloBytes = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "KiloBytes" }), .quantity = EcsData, .base = EcsBytes, .prefix = EcsKilo }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsKiloBytes, .kind = EcsU64 }); EcsMegaBytes = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "MegaBytes" }), .quantity = EcsData, .base = EcsBytes, .prefix = EcsMega }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsMegaBytes, .kind = EcsU64 }); EcsGigaBytes = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "GigaBytes" }), .quantity = EcsData, .base = EcsBytes, .prefix = EcsGiga }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsGigaBytes, .kind = EcsU64 }); EcsKibiBytes = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "KibiBytes" }), .quantity = EcsData, .base = EcsBytes, .prefix = EcsKibi }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsKibiBytes, .kind = EcsU64 }); EcsMebiBytes = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "MebiBytes" }), .quantity = EcsData, .base = EcsBytes, .prefix = EcsMebi }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsMebiBytes, .kind = EcsU64 }); EcsGibiBytes = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "GibiBytes" }), .quantity = EcsData, .base = EcsBytes, .prefix = EcsGibi }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsGibiBytes, .kind = EcsU64 }); ecs_set_scope(world, prev_scope); /* DataRate units */ EcsDataRate = ecs_quantity_init(world, &(ecs_entity_desc_t){ .name = "DataRate" }); prev_scope = ecs_set_scope(world, EcsDataRate); EcsBitsPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "BitsPerSecond" }), .quantity = EcsDataRate, .base = EcsBits, .over = EcsSeconds }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsBitsPerSecond, .kind = EcsU64 }); EcsKiloBitsPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "KiloBitsPerSecond" }), .quantity = EcsDataRate, .base = EcsKiloBits, .over = EcsSeconds }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsKiloBitsPerSecond, .kind = EcsU64 }); EcsMegaBitsPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "MegaBitsPerSecond" }), .quantity = EcsDataRate, .base = EcsMegaBits, .over = EcsSeconds }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsMegaBitsPerSecond, .kind = EcsU64 }); EcsGigaBitsPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "GigaBitsPerSecond" }), .quantity = EcsDataRate, .base = EcsGigaBits, .over = EcsSeconds }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsGigaBitsPerSecond, .kind = EcsU64 }); EcsBytesPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "BytesPerSecond" }), .quantity = EcsDataRate, .base = EcsBytes, .over = EcsSeconds }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsBytesPerSecond, .kind = EcsU64 }); EcsKiloBytesPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "KiloBytesPerSecond" }), .quantity = EcsDataRate, .base = EcsKiloBytes, .over = EcsSeconds }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsKiloBytesPerSecond, .kind = EcsU64 }); EcsMegaBytesPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "MegaBytesPerSecond" }), .quantity = EcsDataRate, .base = EcsMegaBytes, .over = EcsSeconds }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsMegaBytesPerSecond, .kind = EcsU64 }); EcsGigaBytesPerSecond = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "GigaBytesPerSecond" }), .quantity = EcsDataRate, .base = EcsGigaBytes, .over = EcsSeconds }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsGigaBytesPerSecond, .kind = EcsU64 }); ecs_set_scope(world, prev_scope); /* Percentage */ EcsPercentage = ecs_quantity_init(world, &(ecs_entity_desc_t){ .name = "Percentage" }); ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = EcsPercentage, .symbol = "%" }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsPercentage, .kind = EcsF32 }); /* Angles */ EcsAngle = ecs_quantity_init(world, &(ecs_entity_desc_t){ .name = "Angle" }); prev_scope = ecs_set_scope(world, EcsAngle); EcsRadians = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "Radians" }), .quantity = EcsAngle, .symbol = "rad" }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsRadians, .kind = EcsF32 }); EcsDegrees = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "Degrees" }), .quantity = EcsAngle, .symbol = "°" }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsDegrees, .kind = EcsF32 }); ecs_set_scope(world, prev_scope); /* DeciBel */ EcsBel = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "Bel" }), .symbol = "B" }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsBel, .kind = EcsF32 }); EcsDeciBel = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "DeciBel" }), .prefix = EcsDeci, .base = EcsBel }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsDeciBel, .kind = EcsF32 }); EcsFrequency = ecs_quantity_init(world, &(ecs_entity_desc_t){ .name = "Frequency" }); prev_scope = ecs_set_scope(world, EcsFrequency); EcsHertz = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "Hertz" }), .quantity = EcsFrequency, .symbol = "Hz" }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsHertz, .kind = EcsF32 }); EcsKiloHertz = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "KiloHertz" }), .prefix = EcsKilo, .base = EcsHertz }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsKiloHertz, .kind = EcsF32 }); EcsMegaHertz = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "MegaHertz" }), .prefix = EcsMega, .base = EcsHertz }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsMegaHertz, .kind = EcsF32 }); EcsGigaHertz = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "GigaHertz" }), .prefix = EcsGiga, .base = EcsHertz }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsGigaHertz, .kind = EcsF32 }); ecs_set_scope(world, prev_scope); EcsUri = ecs_quantity_init(world, &(ecs_entity_desc_t){ .name = "Uri" }); prev_scope = ecs_set_scope(world, EcsUri); EcsUriHyperlink = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "Hyperlink" }), .quantity = EcsUri }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsUriHyperlink, .kind = EcsString }); EcsUriImage = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "Image" }), .quantity = EcsUri }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsUriImage, .kind = EcsString }); EcsUriFile = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "File" }), .quantity = EcsUri }); ecs_primitive_init(world, &(ecs_primitive_desc_t){ .entity = EcsUriFile, .kind = EcsString }); ecs_set_scope(world, prev_scope); /* Documentation */ #ifdef FLECS_DOC ECS_IMPORT(world, FlecsDoc); ecs_doc_set_brief(world, EcsDuration, "Time amount (e.g. \"20 seconds\", \"2 hours\")"); ecs_doc_set_brief(world, EcsSeconds, "Time amount in seconds"); ecs_doc_set_brief(world, EcsMinutes, "60 seconds"); ecs_doc_set_brief(world, EcsHours, "60 minutes"); ecs_doc_set_brief(world, EcsDays, "24 hours"); ecs_doc_set_brief(world, EcsTime, "Time passed since an epoch (e.g. \"5pm\", \"March 3rd 2022\")"); ecs_doc_set_brief(world, EcsDate, "Seconds passed since January 1st 1970"); ecs_doc_set_brief(world, EcsMass, "Units of mass (e.g. \"5 kilograms\")"); ecs_doc_set_brief(world, EcsElectricCurrent, "Units of electrical current (e.g. \"2 ampere\")"); ecs_doc_set_brief(world, EcsAmount, "Units of amount of substance (e.g. \"2 mole\")"); ecs_doc_set_brief(world, EcsLuminousIntensity, "Units of luminous intensity (e.g. \"1 candela\")"); ecs_doc_set_brief(world, EcsForce, "Units of force (e.g. \"10 newton\")"); ecs_doc_set_brief(world, EcsLength, "Units of length (e.g. \"5 meters\", \"20 miles\")"); ecs_doc_set_brief(world, EcsPressure, "Units of pressure (e.g. \"1 bar\", \"1000 pascal\")"); ecs_doc_set_brief(world, EcsSpeed, "Units of movement (e.g. \"5 meters/second\")"); ecs_doc_set_brief(world, EcsAcceleration, "Unit of speed increase (e.g. \"5 meters/second/second\")"); ecs_doc_set_brief(world, EcsTemperature, "Units of temperature (e.g. \"5 degrees Celsius\")"); ecs_doc_set_brief(world, EcsData, "Units of information (e.g. \"8 bits\", \"100 megabytes\")"); ecs_doc_set_brief(world, EcsDataRate, "Units of data transmission (e.g. \"100 megabits/second\")"); ecs_doc_set_brief(world, EcsAngle, "Units of rotation (e.g. \"1.2 radians\", \"180 degrees\")"); ecs_doc_set_brief(world, EcsFrequency, "The number of occurrences of a repeating event per unit of time."); ecs_doc_set_brief(world, EcsUri, "Universal resource identifier."); #endif } #endif /** * @file addons/snapshot.c * @brief Snapshot addon. */ #ifdef FLECS_SNAPSHOT /* World snapshot */ struct ecs_snapshot_t { ecs_world_t *world; ecs_sparse_t entity_index; ecs_vec_t tables; ecs_entity_t last_id; }; /** Small footprint data structure for storing data associated with a table. */ typedef struct ecs_table_leaf_t { ecs_table_t *table; ecs_type_t type; ecs_data_t *data; } ecs_table_leaf_t; static ecs_data_t* flecs_duplicate_data( ecs_world_t *world, ecs_table_t *table, ecs_data_t *main_data) { if (!ecs_table_count(table)) { return NULL; } ecs_data_t *result = ecs_os_calloc_t(ecs_data_t); int32_t i, column_count = table->storage_count; result->columns = flecs_wdup_n(world, ecs_vec_t, column_count, main_data->columns); /* Copy entities and records */ ecs_allocator_t *a = &world->allocator; result->entities = ecs_vec_copy_t(a, &main_data->entities, ecs_entity_t); result->records = ecs_vec_copy_t(a, &main_data->records, ecs_record_t*); /* Copy each column */ for (i = 0; i < column_count; i ++) { ecs_vec_t *column = &result->columns[i]; ecs_type_info_t *ti = table->type_info[i]; int32_t size = ti->size; ecs_copy_t copy = ti->hooks.copy; if (copy) { ecs_vec_t dst = ecs_vec_copy(a, column, size); int32_t count = ecs_vec_count(column); void *dst_ptr = ecs_vec_first(&dst); void *src_ptr = ecs_vec_first(column); ecs_xtor_t ctor = ti->hooks.ctor; if (ctor) { ctor(dst_ptr, count, ti); } copy(dst_ptr, src_ptr, count, ti); *column = dst; } else { *column = ecs_vec_copy(a, column, size); } } return result; } static void snapshot_table( const ecs_world_t *world, ecs_snapshot_t *snapshot, ecs_table_t *table) { if (table->flags & EcsTableHasBuiltins) { return; } ecs_table_leaf_t *l = ecs_vec_get_t( &snapshot->tables, ecs_table_leaf_t, (int32_t)table->id); ecs_assert(l != NULL, ECS_INTERNAL_ERROR, NULL); l->table = table; l->type = flecs_type_copy((ecs_world_t*)world, &table->type); l->data = flecs_duplicate_data((ecs_world_t*)world, table, &table->data); } static ecs_snapshot_t* snapshot_create( const ecs_world_t *world, const ecs_sparse_t *entity_index, ecs_iter_t *iter, ecs_iter_next_action_t next) { ecs_snapshot_t *result = ecs_os_calloc_t(ecs_snapshot_t); ecs_assert(result != NULL, ECS_OUT_OF_MEMORY, NULL); ecs_run_aperiodic((ecs_world_t*)world, 0); result->world = (ecs_world_t*)world; /* If no iterator is provided, the snapshot will be taken of the entire * world, and we can simply copy the entity index as it will be restored * entirely upon snapshote restore. */ if (!iter && entity_index) { flecs_sparse_copy(&result->entity_index, entity_index); } /* Create vector with as many elements as tables, so we can store the * snapshot tables at their element ids. When restoring a snapshot, the code * will run a diff between the tables in the world and the snapshot, to see * which of the world tables still exist, no longer exist, or need to be * deleted. */ uint64_t t, table_count = flecs_sparse_last_id(&world->store.tables) + 1; ecs_vec_init_t(NULL, &result->tables, ecs_table_leaf_t, (int32_t)table_count); ecs_vec_set_count_t(NULL, &result->tables, ecs_table_leaf_t, (int32_t)table_count); ecs_table_leaf_t *arr = ecs_vec_first_t(&result->tables, ecs_table_leaf_t); /* Array may have holes, so initialize with 0 */ ecs_os_memset_n(arr, 0, ecs_table_leaf_t, table_count); /* Iterate tables in iterator */ if (iter) { while (next(iter)) { ecs_table_t *table = iter->table; snapshot_table(world, result, table); } } else { for (t = 0; t < table_count; t ++) { ecs_table_t *table = flecs_sparse_get_t( &world->store.tables, ecs_table_t, t); snapshot_table(world, result, table); } } return result; } /** Create a snapshot */ ecs_snapshot_t* ecs_snapshot_take( ecs_world_t *stage) { const ecs_world_t *world = ecs_get_world(stage); ecs_snapshot_t *result = snapshot_create( world, ecs_eis(world), NULL, NULL); result->last_id = world->info.last_id; return result; } /** Create a filtered snapshot */ ecs_snapshot_t* ecs_snapshot_take_w_iter( ecs_iter_t *iter) { ecs_world_t *world = iter->world; ecs_assert(world != NULL, ECS_INTERNAL_ERROR, NULL); ecs_snapshot_t *result = snapshot_create( world, ecs_eis(world), iter, iter ? iter->next : NULL); result->last_id = world->info.last_id; return result; } /* Restoring an unfiltered snapshot restores the world to the exact state it was * when the snapshot was taken. */ static void restore_unfiltered( ecs_world_t *world, ecs_snapshot_t *snapshot) { flecs_sparse_restore(ecs_eis(world), &snapshot->entity_index); flecs_sparse_fini(&snapshot->entity_index); world->info.last_id = snapshot->last_id; ecs_table_leaf_t *leafs = ecs_vec_first_t(&snapshot->tables, ecs_table_leaf_t); int32_t i, count = (int32_t)flecs_sparse_last_id(&world->store.tables); int32_t snapshot_count = ecs_vec_count(&snapshot->tables); for (i = 0; i <= count; i ++) { ecs_table_t *world_table = flecs_sparse_get_t( &world->store.tables, ecs_table_t, (uint32_t)i); if (world_table && (world_table->flags & EcsTableHasBuiltins)) { continue; } ecs_table_leaf_t *snapshot_table = NULL; if (i < snapshot_count) { snapshot_table = &leafs[i]; if (!snapshot_table->table) { snapshot_table = NULL; } } /* If the world table no longer exists but the snapshot table does, * reinsert it */ if (!world_table && snapshot_table) { ecs_table_t *table = flecs_table_find_or_create(world, &snapshot_table->type); ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL); if (snapshot_table->data) { flecs_table_replace_data(world, table, snapshot_table->data); } /* If the world table still exists, replace its data */ } else if (world_table && snapshot_table) { ecs_assert(snapshot_table->table == world_table, ECS_INTERNAL_ERROR, NULL); if (snapshot_table->data) { flecs_table_replace_data( world, world_table, snapshot_table->data); } else { flecs_table_clear_data( world, world_table, &world_table->data); flecs_table_init_data(world, world_table); } /* If the snapshot table doesn't exist, this table was created after the * snapshot was taken and needs to be deleted */ } else if (world_table && !snapshot_table) { /* Deleting a table invokes OnRemove triggers & updates the entity * index. That is not what we want, since entities may no longer be * valid (if they don't exist in the snapshot) or may have been * restored in a different table. Therefore first clear the data * from the table (which doesn't invoke triggers), and then delete * the table. */ flecs_table_clear_data(world, world_table, &world_table->data); flecs_delete_table(world, world_table); /* If there is no world & snapshot table, nothing needs to be done */ } else { } if (snapshot_table) { ecs_os_free(snapshot_table->data); flecs_type_free(world, &snapshot_table->type); } } /* Now that all tables have been restored and world is in a consistent * state, run OnSet systems */ int32_t world_count = flecs_sparse_count(&world->store.tables); for (i = 0; i < world_count; i ++) { ecs_table_t *table = flecs_sparse_get_dense_t( &world->store.tables, ecs_table_t, i); if (table->flags & EcsTableHasBuiltins) { continue; } int32_t tcount = ecs_table_count(table); if (tcount) { flecs_notify_on_set(world, table, 0, tcount, NULL, true); } } } /* Restoring a filtered snapshots only restores the entities in the snapshot * to their previous state. */ static void restore_filtered( ecs_world_t *world, ecs_snapshot_t *snapshot) { ecs_table_leaf_t *leafs = ecs_vec_first_t(&snapshot->tables, ecs_table_leaf_t); int32_t l = 0, snapshot_count = ecs_vec_count(&snapshot->tables); for (l = 0; l < snapshot_count; l ++) { ecs_table_leaf_t *snapshot_table = &leafs[l]; ecs_table_t *table = snapshot_table->table; if (!table) { continue; } ecs_data_t *data = snapshot_table->data; if (!data) { flecs_type_free(world, &snapshot_table->type); continue; } /* Delete entity from storage first, so that when we restore it to the * current table we can be sure that there won't be any duplicates */ int32_t i, entity_count = ecs_vec_count(&data->entities); ecs_entity_t *entities = ecs_vec_first( &snapshot_table->data->entities); for (i = 0; i < entity_count; i ++) { ecs_entity_t e = entities[i]; ecs_record_t *r = flecs_entities_try(world, e); if (r && r->table) { flecs_table_delete(world, r->table, ECS_RECORD_TO_ROW(r->row), true); } else { /* Make sure that the entity has the same generation count */ flecs_entities_set_generation(world, e); } } /* Merge data from snapshot table with world table */ int32_t old_count = ecs_table_count(snapshot_table->table); int32_t new_count = flecs_table_data_count(snapshot_table->data); flecs_table_merge(world, table, table, &table->data, snapshot_table->data); /* Run OnSet systems for merged entities */ if (new_count) { flecs_notify_on_set( world, table, old_count, new_count, NULL, true); } flecs_wfree_n(world, ecs_vec_t, table->storage_count, snapshot_table->data->columns); ecs_os_free(snapshot_table->data); flecs_type_free(world, &snapshot_table->type); } } /** Restore a snapshot */ void ecs_snapshot_restore( ecs_world_t *world, ecs_snapshot_t *snapshot) { ecs_run_aperiodic(world, 0); if (flecs_sparse_count(&snapshot->entity_index)) { /* Unfiltered snapshots have a copy of the entity index which is * copied back entirely when the snapshot is restored */ restore_unfiltered(world, snapshot); } else { restore_filtered(world, snapshot); } ecs_vec_fini_t(NULL, &snapshot->tables, ecs_table_leaf_t); ecs_os_free(snapshot); } ecs_iter_t ecs_snapshot_iter( ecs_snapshot_t *snapshot) { ecs_snapshot_iter_t iter = { .tables = snapshot->tables, .index = 0 }; return (ecs_iter_t){ .world = snapshot->world, .table_count = ecs_vec_count(&snapshot->tables), .priv.iter.snapshot = iter, .next = ecs_snapshot_next }; } bool ecs_snapshot_next( ecs_iter_t *it) { ecs_snapshot_iter_t *iter = &it->priv.iter.snapshot; ecs_table_leaf_t *tables = ecs_vec_first_t(&iter->tables, ecs_table_leaf_t); int32_t count = ecs_vec_count(&iter->tables); int32_t i; for (i = iter->index; i < count; i ++) { ecs_table_t *table = tables[i].table; if (!table) { continue; } ecs_data_t *data = tables[i].data; it->table = table; it->count = ecs_table_count(table); if (data) { it->entities = ecs_vec_first(&data->entities); } else { it->entities = NULL; } ECS_BIT_SET(it->flags, EcsIterIsValid); iter->index = i + 1; goto yield; } ECS_BIT_CLEAR(it->flags, EcsIterIsValid); return false; yield: ECS_BIT_CLEAR(it->flags, EcsIterIsValid); return true; } /** Cleanup snapshot */ void ecs_snapshot_free( ecs_snapshot_t *snapshot) { flecs_sparse_fini(&snapshot->entity_index); ecs_table_leaf_t *tables = ecs_vec_first_t(&snapshot->tables, ecs_table_leaf_t); int32_t i, count = ecs_vec_count(&snapshot->tables); for (i = 0; i < count; i ++) { ecs_table_leaf_t *snapshot_table = &tables[i]; ecs_table_t *table = snapshot_table->table; if (table) { ecs_data_t *data = snapshot_table->data; if (data) { flecs_table_clear_data(snapshot->world, table, data); ecs_os_free(data); } flecs_type_free(snapshot->world, &snapshot_table->type); } } ecs_vec_fini_t(NULL, &snapshot->tables, ecs_table_leaf_t); ecs_os_free(snapshot); } #endif /** * @file addons/system/system.c * @brief System addon. */ #ifdef FLECS_SYSTEM ecs_mixins_t ecs_system_t_mixins = { .type_name = "ecs_system_t", .elems = { [EcsMixinWorld] = offsetof(ecs_system_t, world), [EcsMixinEntity] = offsetof(ecs_system_t, entity), [EcsMixinDtor] = offsetof(ecs_system_t, dtor) } }; /* -- Public API -- */ ecs_entity_t ecs_run_intern( ecs_world_t *world, ecs_stage_t *stage, ecs_entity_t system, ecs_system_t *system_data, int32_t stage_index, int32_t stage_count, ecs_ftime_t delta_time, int32_t offset, int32_t limit, void *param) { ecs_ftime_t time_elapsed = delta_time; ecs_entity_t tick_source = system_data->tick_source; /* Support legacy behavior */ if (!param) { param = system_data->ctx; } if (tick_source) { const EcsTickSource *tick = ecs_get( world, tick_source, EcsTickSource); if (tick) { time_elapsed = tick->time_elapsed; /* If timer hasn't fired we shouldn't run the system */ if (!tick->tick) { return 0; } } else { /* If a timer has been set but the timer entity does not have the * EcsTimer component, don't run the system. This can be the result * of a single-shot timer that has fired already. Not resetting the * timer field of the system will ensure that the system won't be * ran after the timer has fired. */ return 0; } } if (ecs_should_log_3()) { char *path = ecs_get_fullpath(world, system); ecs_dbg_3("worker %d: %s", stage_index, path); ecs_os_free(path); } ecs_time_t time_start; bool measure_time = ECS_BIT_IS_SET(world->flags, EcsWorldMeasureSystemTime); if (measure_time) { ecs_os_get_time(&time_start); } ecs_world_t *thread_ctx = world; if (stage) { thread_ctx = stage->thread_ctx; } else { stage = &world->stages[0]; } /* Prepare the query iterator */ ecs_iter_t pit, wit, qit = ecs_query_iter(thread_ctx, system_data->query); ecs_iter_t *it = &qit; qit.system = system; qit.delta_time = delta_time; qit.delta_system_time = time_elapsed; qit.frame_offset = offset; qit.param = param; qit.ctx = system_data->ctx; qit.binding_ctx = system_data->binding_ctx; flecs_defer_begin(world, stage); if (offset || limit) { pit = ecs_page_iter(it, offset, limit); it = &pit; } if (stage_count > 1 && system_data->multi_threaded) { wit = ecs_worker_iter(it, stage_index, stage_count); it = &wit; } ecs_iter_action_t action = system_data->action; it->callback = action; ecs_run_action_t run = system_data->run; if (run) { run(it); } else if (system_data->query->filter.term_count) { if (it == &qit) { while (ecs_query_next(&qit)) { action(&qit); } } else { while (ecs_iter_next(it)) { action(it); } } } else { action(&qit); ecs_iter_fini(&qit); } if (measure_time) { system_data->time_spent += (ecs_ftime_t)ecs_time_measure(&time_start); } system_data->invoke_count ++; flecs_defer_end(world, stage); return it->interrupted_by; } /* -- Public API -- */ ecs_entity_t ecs_run_w_filter( ecs_world_t *world, ecs_entity_t system, ecs_ftime_t delta_time, int32_t offset, int32_t limit, void *param) { ecs_stage_t *stage = flecs_stage_from_world(&world); ecs_system_t *system_data = ecs_poly_get(world, system, ecs_system_t); ecs_assert(system_data != NULL, ECS_INVALID_PARAMETER, NULL); return ecs_run_intern(world, stage, system, system_data, 0, 0, delta_time, offset, limit, param); } ecs_entity_t ecs_run_worker( ecs_world_t *world, ecs_entity_t system, int32_t stage_index, int32_t stage_count, ecs_ftime_t delta_time, void *param) { ecs_stage_t *stage = flecs_stage_from_world(&world); ecs_system_t *system_data = ecs_poly_get(world, system, ecs_system_t); ecs_assert(system_data != NULL, ECS_INVALID_PARAMETER, NULL); return ecs_run_intern( world, stage, system, system_data, stage_index, stage_count, delta_time, 0, 0, param); } ecs_entity_t ecs_run( ecs_world_t *world, ecs_entity_t system, ecs_ftime_t delta_time, void *param) { return ecs_run_w_filter(world, system, delta_time, 0, 0, param); } ecs_query_t* ecs_system_get_query( const ecs_world_t *world, ecs_entity_t system) { const ecs_system_t *s = ecs_poly_get(world, system, ecs_system_t); if (s) { return s->query; } else { return NULL; } } void* ecs_get_system_ctx( const ecs_world_t *world, ecs_entity_t system) { const ecs_system_t *s = ecs_poly_get(world, system, ecs_system_t); if (s) { return s->ctx; } else { return NULL; } } void* ecs_get_system_binding_ctx( const ecs_world_t *world, ecs_entity_t system) { const ecs_system_t *s = ecs_poly_get(world, system, ecs_system_t); if (s) { return s->binding_ctx; } else { return NULL; } } /* System deinitialization */ static void flecs_system_fini(ecs_system_t *sys) { if (sys->ctx_free) { sys->ctx_free(sys->ctx); } if (sys->binding_ctx_free) { sys->binding_ctx_free(sys->binding_ctx); } ecs_poly_free(sys, ecs_system_t); } ecs_entity_t ecs_system_init( ecs_world_t *world, const ecs_system_desc_t *desc) { ecs_poly_assert(world, ecs_world_t); ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, NULL); ecs_assert(!(world->flags & EcsWorldReadonly), ECS_INVALID_WHILE_READONLY, NULL); ecs_entity_t entity = desc->entity; if (!entity) { entity = ecs_new(world, 0); } EcsPoly *poly = ecs_poly_bind(world, entity, ecs_system_t); if (!poly->poly) { ecs_system_t *system = ecs_poly_new(ecs_system_t); ecs_assert(system != NULL, ECS_INTERNAL_ERROR, NULL); poly->poly = system; system->world = world; system->dtor = (ecs_poly_dtor_t)flecs_system_fini; system->entity = entity; ecs_query_desc_t query_desc = desc->query; query_desc.filter.entity = entity; ecs_query_t *query = ecs_query_init(world, &query_desc); if (!query) { ecs_delete(world, entity); return 0; } /* Prevent the system from moving while we're initializing */ flecs_defer_begin(world, &world->stages[0]); system->query = query; system->query_entity = query->filter.entity; system->run = desc->run; system->action = desc->callback; system->ctx = desc->ctx; system->binding_ctx = desc->binding_ctx; system->ctx_free = desc->ctx_free; system->binding_ctx_free = desc->binding_ctx_free; system->tick_source = desc->tick_source; system->multi_threaded = desc->multi_threaded; system->no_readonly = desc->no_readonly; if (desc->interval != 0 || desc->rate != 0 || desc->tick_source != 0) { #ifdef FLECS_TIMER if (desc->interval != 0) { ecs_set_interval(world, entity, desc->interval); } if (desc->rate) { ecs_set_rate(world, entity, desc->rate, desc->tick_source); } else if (desc->tick_source) { ecs_set_tick_source(world, entity, desc->tick_source); } #else ecs_abort(ECS_UNSUPPORTED, "timer module not available"); #endif } if (ecs_get_name(world, entity)) { ecs_trace("#[green]system#[reset] %s created", ecs_get_name(world, entity)); } ecs_defer_end(world); } else { ecs_system_t *system = ecs_poly(poly->poly, ecs_system_t); if (desc->run) { system->run = desc->run; } if (desc->callback) { system->action = desc->callback; } if (desc->ctx) { system->ctx = desc->ctx; } if (desc->binding_ctx) { system->binding_ctx = desc->binding_ctx; } if (desc->query.filter.instanced) { ECS_BIT_SET(system->query->filter.flags, EcsFilterIsInstanced); } if (desc->multi_threaded) { system->multi_threaded = desc->multi_threaded; } if (desc->no_readonly) { system->no_readonly = desc->no_readonly; } } ecs_poly_modified(world, entity, ecs_system_t); return entity; error: return 0; } void FlecsSystemImport( ecs_world_t *world) { ECS_MODULE(world, FlecsSystem); ecs_set_name_prefix(world, "Ecs"); flecs_bootstrap_tag(world, EcsSystem); flecs_bootstrap_component(world, EcsTickSource); /* Make sure to never inherit system component. This makes sure that any * term created for the System component will default to 'self' traversal, * which improves efficiency of the query. */ ecs_add_id(world, EcsSystem, EcsDontInherit); } #endif /** * @file json/json.c * @brief JSON serializer utilities. */ /** * @file json/json.h * @brief Internal functions for JSON addon. */ #ifdef FLECS_JSON /* Deserialize from JSON */ typedef enum ecs_json_token_t { JsonObjectOpen, JsonObjectClose, JsonArrayOpen, JsonArrayClose, JsonColon, JsonComma, JsonNumber, JsonString, JsonTrue, JsonFalse, JsonNull, JsonLargeString, JsonInvalid } ecs_json_token_t; const char* flecs_json_parse( const char *json, ecs_json_token_t *token_kind, char *token); const char* flecs_json_parse_large_string( const char *json, ecs_strbuf_t *buf); const char* flecs_json_expect( const char *json, ecs_json_token_t token_kind, char *token, const ecs_from_json_desc_t *desc); const char* flecs_json_expect_member( const char *json, char *token, const ecs_from_json_desc_t *desc); const char* flecs_json_expect_member_name( const char *json, char *token, const char *member_name, const ecs_from_json_desc_t *desc); const char* flecs_json_skip_object( const char *json, char *token, const ecs_from_json_desc_t *desc); const char* flecs_json_skip_array( const char *json, char *token, const ecs_from_json_desc_t *desc); /* Serialize to JSON */ void flecs_json_next( ecs_strbuf_t *buf); void flecs_json_number( ecs_strbuf_t *buf, double value); void flecs_json_true( ecs_strbuf_t *buf); void flecs_json_false( ecs_strbuf_t *buf); void flecs_json_bool( ecs_strbuf_t *buf, bool value); void flecs_json_array_push( ecs_strbuf_t *buf); void flecs_json_array_pop( ecs_strbuf_t *buf); void flecs_json_object_push( ecs_strbuf_t *buf); void flecs_json_object_pop( ecs_strbuf_t *buf); void flecs_json_string( ecs_strbuf_t *buf, const char *value); void flecs_json_string_escape( ecs_strbuf_t *buf, const char *value); void flecs_json_member( ecs_strbuf_t *buf, const char *name); void flecs_json_membern( ecs_strbuf_t *buf, const char *name, int32_t name_len); #define flecs_json_memberl(buf, name)\ flecs_json_membern(buf, name, sizeof(name) - 1) void flecs_json_path( ecs_strbuf_t *buf, const ecs_world_t *world, ecs_entity_t e); void flecs_json_label( ecs_strbuf_t *buf, const ecs_world_t *world, ecs_entity_t e); void flecs_json_color( ecs_strbuf_t *buf, const ecs_world_t *world, ecs_entity_t e); void flecs_json_id( ecs_strbuf_t *buf, const ecs_world_t *world, ecs_id_t id); ecs_primitive_kind_t flecs_json_op_to_primitive_kind( ecs_meta_type_op_kind_t kind); #endif #include #ifdef FLECS_JSON static const char* flecs_json_token_str( ecs_json_token_t token_kind) { switch(token_kind) { case JsonObjectOpen: return "{"; case JsonObjectClose: return "}"; case JsonArrayOpen: return "["; case JsonArrayClose: return "]"; case JsonColon: return ":"; case JsonComma: return ","; case JsonNumber: return "number"; case JsonString: return "string"; case JsonTrue: return "true"; case JsonFalse: return "false"; case JsonNull: return "null"; case JsonInvalid: return "invalid"; default: ecs_abort(ECS_INTERNAL_ERROR, NULL); } return "invalid"; } const char* flecs_json_parse( const char *json, ecs_json_token_t *token_kind, char *token) { json = ecs_parse_ws_eol(json); char ch = json[0]; if (ch == '{') { token_kind[0] = JsonObjectOpen; return json + 1; } else if (ch == '}') { token_kind[0] = JsonObjectClose; return json + 1; } else if (ch == '[') { token_kind[0] = JsonArrayOpen; return json + 1; } else if (ch == ']') { token_kind[0] = JsonArrayClose; return json + 1; } else if (ch == ':') { token_kind[0] = JsonColon; return json + 1; } else if (ch == ',') { token_kind[0] = JsonComma; return json + 1; } else if (ch == '"') { const char *start = json; char *token_ptr = token; json ++; for (; (ch = json[0]); ) { if (ch == '"') { json ++; token_ptr[0] = '\0'; break; } if (token_ptr - token >= ECS_MAX_TOKEN_SIZE) { /* Token doesn't fit in buffer, signal to app to try again with * dynamic buffer. */ token_kind[0] = JsonLargeString; return start; } json = ecs_chrparse(json, token_ptr ++); } if (!ch) { token_kind[0] = JsonInvalid; return NULL; } else { token_kind[0] = JsonString; return json; } } else if (isdigit(ch) || (ch == '-')) { token_kind[0] = JsonNumber; return ecs_parse_digit(json, token); } else if (isalpha(ch)) { if (!ecs_os_strncmp(json, "null", 4)) { token_kind[0] = JsonNull; json += 4; } else if (!ecs_os_strncmp(json, "true", 4)) { token_kind[0] = JsonTrue; json += 4; } else if (!ecs_os_strncmp(json, "false", 5)) { token_kind[0] = JsonFalse; json += 5; } if (isalpha(json[0]) || isdigit(json[0])) { token_kind[0] = JsonInvalid; return NULL; } return json; } else { token_kind[0] = JsonInvalid; return NULL; } } const char* flecs_json_parse_large_string( const char *json, ecs_strbuf_t *buf) { if (json[0] != '"') { return NULL; /* can only parse strings */ } char ch, ch_out; json ++; for (; (ch = json[0]); ) { if (ch == '"') { json ++; break; } json = ecs_chrparse(json, &ch_out); ecs_strbuf_appendch(buf, ch_out); } if (!ch) { return NULL; } else { return json; } } const char* flecs_json_expect( const char *json, ecs_json_token_t token_kind, char *token, const ecs_from_json_desc_t *desc) { ecs_json_token_t kind = 0; json = flecs_json_parse(json, &kind, token); if (kind == JsonInvalid) { ecs_parser_error(desc->name, desc->expr, json - desc->expr, "invalid json"); return NULL; } else if (kind != token_kind) { ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expected %s", flecs_json_token_str(token_kind)); return NULL; } return json; } const char* flecs_json_expect_member( const char *json, char *token, const ecs_from_json_desc_t *desc) { json = flecs_json_expect(json, JsonString, token, desc); if (!json) { return NULL; } json = flecs_json_expect(json, JsonColon, token, desc); if (!json) { return NULL; } return json; } const char* flecs_json_expect_member_name( const char *json, char *token, const char *member_name, const ecs_from_json_desc_t *desc) { json = flecs_json_expect_member(json, token, desc); if (!json) { return NULL; } if (ecs_os_strcmp(token, member_name)) { ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expected member '%s'", member_name); return NULL; } return json; } const char* flecs_json_skip_object( const char *json, char *token, const ecs_from_json_desc_t *desc) { ecs_json_token_t token_kind = 0; while ((json = flecs_json_parse(json, &token_kind, token))) { if (token_kind == JsonObjectOpen) { json = flecs_json_skip_object(json, token, desc); } else if (token_kind == JsonArrayOpen) { json = flecs_json_skip_array(json, token, desc); } else if (token_kind == JsonObjectClose) { return json; } else if (token_kind == JsonArrayClose) { ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expected }"); return NULL; } } ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expected }"); return NULL; } const char* flecs_json_skip_array( const char *json, char *token, const ecs_from_json_desc_t *desc) { ecs_json_token_t token_kind = 0; while ((json = flecs_json_parse(json, &token_kind, token))) { if (token_kind == JsonObjectOpen) { json = flecs_json_skip_object(json, token, desc); } else if (token_kind == JsonArrayOpen) { json = flecs_json_skip_array(json, token, desc); } else if (token_kind == JsonObjectClose) { ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expected ]"); return NULL; } else if (token_kind == JsonArrayClose) { return json; } } ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expected ]"); return NULL; } void flecs_json_next( ecs_strbuf_t *buf) { ecs_strbuf_list_next(buf); } void flecs_json_number( ecs_strbuf_t *buf, double value) { ecs_strbuf_appendflt(buf, value, '"'); } void flecs_json_true( ecs_strbuf_t *buf) { ecs_strbuf_appendlit(buf, "true"); } void flecs_json_false( ecs_strbuf_t *buf) { ecs_strbuf_appendlit(buf, "false"); } void flecs_json_bool( ecs_strbuf_t *buf, bool value) { if (value) { flecs_json_true(buf); } else { flecs_json_false(buf); } } void flecs_json_array_push( ecs_strbuf_t *buf) { ecs_strbuf_list_push(buf, "[", ", "); } void flecs_json_array_pop( ecs_strbuf_t *buf) { ecs_strbuf_list_pop(buf, "]"); } void flecs_json_object_push( ecs_strbuf_t *buf) { ecs_strbuf_list_push(buf, "{", ", "); } void flecs_json_object_pop( ecs_strbuf_t *buf) { ecs_strbuf_list_pop(buf, "}"); } void flecs_json_string( ecs_strbuf_t *buf, const char *value) { ecs_strbuf_appendch(buf, '"'); ecs_strbuf_appendstr(buf, value); ecs_strbuf_appendch(buf, '"'); } void flecs_json_string_escape( ecs_strbuf_t *buf, const char *value) { ecs_size_t length = ecs_stresc(NULL, 0, '"', value); if (length == ecs_os_strlen(value)) { ecs_strbuf_appendch(buf, '"'); ecs_strbuf_appendstrn(buf, value, length); ecs_strbuf_appendch(buf, '"'); } else { char *out = ecs_os_malloc(length + 3); ecs_stresc(out + 1, length, '"', value); out[0] = '"'; out[length + 1] = '"'; out[length + 2] = '\0'; ecs_strbuf_appendstr_zerocpy(buf, out); } } void flecs_json_member( ecs_strbuf_t *buf, const char *name) { flecs_json_membern(buf, name, ecs_os_strlen(name)); } void flecs_json_membern( ecs_strbuf_t *buf, const char *name, int32_t name_len) { ecs_strbuf_list_appendch(buf, '"'); ecs_strbuf_appendstrn(buf, name, name_len); ecs_strbuf_appendlit(buf, "\":"); } void flecs_json_path( ecs_strbuf_t *buf, const ecs_world_t *world, ecs_entity_t e) { ecs_strbuf_appendch(buf, '"'); ecs_get_path_w_sep_buf(world, 0, e, ".", "", buf); ecs_strbuf_appendch(buf, '"'); } void flecs_json_label( ecs_strbuf_t *buf, const ecs_world_t *world, ecs_entity_t e) { const char *lbl = NULL; #ifdef FLECS_DOC lbl = ecs_doc_get_name(world, e); #else lbl = ecs_get_name(world, e); #endif if (lbl) { ecs_strbuf_appendch(buf, '"'); ecs_strbuf_appendstr(buf, lbl); ecs_strbuf_appendch(buf, '"'); } else { ecs_strbuf_appendch(buf, '0'); } } void flecs_json_color( ecs_strbuf_t *buf, const ecs_world_t *world, ecs_entity_t e) { (void)world; (void)e; const char *color = NULL; #ifdef FLECS_DOC color = ecs_doc_get_color(world, e); #endif if (color) { ecs_strbuf_appendch(buf, '"'); ecs_strbuf_appendstr(buf, color); ecs_strbuf_appendch(buf, '"'); } else { ecs_strbuf_appendch(buf, '0'); } } void flecs_json_id( ecs_strbuf_t *buf, const ecs_world_t *world, ecs_id_t id) { ecs_strbuf_appendch(buf, '['); if (ECS_IS_PAIR(id)) { ecs_entity_t first = ECS_PAIR_FIRST(id); ecs_entity_t second = ECS_PAIR_SECOND(id); ecs_strbuf_appendch(buf, '"'); ecs_get_path_w_sep_buf(world, 0, first, ".", "", buf); ecs_strbuf_appendch(buf, '"'); ecs_strbuf_appendch(buf, ','); ecs_strbuf_appendch(buf, '"'); ecs_get_path_w_sep_buf(world, 0, second, ".", "", buf); ecs_strbuf_appendch(buf, '"'); } else { ecs_strbuf_appendch(buf, '"'); ecs_get_path_w_sep_buf(world, 0, id & ECS_COMPONENT_MASK, ".", "", buf); ecs_strbuf_appendch(buf, '"'); } ecs_strbuf_appendch(buf, ']'); } ecs_primitive_kind_t flecs_json_op_to_primitive_kind( ecs_meta_type_op_kind_t kind) { return kind - EcsOpPrimitive; } #endif /** * @file json/serialize.c * @brief Serialize (component) values to JSON strings. */ #ifdef FLECS_JSON /* Cached id records during serialization */ typedef struct ecs_json_ser_idr_t { ecs_id_record_t *idr_doc_name; ecs_id_record_t *idr_doc_color; } ecs_json_ser_idr_t; static int json_ser_type( const ecs_world_t *world, const ecs_vec_t *ser, const void *base, ecs_strbuf_t *str); static int json_ser_type_ops( const ecs_world_t *world, ecs_meta_type_op_t *ops, int32_t op_count, const void *base, ecs_strbuf_t *str, int32_t in_array); static int json_ser_type_op( const ecs_world_t *world, ecs_meta_type_op_t *op, const void *base, ecs_strbuf_t *str); /* Serialize enumeration */ static int json_ser_enum( const ecs_world_t *world, ecs_meta_type_op_t *op, const void *base, ecs_strbuf_t *str) { const EcsEnum *enum_type = ecs_get(world, op->type, EcsEnum); ecs_check(enum_type != NULL, ECS_INVALID_PARAMETER, NULL); int32_t value = *(int32_t*)base; /* Enumeration constants are stored in a map that is keyed on the * enumeration value. */ ecs_enum_constant_t *constant = ecs_map_get_deref(&enum_type->constants, ecs_enum_constant_t, (ecs_map_key_t)value); if (!constant) { goto error; } ecs_strbuf_appendch(str, '"'); ecs_strbuf_appendstr(str, ecs_get_name(world, constant->constant)); ecs_strbuf_appendch(str, '"'); return 0; error: return -1; } /* Serialize bitmask */ static int json_ser_bitmask( const ecs_world_t *world, ecs_meta_type_op_t *op, const void *ptr, ecs_strbuf_t *str) { const EcsBitmask *bitmask_type = ecs_get(world, op->type, EcsBitmask); ecs_check(bitmask_type != NULL, ECS_INVALID_PARAMETER, NULL); uint32_t value = *(uint32_t*)ptr; if (!value) { ecs_strbuf_appendch(str, '0'); return 0; } ecs_strbuf_list_push(str, "\"", "|"); /* Multiple flags can be set at a given time. Iterate through all the flags * and append the ones that are set. */ ecs_map_iter_t it = ecs_map_iter(&bitmask_type->constants); while (ecs_map_next(&it)) { ecs_bitmask_constant_t *constant = ecs_map_ptr(&it); ecs_map_key_t key = ecs_map_key(&it); if ((value & key) == key) { ecs_strbuf_list_appendstr(str, ecs_get_name(world, constant->constant)); value -= (uint32_t)key; } } if (value != 0) { /* All bits must have been matched by a constant */ goto error; } ecs_strbuf_list_pop(str, "\""); return 0; error: return -1; } /* Serialize elements of a contiguous array */ static int json_ser_elements( const ecs_world_t *world, ecs_meta_type_op_t *ops, int32_t op_count, const void *base, int32_t elem_count, int32_t elem_size, ecs_strbuf_t *str, bool is_array) { flecs_json_array_push(str); const void *ptr = base; int i; for (i = 0; i < elem_count; i ++) { ecs_strbuf_list_next(str); if (json_ser_type_ops(world, ops, op_count, ptr, str, is_array)) { return -1; } ptr = ECS_OFFSET(ptr, elem_size); } flecs_json_array_pop(str); return 0; } static int json_ser_type_elements( const ecs_world_t *world, ecs_entity_t type, const void *base, int32_t elem_count, ecs_strbuf_t *str, bool is_array) { const EcsMetaTypeSerialized *ser = ecs_get( world, type, EcsMetaTypeSerialized); ecs_assert(ser != NULL, ECS_INTERNAL_ERROR, NULL); const EcsComponent *comp = ecs_get(world, type, EcsComponent); ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL); ecs_meta_type_op_t *ops = ecs_vec_first_t(&ser->ops, ecs_meta_type_op_t); int32_t op_count = ecs_vec_count(&ser->ops); return json_ser_elements( world, ops, op_count, base, elem_count, comp->size, str, is_array); } /* Serialize array */ static int json_ser_array( const ecs_world_t *world, ecs_meta_type_op_t *op, const void *ptr, ecs_strbuf_t *str) { const EcsArray *a = ecs_get(world, op->type, EcsArray); ecs_assert(a != NULL, ECS_INTERNAL_ERROR, NULL); return json_ser_type_elements( world, a->type, ptr, a->count, str, true); } /* Serialize vector */ static int json_ser_vector( const ecs_world_t *world, ecs_meta_type_op_t *op, const void *base, ecs_strbuf_t *str) { const ecs_vec_t *value = base; const EcsVector *v = ecs_get(world, op->type, EcsVector); ecs_assert(v != NULL, ECS_INTERNAL_ERROR, NULL); int32_t count = ecs_vec_count(value); void *array = ecs_vec_first(value); /* Serialize contiguous buffer of vector */ return json_ser_type_elements(world, v->type, array, count, str, false); } typedef struct json_serializer_ctx_t { ecs_strbuf_t *str; bool is_collection; bool is_struct; } json_serializer_ctx_t; static int json_ser_custom_value( const ecs_serializer_t *ser, ecs_entity_t type, const void *value) { json_serializer_ctx_t *json_ser = ser->ctx; if (json_ser->is_collection) { ecs_strbuf_list_next(json_ser->str); } return ecs_ptr_to_json_buf(ser->world, type, value, json_ser->str); } static int json_ser_custom_member( const ecs_serializer_t *ser, const char *name) { json_serializer_ctx_t *json_ser = ser->ctx; if (!json_ser->is_struct) { ecs_err("serializer::member can only be called for structs"); return -1; } flecs_json_member(json_ser->str, name); return 0; } static int json_ser_custom_type( const ecs_world_t *world, ecs_meta_type_op_t *op, const void *base, ecs_strbuf_t *str) { const EcsOpaque *ct = ecs_get(world, op->type, EcsOpaque); ecs_assert(ct != NULL, ECS_INVALID_OPERATION, NULL); ecs_assert(ct->as_type != 0, ECS_INVALID_OPERATION, NULL); ecs_assert(ct->serialize != NULL, ECS_INVALID_OPERATION, ecs_get_name(world, op->type)); const EcsMetaType *pt = ecs_get(world, ct->as_type, EcsMetaType); ecs_assert(pt != NULL, ECS_INVALID_OPERATION, NULL); ecs_type_kind_t kind = pt->kind; bool is_collection = false; bool is_struct = false; if (kind == EcsStructType) { flecs_json_object_push(str); is_struct = true; } else if (kind == EcsArrayType || kind == EcsVectorType) { flecs_json_array_push(str); is_collection = true; } json_serializer_ctx_t json_ser = { .str = str, .is_struct = is_struct, .is_collection = is_collection }; ecs_serializer_t ser = { .world = world, .value = json_ser_custom_value, .member = json_ser_custom_member, .ctx = &json_ser }; if (ct->serialize(&ser, base)) { return -1; } if (kind == EcsStructType) { flecs_json_object_pop(str); } else if (kind == EcsArrayType || kind == EcsVectorType) { flecs_json_array_pop(str); } return 0; } /* Forward serialization to the different type kinds */ static int json_ser_type_op( const ecs_world_t *world, ecs_meta_type_op_t *op, const void *ptr, ecs_strbuf_t *str) { switch(op->kind) { case EcsOpPush: case EcsOpPop: /* Should not be parsed as single op */ ecs_throw(ECS_INVALID_PARAMETER, NULL); break; case EcsOpF32: ecs_strbuf_appendflt(str, (ecs_f64_t)*(ecs_f32_t*)ECS_OFFSET(ptr, op->offset), '"'); break; case EcsOpF64: ecs_strbuf_appendflt(str, *(ecs_f64_t*)ECS_OFFSET(ptr, op->offset), '"'); break; case EcsOpEnum: if (json_ser_enum(world, op, ECS_OFFSET(ptr, op->offset), str)) { goto error; } break; case EcsOpBitmask: if (json_ser_bitmask(world, op, ECS_OFFSET(ptr, op->offset), str)) { goto error; } break; case EcsOpArray: if (json_ser_array(world, op, ECS_OFFSET(ptr, op->offset), str)) { goto error; } break; case EcsOpVector: if (json_ser_vector(world, op, ECS_OFFSET(ptr, op->offset), str)) { goto error; } break; case EcsOpOpaque: if (json_ser_custom_type(world, op, ECS_OFFSET(ptr, op->offset), str)) { goto error; } break; case EcsOpEntity: { ecs_entity_t e = *(ecs_entity_t*)ECS_OFFSET(ptr, op->offset); if (!e) { ecs_strbuf_appendch(str, '0'); } else { flecs_json_path(str, world, e); } break; } default: if (ecs_primitive_to_expr_buf(world, flecs_json_op_to_primitive_kind(op->kind), ECS_OFFSET(ptr, op->offset), str)) { /* Unknown operation */ ecs_throw(ECS_INTERNAL_ERROR, NULL); return -1; } break; } return 0; error: return -1; } /* Iterate over a slice of the type ops array */ static int json_ser_type_ops( const ecs_world_t *world, ecs_meta_type_op_t *ops, int32_t op_count, const void *base, ecs_strbuf_t *str, int32_t in_array) { for (int i = 0; i < op_count; i ++) { ecs_meta_type_op_t *op = &ops[i]; if (in_array <= 0) { if (op->name) { flecs_json_member(str, op->name); } int32_t elem_count = op->count; if (elem_count > 1) { /* Serialize inline array */ if (json_ser_elements(world, op, op->op_count, base, elem_count, op->size, str, true)) { return -1; } i += op->op_count - 1; continue; } } switch(op->kind) { case EcsOpPush: flecs_json_object_push(str); in_array --; break; case EcsOpPop: flecs_json_object_pop(str); in_array ++; break; default: if (json_ser_type_op(world, op, base, str)) { goto error; } break; } } return 0; error: return -1; } /* Iterate over the type ops of a type */ static int json_ser_type( const ecs_world_t *world, const ecs_vec_t *v_ops, const void *base, ecs_strbuf_t *str) { ecs_meta_type_op_t *ops = ecs_vec_first_t(v_ops, ecs_meta_type_op_t); int32_t count = ecs_vec_count(v_ops); return json_ser_type_ops(world, ops, count, base, str, 0); } static int array_to_json_buf_w_type_data( const ecs_world_t *world, const void *ptr, int32_t count, ecs_strbuf_t *buf, const EcsComponent *comp, const EcsMetaTypeSerialized *ser) { if (count) { ecs_size_t size = comp->size; flecs_json_array_push(buf); do { ecs_strbuf_list_next(buf); if (json_ser_type(world, &ser->ops, ptr, buf)) { return -1; } ptr = ECS_OFFSET(ptr, size); } while (-- count); flecs_json_array_pop(buf); } else { if (json_ser_type(world, &ser->ops, ptr, buf)) { return -1; } } return 0; } int ecs_array_to_json_buf( const ecs_world_t *world, ecs_entity_t type, const void *ptr, int32_t count, ecs_strbuf_t *buf) { const EcsComponent *comp = ecs_get(world, type, EcsComponent); if (!comp) { char *path = ecs_get_fullpath(world, type); ecs_err("cannot serialize to JSON, '%s' is not a component", path); ecs_os_free(path); return -1; } const EcsMetaTypeSerialized *ser = ecs_get( world, type, EcsMetaTypeSerialized); if (!ser) { char *path = ecs_get_fullpath(world, type); ecs_err("cannot serialize to JSON, '%s' has no reflection data", path); ecs_os_free(path); return -1; } return array_to_json_buf_w_type_data(world, ptr, count, buf, comp, ser); } char* ecs_array_to_json( const ecs_world_t *world, ecs_entity_t type, const void* ptr, int32_t count) { ecs_strbuf_t str = ECS_STRBUF_INIT; if (ecs_array_to_json_buf(world, type, ptr, count, &str) != 0) { ecs_strbuf_reset(&str); return NULL; } return ecs_strbuf_get(&str); } int ecs_ptr_to_json_buf( const ecs_world_t *world, ecs_entity_t type, const void *ptr, ecs_strbuf_t *buf) { return ecs_array_to_json_buf(world, type, ptr, 0, buf); } char* ecs_ptr_to_json( const ecs_world_t *world, ecs_entity_t type, const void* ptr) { return ecs_array_to_json(world, type, ptr, 0); } static bool flecs_json_skip_id( const ecs_world_t *world, ecs_id_t id, const ecs_entity_to_json_desc_t *desc, ecs_entity_t ent, ecs_entity_t inst, ecs_entity_t *pred_out, ecs_entity_t *obj_out, ecs_entity_t *role_out, bool *hidden_out) { bool is_base = ent != inst; ecs_entity_t pred = 0, obj = 0, role = 0; bool hidden = false; if (ECS_HAS_ID_FLAG(id, PAIR)) { pred = ecs_pair_first(world, id); obj = ecs_pair_second(world, id); } else { pred = id & ECS_COMPONENT_MASK; if (id & ECS_ID_FLAGS_MASK) { role = id & ECS_ID_FLAGS_MASK; } } if (!desc || !desc->serialize_meta_ids) { if (pred == EcsIsA || pred == EcsChildOf || pred == ecs_id(EcsIdentifier)) { return true; } #ifdef FLECS_DOC if (pred == ecs_id(EcsDocDescription)) { return true; } #endif } if (is_base) { if (ecs_has_id(world, pred, EcsDontInherit)) { return true; } } if (!desc || !desc->serialize_private) { if (ecs_has_id(world, pred, EcsPrivate)) { return true; } } if (is_base) { if (ecs_get_target_for_id(world, inst, EcsIsA, id) != ent) { hidden = true; } } if (hidden && (!desc || !desc->serialize_hidden)) { return true; } *pred_out = pred; *obj_out = obj; *role_out = role; if (hidden_out) *hidden_out = hidden; return false; } static int flecs_json_append_type_labels( const ecs_world_t *world, ecs_strbuf_t *buf, const ecs_id_t *ids, int32_t count, ecs_entity_t ent, ecs_entity_t inst, const ecs_entity_to_json_desc_t *desc) { (void)world; (void)buf; (void)ids; (void)count; (void)ent; (void)inst; (void)desc; #ifdef FLECS_DOC if (!desc || !desc->serialize_id_labels) { return 0; } flecs_json_memberl(buf, "id_labels"); flecs_json_array_push(buf); int32_t i; for (i = 0; i < count; i ++) { ecs_entity_t pred = 0, obj = 0, role = 0; if (flecs_json_skip_id(world, ids[i], desc, ent, inst, &pred, &obj, &role, 0)) { continue; } if (obj && (pred == EcsUnion)) { pred = obj; obj = ecs_get_target(world, ent, pred, 0); if (!ecs_is_alive(world, obj)) { /* Union relationships aren't automatically cleaned up, so they * can contain invalid entity ids. Don't serialize value until * relationship is valid again. */ continue; } } if (desc && desc->serialize_id_labels) { flecs_json_next(buf); flecs_json_array_push(buf); flecs_json_next(buf); flecs_json_label(buf, world, pred); if (obj) { flecs_json_next(buf); flecs_json_label(buf, world, obj); } flecs_json_array_pop(buf); } } flecs_json_array_pop(buf); #endif return 0; } static int flecs_json_append_type_values( const ecs_world_t *world, ecs_strbuf_t *buf, const ecs_id_t *ids, int32_t count, ecs_entity_t ent, ecs_entity_t inst, const ecs_entity_to_json_desc_t *desc) { if (!desc || !desc->serialize_values) { return 0; } flecs_json_memberl(buf, "values"); flecs_json_array_push(buf); int32_t i; for (i = 0; i < count; i ++) { bool hidden; ecs_entity_t pred = 0, obj = 0, role = 0; ecs_id_t id = ids[i]; if (flecs_json_skip_id(world, id, desc, ent, inst, &pred, &obj, &role, &hidden)) { continue; } if (!hidden) { bool serialized = false; ecs_entity_t typeid = ecs_get_typeid(world, id); if (typeid) { const EcsMetaTypeSerialized *ser = ecs_get( world, typeid, EcsMetaTypeSerialized); if (ser) { const void *ptr = ecs_get_id(world, ent, id); ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); flecs_json_next(buf); if (json_ser_type(world, &ser->ops, ptr, buf) != 0) { /* Entity contains invalid value */ return -1; } serialized = true; } } if (!serialized) { flecs_json_next(buf); flecs_json_number(buf, 0); } } else { if (!desc || desc->serialize_hidden) { flecs_json_next(buf); flecs_json_number(buf, 0); } } } flecs_json_array_pop(buf); return 0; } static int flecs_json_append_type_info( const ecs_world_t *world, ecs_strbuf_t *buf, const ecs_id_t *ids, int32_t count, ecs_entity_t ent, ecs_entity_t inst, const ecs_entity_to_json_desc_t *desc) { if (!desc || !desc->serialize_type_info) { return 0; } flecs_json_memberl(buf, "type_info"); flecs_json_array_push(buf); int32_t i; for (i = 0; i < count; i ++) { bool hidden; ecs_entity_t pred = 0, obj = 0, role = 0; ecs_id_t id = ids[i]; if (flecs_json_skip_id(world, id, desc, ent, inst, &pred, &obj, &role, &hidden)) { continue; } if (!hidden) { ecs_entity_t typeid = ecs_get_typeid(world, id); if (typeid) { flecs_json_next(buf); if (ecs_type_info_to_json_buf(world, typeid, buf) != 0) { return -1; } } else { flecs_json_next(buf); flecs_json_number(buf, 0); } } else { if (!desc || desc->serialize_hidden) { flecs_json_next(buf); flecs_json_number(buf, 0); } } } flecs_json_array_pop(buf); return 0; } static int flecs_json_append_type_hidden( const ecs_world_t *world, ecs_strbuf_t *buf, const ecs_id_t *ids, int32_t count, ecs_entity_t ent, ecs_entity_t inst, const ecs_entity_to_json_desc_t *desc) { if (!desc || !desc->serialize_hidden) { return 0; } if (ent == inst) { return 0; /* if this is not a base, components are never hidden */ } flecs_json_memberl(buf, "hidden"); flecs_json_array_push(buf); int32_t i; for (i = 0; i < count; i ++) { bool hidden; ecs_entity_t pred = 0, obj = 0, role = 0; ecs_id_t id = ids[i]; if (flecs_json_skip_id(world, id, desc, ent, inst, &pred, &obj, &role, &hidden)) { continue; } flecs_json_next(buf); flecs_json_bool(buf, hidden); } flecs_json_array_pop(buf); return 0; } static int flecs_json_append_type( const ecs_world_t *world, ecs_strbuf_t *buf, ecs_entity_t ent, ecs_entity_t inst, const ecs_entity_to_json_desc_t *desc) { const ecs_id_t *ids = NULL; int32_t i, count = 0; const ecs_type_t *type = ecs_get_type(world, ent); if (type) { ids = type->array; count = type->count; } flecs_json_memberl(buf, "ids"); flecs_json_array_push(buf); for (i = 0; i < count; i ++) { ecs_entity_t pred = 0, obj = 0, role = 0; if (flecs_json_skip_id(world, ids[i], desc, ent, inst, &pred, &obj, &role, 0)) { continue; } if (obj && (pred == EcsUnion)) { pred = obj; obj = ecs_get_target(world, ent, pred, 0); if (!ecs_is_alive(world, obj)) { /* Union relationships aren't automatically cleaned up, so they * can contain invalid entity ids. Don't serialize value until * relationship is valid again. */ continue; } } flecs_json_next(buf); flecs_json_array_push(buf); flecs_json_next(buf); flecs_json_path(buf, world, pred); if (obj || role) { flecs_json_next(buf); if (obj) { flecs_json_path(buf, world, obj); } else { flecs_json_number(buf, 0); } if (role) { flecs_json_next(buf); flecs_json_string(buf, ecs_id_flag_str(role)); } } flecs_json_array_pop(buf); } flecs_json_array_pop(buf); if (flecs_json_append_type_labels(world, buf, ids, count, ent, inst, desc)) { return -1; } if (flecs_json_append_type_values(world, buf, ids, count, ent, inst, desc)) { return -1; } if (flecs_json_append_type_info(world, buf, ids, count, ent, inst, desc)) { return -1; } if (flecs_json_append_type_hidden(world, buf, ids, count, ent, inst, desc)) { return -1; } return 0; } static int flecs_json_append_base( const ecs_world_t *world, ecs_strbuf_t *buf, ecs_entity_t ent, ecs_entity_t inst, const ecs_entity_to_json_desc_t *desc) { const ecs_type_t *type = ecs_get_type(world, ent); ecs_id_t *ids = NULL; int32_t i, count = 0; if (type) { ids = type->array; count = type->count; } for (i = 0; i < count; i ++) { ecs_id_t id = ids[i]; if (ECS_HAS_RELATION(id, EcsIsA)) { if (flecs_json_append_base(world, buf, ecs_pair_second(world, id), inst, desc)) { return -1; } } } ecs_strbuf_list_next(buf); flecs_json_object_push(buf); flecs_json_memberl(buf, "path"); flecs_json_path(buf, world, ent); if (flecs_json_append_type(world, buf, ent, inst, desc)) { return -1; } flecs_json_object_pop(buf); return 0; } int ecs_entity_to_json_buf( const ecs_world_t *world, ecs_entity_t entity, ecs_strbuf_t *buf, const ecs_entity_to_json_desc_t *desc) { if (!entity || !ecs_is_valid(world, entity)) { return -1; } flecs_json_object_push(buf); if (!desc || desc->serialize_path) { flecs_json_memberl(buf, "path"); flecs_json_path(buf, world, entity); } #ifdef FLECS_DOC if (desc && desc->serialize_label) { flecs_json_memberl(buf, "label"); const char *doc_name = ecs_doc_get_name(world, entity); if (doc_name) { flecs_json_string_escape(buf, doc_name); } else { char num_buf[20]; ecs_os_sprintf(num_buf, "%u", (uint32_t)entity); flecs_json_string(buf, num_buf); } } if (desc && desc->serialize_brief) { const char *doc_brief = ecs_doc_get_brief(world, entity); if (doc_brief) { flecs_json_memberl(buf, "brief"); flecs_json_string_escape(buf, doc_brief); } } if (desc && desc->serialize_link) { const char *doc_link = ecs_doc_get_link(world, entity); if (doc_link) { flecs_json_memberl(buf, "link"); flecs_json_string_escape(buf, doc_link); } } if (desc && desc->serialize_color) { const char *doc_color = ecs_doc_get_color(world, entity); if (doc_color) { flecs_json_memberl(buf, "color"); flecs_json_string_escape(buf, doc_color); } } #endif const ecs_type_t *type = ecs_get_type(world, entity); ecs_id_t *ids = NULL; int32_t i, count = 0; if (type) { ids = type->array; count = type->count; } if (!desc || desc->serialize_base) { if (ecs_has_pair(world, entity, EcsIsA, EcsWildcard)) { flecs_json_memberl(buf, "is_a"); flecs_json_array_push(buf); for (i = 0; i < count; i ++) { ecs_id_t id = ids[i]; if (ECS_HAS_RELATION(id, EcsIsA)) { if (flecs_json_append_base( world, buf, ecs_pair_second(world, id), entity, desc)) { return -1; } } } flecs_json_array_pop(buf); } } if (flecs_json_append_type(world, buf, entity, entity, desc)) { goto error; } flecs_json_object_pop(buf); return 0; error: return -1; } char* ecs_entity_to_json( const ecs_world_t *world, ecs_entity_t entity, const ecs_entity_to_json_desc_t *desc) { ecs_strbuf_t buf = ECS_STRBUF_INIT; if (ecs_entity_to_json_buf(world, entity, &buf, desc) != 0) { ecs_strbuf_reset(&buf); return NULL; } return ecs_strbuf_get(&buf); } static bool flecs_json_skip_variable( const char *name) { if (!name || name[0] == '_' || !ecs_os_strcmp(name, "this")) { return true; } else { return false; } } static void flecs_json_serialize_id( const ecs_world_t *world, ecs_id_t id, ecs_strbuf_t *buf) { flecs_json_id(buf, world, id); } static void flecs_json_serialize_iter_ids( const ecs_world_t *world, const ecs_iter_t *it, ecs_strbuf_t *buf) { int32_t field_count = it->field_count; if (!field_count) { return; } flecs_json_memberl(buf, "ids"); flecs_json_array_push(buf); for (int i = 0; i < field_count; i ++) { flecs_json_next(buf); flecs_json_serialize_id(world, it->terms[i].id, buf); } flecs_json_array_pop(buf); } static void flecs_json_serialize_id_str( const ecs_world_t *world, ecs_id_t id, ecs_strbuf_t *buf) { ecs_strbuf_appendch(buf, '"'); if (ECS_IS_PAIR(id)) { ecs_entity_t first = ecs_pair_first(world, id); ecs_entity_t second = ecs_pair_first(world, id); ecs_strbuf_appendch(buf, '('); ecs_get_path_w_sep_buf(world, 0, first, ".", "", buf); ecs_strbuf_appendch(buf, ','); ecs_get_path_w_sep_buf(world, 0, second, ".", "", buf); ecs_strbuf_appendch(buf, ')'); } else { ecs_get_path_w_sep_buf( world, 0, id & ECS_COMPONENT_MASK, ".", "", buf); } ecs_strbuf_appendch(buf, '"'); } static void flecs_json_serialize_type_info( const ecs_world_t *world, const ecs_iter_t *it, ecs_strbuf_t *buf) { int32_t field_count = it->field_count; if (!field_count) { return; } if (it->flags & EcsIterNoData) { return; } flecs_json_memberl(buf, "type_info"); flecs_json_object_push(buf); for (int i = 0; i < field_count; i ++) { flecs_json_next(buf); ecs_entity_t typeid = 0; if (it->terms[i].inout != EcsInOutNone) { typeid = ecs_get_typeid(world, it->terms[i].id); } if (typeid) { flecs_json_serialize_id_str(world, typeid, buf); ecs_strbuf_appendch(buf, ':'); ecs_type_info_to_json_buf(world, typeid, buf); } else { flecs_json_serialize_id_str(world, it->terms[i].id, buf); ecs_strbuf_appendlit(buf, ":0"); } } flecs_json_object_pop(buf); } static void flecs_json_serialize_iter_variables(ecs_iter_t *it, ecs_strbuf_t *buf) { char **variable_names = it->variable_names; int32_t var_count = it->variable_count; int32_t actual_count = 0; for (int i = 0; i < var_count; i ++) { const char *var_name = variable_names[i]; if (flecs_json_skip_variable(var_name)) continue; if (!actual_count) { flecs_json_memberl(buf, "vars"); flecs_json_array_push(buf); actual_count ++; } ecs_strbuf_list_next(buf); flecs_json_string(buf, var_name); } if (actual_count) { flecs_json_array_pop(buf); } } static void flecs_json_serialize_iter_result_ids( const ecs_world_t *world, const ecs_iter_t *it, ecs_strbuf_t *buf) { flecs_json_memberl(buf, "ids"); flecs_json_array_push(buf); for (int i = 0; i < it->field_count; i ++) { flecs_json_next(buf); flecs_json_serialize_id(world, ecs_field_id(it, i + 1), buf); } flecs_json_array_pop(buf); } static void flecs_json_serialize_iter_result_table_type( const ecs_world_t *world, const ecs_iter_t *it, ecs_strbuf_t *buf) { if (!it->table) { return; } flecs_json_memberl(buf, "ids"); flecs_json_array_push(buf); ecs_type_t *type = &it->table->type; for (int i = 0; i < type->count; i ++) { flecs_json_next(buf); flecs_json_serialize_id(world, type->array[i], buf); } flecs_json_array_pop(buf); } static void flecs_json_serialize_iter_result_sources( const ecs_world_t *world, const ecs_iter_t *it, ecs_strbuf_t *buf) { flecs_json_memberl(buf, "sources"); flecs_json_array_push(buf); for (int i = 0; i < it->field_count; i ++) { flecs_json_next(buf); ecs_entity_t subj = it->sources[i]; if (subj) { flecs_json_path(buf, world, subj); } else { ecs_strbuf_appendch(buf, '0'); } } flecs_json_array_pop(buf); } static void flecs_json_serialize_iter_result_is_set( const ecs_iter_t *it, ecs_strbuf_t *buf) { if (!(it->flags & EcsIterHasCondSet)) { return; } flecs_json_memberl(buf, "is_set"); flecs_json_array_push(buf); for (int i = 0; i < it->field_count; i ++) { ecs_strbuf_list_next(buf); if (ecs_field_is_set(it, i + 1)) { flecs_json_true(buf); } else { flecs_json_false(buf); } } flecs_json_array_pop(buf); } static void flecs_json_serialize_iter_result_variables( const ecs_world_t *world, const ecs_iter_t *it, ecs_strbuf_t *buf) { char **variable_names = it->variable_names; ecs_var_t *variables = it->variables; int32_t var_count = it->variable_count; int32_t actual_count = 0; for (int i = 0; i < var_count; i ++) { const char *var_name = variable_names[i]; if (flecs_json_skip_variable(var_name)) continue; if (!actual_count) { flecs_json_memberl(buf, "vars"); flecs_json_array_push(buf); actual_count ++; } ecs_strbuf_list_next(buf); flecs_json_path(buf, world, variables[i].entity); } if (actual_count) { flecs_json_array_pop(buf); } } static void flecs_json_serialize_iter_result_variable_labels( const ecs_world_t *world, const ecs_iter_t *it, ecs_strbuf_t *buf) { char **variable_names = it->variable_names; ecs_var_t *variables = it->variables; int32_t var_count = it->variable_count; int32_t actual_count = 0; for (int i = 0; i < var_count; i ++) { const char *var_name = variable_names[i]; if (flecs_json_skip_variable(var_name)) continue; if (!actual_count) { flecs_json_memberl(buf, "var_labels"); flecs_json_array_push(buf); actual_count ++; } ecs_strbuf_list_next(buf); flecs_json_label(buf, world, variables[i].entity); } if (actual_count) { flecs_json_array_pop(buf); } } static void flecs_json_serialize_iter_result_variable_ids( const ecs_iter_t *it, ecs_strbuf_t *buf) { char **variable_names = it->variable_names; ecs_var_t *variables = it->variables; int32_t var_count = it->variable_count; int32_t actual_count = 0; for (int i = 0; i < var_count; i ++) { const char *var_name = variable_names[i]; if (flecs_json_skip_variable(var_name)) continue; if (!actual_count) { flecs_json_memberl(buf, "var_ids"); flecs_json_array_push(buf); actual_count ++; } ecs_strbuf_list_next(buf); flecs_json_number(buf, (double)variables[i].entity); } if (actual_count) { flecs_json_array_pop(buf); } } static bool flecs_json_serialize_iter_result_entity_names( const ecs_iter_t *it, ecs_strbuf_t *buf) { ecs_assert(it->count != 0, ECS_INTERNAL_ERROR, NULL); EcsIdentifier *names = ecs_table_get_id(it->world, it->table, ecs_pair(ecs_id(EcsIdentifier), EcsName), it->offset); if (!names) { return false; } int i; for (i = 0; i < it->count; i ++) { flecs_json_next(buf); flecs_json_string(buf, names[i].value); } return true; } static void flecs_json_serialize_iter_result_entity_ids( const ecs_iter_t *it, ecs_strbuf_t *buf) { if (!it->count) { return; } flecs_json_memberl(buf, "entity_ids"); flecs_json_array_push(buf); ecs_entity_t *entities = it->entities; int i, count = it->count; for (i = 0; i < count; i ++) { flecs_json_next(buf); flecs_json_number(buf, (double)(uint32_t)entities[i]); } flecs_json_array_pop(buf); } static void flecs_json_serialize_iter_result_parent( const ecs_world_t *world, const ecs_iter_t *it, ecs_strbuf_t *buf) { ecs_table_t *table = it->table; if (!(table->flags & EcsTableHasChildOf)) { return; } const ecs_table_record_t *tr = flecs_id_record_get_table( world->idr_childof_wildcard, it->table); if (tr == NULL) { return; } ecs_id_t id = table->type.array[tr->column]; ecs_entity_t parent = ecs_pair_second(world, id); char *path = ecs_get_fullpath(world, parent); flecs_json_memberl(buf, "parent"); flecs_json_string(buf, path); ecs_os_free(path); } static void flecs_json_serialize_iter_result_entities( const ecs_world_t *world, const ecs_iter_t *it, ecs_strbuf_t *buf) { if (!it->count) { return; } flecs_json_serialize_iter_result_parent(world, it, buf); flecs_json_memberl(buf, "entities"); flecs_json_array_push(buf); if (!flecs_json_serialize_iter_result_entity_names(it, buf)) { ecs_entity_t *entities = it->entities; int i, count = it->count; for (i = 0; i < count; i ++) { flecs_json_next(buf); flecs_json_number(buf, (double)(uint32_t)entities[i]); } } flecs_json_array_pop(buf); } static void flecs_json_serialize_iter_result_entity_labels( const ecs_iter_t *it, ecs_strbuf_t *buf, const ecs_json_ser_idr_t *ser_idr) { (void)buf; (void)ser_idr; if (!it->count) { return; } if (!ser_idr->idr_doc_name) { return; } #ifdef FLECS_DOC const ecs_table_record_t *tr = flecs_id_record_get_table( ser_idr->idr_doc_name, it->table); if (tr == NULL) { return; } EcsDocDescription *labels = ecs_table_get_column( it->table, tr->column, it->offset); ecs_assert(labels != NULL, ECS_INTERNAL_ERROR, NULL); flecs_json_memberl(buf, "entity_labels"); flecs_json_array_push(buf); int i; for (i = 0; i < it->count; i ++) { flecs_json_next(buf); flecs_json_string(buf, labels[i].value); } flecs_json_array_pop(buf); #endif } static void flecs_json_serialize_iter_result_colors( const ecs_iter_t *it, ecs_strbuf_t *buf, const ecs_json_ser_idr_t *ser_idr) { (void)buf; (void)ser_idr; if (!it->count) { return; } #ifdef FLECS_DOC if (!ser_idr->idr_doc_color) { return; } const ecs_table_record_t *tr = flecs_id_record_get_table( ser_idr->idr_doc_color, it->table); if (tr == NULL) { return; } EcsDocDescription *colors = ecs_table_get_column( it->table, tr->column, it->offset); ecs_assert(colors != NULL, ECS_INTERNAL_ERROR, NULL); flecs_json_memberl(buf, "colors"); flecs_json_array_push(buf); int i; for (i = 0; i < it->count; i ++) { flecs_json_next(buf); flecs_json_string(buf, colors[i].value); } flecs_json_array_pop(buf); #endif } static void flecs_json_serialize_iter_result_values( const ecs_world_t *world, const ecs_iter_t *it, ecs_strbuf_t *buf) { if (!it->ptrs || (it->flags & EcsIterNoData)) { return; } flecs_json_memberl(buf, "values"); flecs_json_array_push(buf); int32_t i, term_count = it->field_count; for (i = 0; i < term_count; i ++) { ecs_strbuf_list_next(buf); const void *ptr = NULL; if (it->ptrs) { ptr = it->ptrs[i]; } if (!ptr) { /* No data in column. Append 0 if this is not an optional term */ if (ecs_field_is_set(it, i + 1)) { ecs_strbuf_appendch(buf, '0'); continue; } } if (ecs_field_is_writeonly(it, i + 1)) { ecs_strbuf_appendch(buf, '0'); continue; } /* Get component id (can be different in case of pairs) */ ecs_entity_t type = ecs_get_typeid(world, it->ids[i]); if (!type) { /* Odd, we have a ptr but no Component? Not the place of the * serializer to complain about that. */ ecs_strbuf_appendch(buf, '0'); continue; } const EcsComponent *comp = ecs_get(world, type, EcsComponent); if (!comp) { /* Also odd, typeid but not a component? */ ecs_strbuf_appendch(buf, '0'); continue; } const EcsMetaTypeSerialized *ser = ecs_get( world, type, EcsMetaTypeSerialized); if (!ser) { /* Not odd, component just has no reflection data */ ecs_strbuf_appendch(buf, '0'); continue; } /* If term is not set, append empty array. This indicates that the term * could have had data but doesn't */ if (!ecs_field_is_set(it, i + 1)) { ecs_assert(ptr == NULL, ECS_INTERNAL_ERROR, NULL); flecs_json_array_push(buf); flecs_json_array_pop(buf); continue; } if (ecs_field_is_self(it, i + 1)) { int32_t count = it->count; array_to_json_buf_w_type_data(world, ptr, count, buf, comp, ser); } else { array_to_json_buf_w_type_data(world, ptr, 0, buf, comp, ser); } } flecs_json_array_pop(buf); } static void flecs_json_serialize_iter_result_columns( const ecs_world_t *world, const ecs_iter_t *it, ecs_strbuf_t *buf) { ecs_table_t *table = it->table; if (!table || !table->storage_table) { return; } flecs_json_memberl(buf, "values"); flecs_json_array_push(buf); ecs_type_t *type = &table->type; int32_t *storage_map = table->storage_map; ecs_assert(storage_map != NULL, ECS_INTERNAL_ERROR, NULL); for (int i = 0; i < type->count; i ++) { int32_t storage_column = -1; if (storage_map) { storage_column = storage_map[i]; } ecs_strbuf_list_next(buf); if (storage_column == -1) { ecs_strbuf_appendch(buf, '0'); continue; } ecs_entity_t typeid = table->type_info[storage_column]->component; if (!typeid) { ecs_strbuf_appendch(buf, '0'); continue; } const EcsComponent *comp = ecs_get(world, typeid, EcsComponent); if (!comp) { ecs_strbuf_appendch(buf, '0'); continue; } const EcsMetaTypeSerialized *ser = ecs_get( world, typeid, EcsMetaTypeSerialized); if (!ser) { ecs_strbuf_appendch(buf, '0'); continue; } void *ptr = ecs_vec_first(&table->data.columns[storage_column]); array_to_json_buf_w_type_data(world, ptr, it->count, buf, comp, ser); } flecs_json_array_pop(buf); } static void flecs_json_serialize_iter_result( const ecs_world_t *world, const ecs_iter_t *it, ecs_strbuf_t *buf, const ecs_iter_to_json_desc_t *desc, const ecs_json_ser_idr_t *ser_idr) { flecs_json_next(buf); flecs_json_object_push(buf); /* Each result can be matched with different component ids. Add them to * the result so clients know with which component an entity was matched */ if (desc && desc->serialize_table) { flecs_json_serialize_iter_result_table_type(world, it, buf); } else { if (!desc || desc->serialize_ids) { flecs_json_serialize_iter_result_ids(world, it, buf); } } /* Include information on which entity the term is matched with */ if (!desc || (desc->serialize_sources && !desc->serialize_table)) { flecs_json_serialize_iter_result_sources(world, it, buf); } /* Write variable values for current result */ if (!desc || desc->serialize_variables) { flecs_json_serialize_iter_result_variables(world, it, buf); } /* Write labels for variables */ if (desc && desc->serialize_variable_labels) { flecs_json_serialize_iter_result_variable_labels(world, it, buf); } /* Write ids for variables */ if (desc && desc->serialize_variable_ids) { flecs_json_serialize_iter_result_variable_ids(it, buf); } /* Include information on which terms are set, to support optional terms */ if (!desc || (desc->serialize_is_set && !desc->serialize_table)) { flecs_json_serialize_iter_result_is_set(it, buf); } /* Write entity ids for current result (for queries with This terms) */ if (!desc || desc->serialize_entities) { flecs_json_serialize_iter_result_entities(world, it, buf); } /* Write ids for entities */ if (desc && desc->serialize_entity_ids) { flecs_json_serialize_iter_result_entity_ids(it, buf); } /* Write labels for entities */ if (desc && desc->serialize_entity_labels) { flecs_json_serialize_iter_result_entity_labels(it, buf, ser_idr); } /* Write colors for entities */ if (desc && desc->serialize_colors) { flecs_json_serialize_iter_result_colors(it, buf, ser_idr); } /* Serialize component values */ if (desc && desc->serialize_table) { flecs_json_serialize_iter_result_columns(world, it, buf); } else { if (!desc || desc->serialize_values) { flecs_json_serialize_iter_result_values(world, it, buf); } } flecs_json_object_pop(buf); } int ecs_iter_to_json_buf( const ecs_world_t *world, ecs_iter_t *it, ecs_strbuf_t *buf, const ecs_iter_to_json_desc_t *desc) { ecs_time_t duration = {0}; if (desc && desc->measure_eval_duration) { ecs_time_measure(&duration); } flecs_json_object_push(buf); /* Serialize component ids of the terms (usually provided by query) */ if (!desc || desc->serialize_term_ids) { flecs_json_serialize_iter_ids(world, it, buf); } /* Serialize type info if enabled */ if (desc && desc->serialize_type_info) { flecs_json_serialize_type_info(world, it, buf); } /* Serialize variable names, if iterator has any */ flecs_json_serialize_iter_variables(it, buf); /* Serialize results */ flecs_json_memberl(buf, "results"); flecs_json_array_push(buf); /* Use instancing for improved performance */ ECS_BIT_SET(it->flags, EcsIterIsInstanced); /* If serializing entire table, don't bother letting the iterator populate * data fields as we'll be iterating all columns. */ if (desc && desc->serialize_table) { ECS_BIT_SET(it->flags, EcsIterNoData); } /* Cache id record for flecs.doc ids */ ecs_json_ser_idr_t ser_idr = {NULL, NULL}; #ifdef FLECS_DOC ser_idr.idr_doc_name = flecs_id_record_get(world, ecs_pair_t(EcsDocDescription, EcsName)); ser_idr.idr_doc_color = flecs_id_record_get(world, ecs_pair_t(EcsDocDescription, EcsDocColor)); #endif ecs_iter_next_action_t next = it->next; while (next(it)) { flecs_json_serialize_iter_result(world, it, buf, desc, &ser_idr); } flecs_json_array_pop(buf); if (desc && desc->measure_eval_duration) { double dt = ecs_time_measure(&duration); flecs_json_memberl(buf, "eval_duration"); flecs_json_number(buf, dt); } flecs_json_object_pop(buf); return 0; } char* ecs_iter_to_json( const ecs_world_t *world, ecs_iter_t *it, const ecs_iter_to_json_desc_t *desc) { ecs_strbuf_t buf = ECS_STRBUF_INIT; if (ecs_iter_to_json_buf(world, it, &buf, desc)) { ecs_strbuf_reset(&buf); return NULL; } return ecs_strbuf_get(&buf); } int ecs_world_to_json_buf( ecs_world_t *world, ecs_strbuf_t *buf_out, const ecs_world_to_json_desc_t *desc) { ecs_filter_t f = ECS_FILTER_INIT; ecs_filter_desc_t filter_desc = {0}; filter_desc.storage = &f; if (desc && desc->serialize_builtin && desc->serialize_modules) { filter_desc.terms[0].id = EcsAny; } else { bool serialize_builtin = desc && desc->serialize_builtin; bool serialize_modules = desc && desc->serialize_modules; int32_t term_id = 0; if (!serialize_builtin) { filter_desc.terms[term_id].id = ecs_pair(EcsChildOf, EcsFlecs); filter_desc.terms[term_id].oper = EcsNot; filter_desc.terms[term_id].src.flags = EcsSelf | EcsParent; term_id ++; } if (!serialize_modules) { filter_desc.terms[term_id].id = EcsModule; filter_desc.terms[term_id].oper = EcsNot; filter_desc.terms[term_id].src.flags = EcsSelf | EcsParent; } } if (ecs_filter_init(world, &filter_desc) == NULL) { return -1; } ecs_iter_t it = ecs_filter_iter(world, &f); ecs_iter_to_json_desc_t json_desc = { .serialize_table = true, .serialize_entities = true }; int ret = ecs_iter_to_json_buf(world, &it, buf_out, &json_desc); ecs_filter_fini(&f); return ret; } char* ecs_world_to_json( ecs_world_t *world, const ecs_world_to_json_desc_t *desc) { ecs_strbuf_t buf = ECS_STRBUF_INIT; if (ecs_world_to_json_buf(world, &buf, desc)) { ecs_strbuf_reset(&buf); return NULL; } return ecs_strbuf_get(&buf); } #endif /** * @file json/serialize_type_info.c * @brief Serialize type (reflection) information to JSON. */ #ifdef FLECS_JSON static int json_typeinfo_ser_type( const ecs_world_t *world, ecs_entity_t type, ecs_strbuf_t *buf); static int json_typeinfo_ser_primitive( ecs_primitive_kind_t kind, ecs_strbuf_t *str) { switch(kind) { case EcsBool: flecs_json_string(str, "bool"); break; case EcsChar: case EcsString: flecs_json_string(str, "text"); break; case EcsByte: flecs_json_string(str, "byte"); break; case EcsU8: case EcsU16: case EcsU32: case EcsU64: case EcsI8: case EcsI16: case EcsI32: case EcsI64: case EcsIPtr: case EcsUPtr: flecs_json_string(str, "int"); break; case EcsF32: case EcsF64: flecs_json_string(str, "float"); break; case EcsEntity: flecs_json_string(str, "entity"); break; default: return -1; } return 0; } static void json_typeinfo_ser_constants( const ecs_world_t *world, ecs_entity_t type, ecs_strbuf_t *str) { ecs_iter_t it = ecs_term_iter(world, &(ecs_term_t) { .id = ecs_pair(EcsChildOf, type) }); while (ecs_term_next(&it)) { int32_t i, count = it.count; for (i = 0; i < count; i ++) { flecs_json_next(str); flecs_json_string(str, ecs_get_name(world, it.entities[i])); } } } static void json_typeinfo_ser_enum( const ecs_world_t *world, ecs_entity_t type, ecs_strbuf_t *str) { ecs_strbuf_list_appendstr(str, "\"enum\""); json_typeinfo_ser_constants(world, type, str); } static void json_typeinfo_ser_bitmask( const ecs_world_t *world, ecs_entity_t type, ecs_strbuf_t *str) { ecs_strbuf_list_appendstr(str, "\"bitmask\""); json_typeinfo_ser_constants(world, type, str); } static int json_typeinfo_ser_array( const ecs_world_t *world, ecs_entity_t elem_type, int32_t count, ecs_strbuf_t *str) { ecs_strbuf_list_appendstr(str, "\"array\""); flecs_json_next(str); if (json_typeinfo_ser_type(world, elem_type, str)) { goto error; } ecs_strbuf_list_append(str, "%u", count); return 0; error: return -1; } static int json_typeinfo_ser_array_type( const ecs_world_t *world, ecs_entity_t type, ecs_strbuf_t *str) { const EcsArray *arr = ecs_get(world, type, EcsArray); ecs_assert(arr != NULL, ECS_INTERNAL_ERROR, NULL); if (json_typeinfo_ser_array(world, arr->type, arr->count, str)) { goto error; } return 0; error: return -1; } static int json_typeinfo_ser_vector( const ecs_world_t *world, ecs_entity_t type, ecs_strbuf_t *str) { const EcsVector *arr = ecs_get(world, type, EcsVector); ecs_assert(arr != NULL, ECS_INTERNAL_ERROR, NULL); ecs_strbuf_list_appendstr(str, "\"vector\""); flecs_json_next(str); if (json_typeinfo_ser_type(world, arr->type, str)) { goto error; } return 0; error: return -1; } /* Serialize unit information */ static int json_typeinfo_ser_unit( const ecs_world_t *world, ecs_strbuf_t *str, ecs_entity_t unit) { flecs_json_memberl(str, "unit"); flecs_json_path(str, world, unit); const EcsUnit *uptr = ecs_get(world, unit, EcsUnit); if (uptr) { if (uptr->symbol) { flecs_json_memberl(str, "symbol"); flecs_json_string(str, uptr->symbol); } ecs_entity_t quantity = ecs_get_target(world, unit, EcsQuantity, 0); if (quantity) { flecs_json_memberl(str, "quantity"); flecs_json_path(str, world, quantity); } } return 0; } /* Forward serialization to the different type kinds */ static int json_typeinfo_ser_type_op( const ecs_world_t *world, ecs_meta_type_op_t *op, ecs_strbuf_t *str) { if (op->kind == EcsOpOpaque) { const EcsOpaque *ct = ecs_get(world, op->type, EcsOpaque); ecs_assert(ct != NULL, ECS_INTERNAL_ERROR, NULL); return json_typeinfo_ser_type(world, ct->as_type, str); } flecs_json_array_push(str); switch(op->kind) { case EcsOpPush: case EcsOpPop: /* Should not be parsed as single op */ ecs_throw(ECS_INVALID_PARAMETER, NULL); break; case EcsOpEnum: json_typeinfo_ser_enum(world, op->type, str); break; case EcsOpBitmask: json_typeinfo_ser_bitmask(world, op->type, str); break; case EcsOpArray: json_typeinfo_ser_array_type(world, op->type, str); break; case EcsOpVector: json_typeinfo_ser_vector(world, op->type, str); break; case EcsOpOpaque: /* Can't happen, already handled above */ ecs_abort(ECS_INTERNAL_ERROR, NULL); break; default: if (json_typeinfo_ser_primitive( flecs_json_op_to_primitive_kind(op->kind), str)) { /* Unknown operation */ ecs_throw(ECS_INTERNAL_ERROR, NULL); return -1; } break; } ecs_entity_t unit = op->unit; if (unit) { flecs_json_next(str); flecs_json_next(str); flecs_json_object_push(str); json_typeinfo_ser_unit(world, str, unit); flecs_json_object_pop(str); } flecs_json_array_pop(str); return 0; error: return -1; } /* Iterate over a slice of the type ops array */ static int json_typeinfo_ser_type_ops( const ecs_world_t *world, ecs_meta_type_op_t *ops, int32_t op_count, ecs_strbuf_t *str) { for (int i = 0; i < op_count; i ++) { ecs_meta_type_op_t *op = &ops[i]; if (op != ops) { if (op->name) { flecs_json_member(str, op->name); } } int32_t elem_count = op->count; if (elem_count > 1) { flecs_json_array_push(str); json_typeinfo_ser_array(world, op->type, op->count, str); flecs_json_array_pop(str); i += op->op_count - 1; continue; } switch(op->kind) { case EcsOpPush: flecs_json_object_push(str); break; case EcsOpPop: flecs_json_object_pop(str); break; default: if (json_typeinfo_ser_type_op(world, op, str)) { goto error; } break; } } return 0; error: return -1; } static int json_typeinfo_ser_type( const ecs_world_t *world, ecs_entity_t type, ecs_strbuf_t *buf) { const EcsComponent *comp = ecs_get(world, type, EcsComponent); if (!comp) { ecs_strbuf_appendch(buf, '0'); return 0; } const EcsMetaTypeSerialized *ser = ecs_get( world, type, EcsMetaTypeSerialized); if (!ser) { ecs_strbuf_appendch(buf, '0'); return 0; } ecs_meta_type_op_t *ops = ecs_vec_first_t(&ser->ops, ecs_meta_type_op_t); int32_t count = ecs_vec_count(&ser->ops); return json_typeinfo_ser_type_ops(world, ops, count, buf); } int ecs_type_info_to_json_buf( const ecs_world_t *world, ecs_entity_t type, ecs_strbuf_t *buf) { return json_typeinfo_ser_type(world, type, buf); } char* ecs_type_info_to_json( const ecs_world_t *world, ecs_entity_t type) { ecs_strbuf_t str = ECS_STRBUF_INIT; if (ecs_type_info_to_json_buf(world, type, &str) != 0) { ecs_strbuf_reset(&str); return NULL; } return ecs_strbuf_get(&str); } #endif /** * @file json/deserialize.c * @brief Deserialize JSON strings into (component) values. */ #include #ifdef FLECS_JSON static const char* flecs_json_parse_path( const ecs_world_t *world, const char *json, char *token, ecs_entity_t *out, const ecs_from_json_desc_t *desc) { json = flecs_json_expect(json, JsonString, token, desc); if (!json) { goto error; } ecs_entity_t result = ecs_lookup_fullpath(world, token); if (!result) { ecs_parser_error(desc->name, desc->expr, json - desc->expr, "unresolved identifier '%s'", token); goto error; } *out = result; return json; error: return NULL; } const char* ecs_ptr_from_json( const ecs_world_t *world, ecs_entity_t type, void *ptr, const char *json, const ecs_from_json_desc_t *desc) { ecs_json_token_t token_kind = 0; char token_buffer[ECS_MAX_TOKEN_SIZE], t_lah[ECS_MAX_TOKEN_SIZE]; char *token = token_buffer; int depth = 0; const char *name = NULL; const char *expr = NULL; ecs_meta_cursor_t cur = ecs_meta_cursor(world, type, ptr); if (cur.valid == false) { return NULL; } if (desc) { name = desc->name; expr = desc->expr; cur.lookup_action = desc->lookup_action; cur.lookup_ctx = desc->lookup_ctx; } while ((json = flecs_json_parse(json, &token_kind, token))) { if (token_kind == JsonLargeString) { ecs_strbuf_t large_token = ECS_STRBUF_INIT; json = flecs_json_parse_large_string(json, &large_token); if (!json) { break; } token = ecs_strbuf_get(&large_token); token_kind = JsonString; } if (token_kind == JsonObjectOpen) { depth ++; if (ecs_meta_push(&cur) != 0) { goto error; } if (ecs_meta_is_collection(&cur)) { ecs_parser_error(name, expr, json - expr, "expected '['"); return NULL; } } else if (token_kind == JsonObjectClose) { depth --; if (ecs_meta_is_collection(&cur)) { ecs_parser_error(name, expr, json - expr, "expected ']'"); return NULL; } if (ecs_meta_pop(&cur) != 0) { goto error; } } else if (token_kind == JsonArrayOpen) { depth ++; if (ecs_meta_push(&cur) != 0) { goto error; } if (!ecs_meta_is_collection(&cur)) { ecs_parser_error(name, expr, json - expr, "expected '{'"); return NULL; } } else if (token_kind == JsonArrayClose) { depth --; if (!ecs_meta_is_collection(&cur)) { ecs_parser_error(name, expr, json - expr, "expected '}'"); return NULL; } if (ecs_meta_pop(&cur) != 0) { goto error; } } else if (token_kind == JsonComma) { if (ecs_meta_next(&cur) != 0) { goto error; } } else if (token_kind == JsonNull) { if (ecs_meta_set_null(&cur) != 0) { goto error; } } else if (token_kind == JsonString) { const char *lah = flecs_json_parse( json, &token_kind, t_lah); if (token_kind == JsonColon) { /* Member assignment */ json = lah; if (ecs_meta_dotmember(&cur, token) != 0) { goto error; } } else { if (ecs_meta_set_string(&cur, token) != 0) { goto error; } } } else if (token_kind == JsonNumber) { double number = atof(token); if (ecs_meta_set_float(&cur, number) != 0) { goto error; } } else if (token_kind == JsonNull) { if (ecs_meta_set_null(&cur) != 0) { goto error; } } else if (token_kind == JsonTrue) { if (ecs_meta_set_bool(&cur, true) != 0) { goto error; } } else if (token_kind == JsonFalse) { if (ecs_meta_set_bool(&cur, false) != 0) { goto error; } } else { goto error; } if (token != token_buffer) { ecs_os_free(token); token = token_buffer; } if (!depth) { break; } } return json; error: return NULL; } const char* ecs_entity_from_json( ecs_world_t *world, ecs_entity_t e, const char *json, const ecs_from_json_desc_t *desc_param) { ecs_json_token_t token_kind = 0; char token[ECS_MAX_TOKEN_SIZE]; ecs_from_json_desc_t desc = {0}; const char *name = NULL, *expr = json, *ids = NULL, *values = NULL, *lah; if (desc_param) { desc = *desc_param; } json = flecs_json_expect(json, JsonObjectOpen, token, &desc); if (!json) { goto error; } lah = flecs_json_parse(json, &token_kind, token); if (!lah) { goto error; } if (token_kind == JsonObjectClose) { return lah; } json = flecs_json_expect_member(json, token, &desc); if (!json) { return NULL; } if (!ecs_os_strcmp(token, "path")) { json = flecs_json_expect(json, JsonString, token, &desc); if (!json) { goto error; } ecs_add_fullpath(world, e, token); json = flecs_json_parse(json, &token_kind, token); if (!json) { goto error; } if (token_kind == JsonObjectClose) { return json; } else if (token_kind != JsonComma) { ecs_parser_error(name, expr, json - expr, "unexpected character"); goto error; } json = flecs_json_expect_member_name(json, token, "ids", &desc); if (!json) { goto error; } } else if (ecs_os_strcmp(token, "ids")) { ecs_parser_error(name, expr, json - expr, "expected member 'ids'"); goto error; } json = flecs_json_expect(json, JsonArrayOpen, token, &desc); if (!json) { goto error; } ids = json; json = flecs_json_skip_array(json, token, &desc); if (!json) { return NULL; } json = flecs_json_parse(json, &token_kind, token); if (token_kind != JsonObjectClose) { if (token_kind != JsonComma) { ecs_parser_error(name, expr, json - expr, "expected ','"); goto error; } json = flecs_json_expect_member_name(json, token, "values", &desc); if (!json) { goto error; } json = flecs_json_expect(json, JsonArrayOpen, token, &desc); if (!json) { goto error; } values = json; } do { ecs_entity_t first = 0, second = 0, type_id = 0; ecs_id_t id; ids = flecs_json_parse(ids, &token_kind, token); if (!ids) { goto error; } if (token_kind == JsonArrayClose) { if (values) { if (values[0] != ']') { ecs_parser_error(name, expr, values - expr, "expected ']'"); goto error; } json = ecs_parse_ws_eol(values + 1); } else { json = ids; } break; } else if (token_kind == JsonArrayOpen) { ids = flecs_json_parse_path(world, ids, token, &first, &desc); if (!ids) { goto error; } ids = flecs_json_parse(ids, &token_kind, token); if (!ids) { goto error; } if (token_kind == JsonComma) { /* Id is a pair*/ ids = flecs_json_parse_path(world, ids, token, &second, &desc); if (!ids) { goto error; } ids = flecs_json_expect(ids, JsonArrayClose, token, &desc); if (!ids) { goto error; } } else if (token_kind != JsonArrayClose) { ecs_parser_error(name, expr, ids - expr, "expected ',' or ']'"); goto error; } lah = flecs_json_parse(ids, &token_kind, token); if (!lah) { goto error; } if (token_kind == JsonComma) { ids = lah; } else if (token_kind != JsonArrayClose) { ecs_parser_error(name, expr, lah - expr, "expected ',' or ']'"); goto error; } } else { ecs_parser_error(name, expr, lah - expr, "expected '[' or ']'"); goto error; } if (second) { id = ecs_pair(first, second); type_id = ecs_get_typeid(world, id); if (!type_id) { ecs_parser_error(name, expr, ids - expr, "id is not a type"); goto error; } } else { id = first; type_id = first; } /* Get mutable pointer */ void *comp_ptr = ecs_get_mut_id(world, e, id); if (!comp_ptr) { char *idstr = ecs_id_str(world, id); ecs_parser_error(name, expr, json - expr, "id '%s' is not a valid component", idstr); ecs_os_free(idstr); goto error; } if (values) { ecs_from_json_desc_t parse_desc = { .name = name, .expr = expr, }; values = ecs_ptr_from_json( world, type_id, comp_ptr, values, &parse_desc); if (!values) { goto error; } lah = flecs_json_parse(values, &token_kind, token); if (!lah) { goto error; } if (token_kind == JsonComma) { values = lah; } else if (token_kind != JsonArrayClose) { ecs_parser_error(name, expr, json - expr, "expected ',' or ']'"); goto error; } else { values = ecs_parse_ws_eol(values); } ecs_modified_id(world, e, id); } } while(ids[0]); return flecs_json_expect(json, JsonObjectClose, token, &desc); error: return NULL; } static ecs_entity_t flecs_json_new_id( ecs_world_t *world, ecs_entity_t ser_id) { /* Try to honor low id requirements */ if (ser_id < ECS_HI_COMPONENT_ID) { return ecs_new_low_id(world); } else { return ecs_new_id(world); } } static ecs_entity_t flecs_json_lookup( ecs_world_t *world, ecs_entity_t parent, const char *name, const ecs_from_json_desc_t *desc) { ecs_entity_t scope = 0; if (parent) { scope = ecs_set_scope(world, parent); } ecs_entity_t result = desc->lookup_action(world, name, desc->lookup_ctx); if (parent) { ecs_set_scope(world, scope); } return result; } static void flecs_json_mark_reserved( ecs_map_t *anonymous_ids, ecs_entity_t e) { ecs_entity_t *reserved = ecs_map_ensure(anonymous_ids, e); ecs_assert(reserved[0] == 0, ECS_INTERNAL_ERROR, NULL); reserved[0] = 0; } static bool flecs_json_name_is_anonymous( const char *name) { if (isdigit(name[0])) { const char *ptr; for (ptr = name + 1; *ptr; ptr ++) { if (!isdigit(*ptr)) { break; } } if (!(*ptr)) { return true; } } return false; } static ecs_entity_t flecs_json_ensure_entity( ecs_world_t *world, const char *name, ecs_map_t *anonymous_ids) { ecs_entity_t e = 0; if (flecs_json_name_is_anonymous(name)) { /* Anonymous entity, find or create mapping to new id */ ecs_entity_t ser_id = flecs_ito(ecs_entity_t, atoll(name)); ecs_entity_t *deser_id = ecs_map_get(anonymous_ids, ser_id); if (deser_id) { if (!deser_id[0]) { /* Id is already issued by deserializer, create new id */ deser_id[0] = flecs_json_new_id(world, ser_id); /* Mark new id as reserved */ flecs_json_mark_reserved(anonymous_ids, deser_id[0]); } else { /* Id mapping exists */ } } else { /* Id has not yet been issued by deserializer, which means it's safe * to use. This allows the deserializer to bind to existing * anonymous ids, as they will never be reissued. */ deser_id = ecs_map_ensure(anonymous_ids, ser_id); if (!ecs_exists(world, ser_id) || ecs_is_alive(world, ser_id)) { /* Only use existing id if it's alive or doesn't exist yet. The * id could have been recycled for another entity */ deser_id[0] = ser_id; ecs_ensure(world, ser_id); } else { /* If id exists and is not alive, create a new id */ deser_id[0] = flecs_json_new_id(world, ser_id); /* Mark new id as reserved */ flecs_json_mark_reserved(anonymous_ids, deser_id[0]); } } e = deser_id[0]; } else { e = ecs_lookup_path_w_sep(world, 0, name, ".", NULL, false); if (!e) { e = ecs_entity(world, { .name = name }); flecs_json_mark_reserved(anonymous_ids, e); } } return e; } static ecs_table_t* flecs_json_parse_table( ecs_world_t *world, const char *json, char *token, const ecs_from_json_desc_t *desc) { ecs_json_token_t token_kind = 0; ecs_table_t *table = NULL; do { ecs_id_t id = 0; json = flecs_json_expect(json, JsonArrayOpen, token, desc); if (!json) { goto error; } json = flecs_json_expect(json, JsonString, token, desc); if (!json) { goto error; } ecs_entity_t first = flecs_json_lookup(world, 0, token, desc); if (!first) { goto error; } json = flecs_json_parse(json, &token_kind, token); if (token_kind == JsonComma) { json = flecs_json_expect(json, JsonString, token, desc); if (!json) { goto error; } ecs_entity_t second = flecs_json_lookup(world, 0, token, desc); if (!second) { goto error; } id = ecs_pair(first, second); json = flecs_json_expect(json, JsonArrayClose, token, desc); if (!json) { goto error; } } else if (token_kind == JsonArrayClose) { id = first; } else { ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expected ',' or ']"); goto error; } table = ecs_table_add_id(world, table, id); if (!table) { goto error; } const char *lah = flecs_json_parse(json, &token_kind, token); if (token_kind == JsonComma) { json = lah; } else if (token_kind == JsonArrayClose) { break; } else { ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expected ',' or ']'"); goto error; } } while (json[0]); return table; error: return NULL; } static int flecs_json_parse_entities( ecs_world_t *world, ecs_allocator_t *a, ecs_table_t *table, ecs_entity_t parent, const char *json, char *token, ecs_vec_t *records, const ecs_from_json_desc_t *desc) { char name_token[ECS_MAX_TOKEN_SIZE]; ecs_json_token_t token_kind = 0; ecs_vec_clear(records); do { json = flecs_json_parse(json, &token_kind, name_token); if (!json) { goto error; } if ((token_kind != JsonNumber) && (token_kind != JsonString)) { ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expected number or string"); goto error; } ecs_entity_t e = flecs_json_lookup(world, parent, name_token, desc); ecs_record_t *r = flecs_entities_try(world, e); ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); if (r->table != table) { bool cleared = false; if (r->table) { ecs_commit(world, e, r, r->table, NULL, &r->table->type); cleared = true; } ecs_commit(world, e, r, table, &table->type, NULL); if (cleared) { char *entity_name = strrchr(name_token, '.'); if (entity_name) { entity_name ++; } else { entity_name = name_token; } if (!flecs_json_name_is_anonymous(entity_name)) { ecs_set_name(world, e, entity_name); } } } ecs_assert(table == r->table, ECS_INTERNAL_ERROR, NULL); ecs_record_t** elem = ecs_vec_append_t(a, records, ecs_record_t*); *elem = r; json = flecs_json_parse(json, &token_kind, token); if (token_kind == JsonArrayClose) { break; } else if (token_kind != JsonComma) { ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expected ',' or ']'"); goto error; } } while(json[0]); return 0; error: return -1; } static const char* flecs_json_parse_column( ecs_world_t *world, ecs_table_t *table, int32_t column, const char *json, char *token, ecs_vec_t *records, const ecs_from_json_desc_t *desc) { if (!table->storage_table) { ecs_parser_error(desc->name, desc->expr, json - desc->expr, "table has no components"); goto error; } if (column >= table->type.count) { ecs_parser_error(desc->name, desc->expr, json - desc->expr, "more value arrays than component columns in table"); goto error; } int32_t data_column = table->storage_map[column]; if (data_column == -1) { char *table_str = ecs_table_str(world, table); ecs_parser_error(desc->name, desc->expr, json - desc->expr, "values provided for tag at column %d of table [%s]", column, table_str); ecs_os_free(table_str); goto error; } ecs_json_token_t token_kind = 0; ecs_vec_t *data = &table->data.columns[data_column]; ecs_type_info_t *ti = table->type_info[data_column]; ecs_size_t size = ti->size; ecs_entity_t type = ti->component; ecs_record_t **record_array = ecs_vec_first_t(records, ecs_record_t*); int32_t entity = 0; do { ecs_record_t *r = record_array[entity]; int32_t row = ECS_RECORD_TO_ROW(r->row); ecs_assert(ecs_vec_get_t( &table->data.records, ecs_record_t*, row)[0] == r, ECS_INTERNAL_ERROR, NULL); void *ptr = ecs_vec_get(data, size, row); ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); json = ecs_ptr_from_json(world, type, ptr, json, desc); if (!json) { break; } json = flecs_json_parse(json, &token_kind, token); if (token_kind == JsonArrayClose) { break; } else if (token_kind != JsonComma) { ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expected ',' or ']'"); } entity ++; } while (json[0]); return json; error: return NULL; } static const char* flecs_json_parse_values( ecs_world_t *world, ecs_table_t *table, const char *json, char *token, ecs_vec_t *records, ecs_vec_t *columns_set, const ecs_from_json_desc_t *desc) { ecs_allocator_t *a = &world->allocator; ecs_json_token_t token_kind = 0; int32_t column = 0; ecs_vec_clear(columns_set); do { json = flecs_json_parse(json, &token_kind, token); if (!json) { goto error; } if (token_kind == JsonArrayClose) { break; } else if (token_kind == JsonArrayOpen) { json = flecs_json_parse_column(world, table, column, json, token, records, desc); if (!json) { goto error; } ecs_id_t *id_set = ecs_vec_append_t(a, columns_set, ecs_id_t); *id_set = table->type.array[column]; column ++; } else if (token_kind == JsonNumber) { if (!ecs_os_strcmp(token, "0")) { column ++; /* no data */ } else { ecs_parser_error(desc->name, desc->expr, json - desc->expr, "unexpected number"); goto error; } } json = flecs_json_parse(json, &token_kind, token); if (token_kind == JsonArrayClose) { break; } else if (token_kind != JsonComma) { ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expected ',' or ']'"); goto error; } } while (json[0]); /* Send OnSet notifications */ ecs_defer_begin(world); ecs_type_t type = { .array = columns_set->array, .count = columns_set->count }; int32_t table_count = ecs_table_count(table); int32_t i, record_count = ecs_vec_count(records); /* If the entire table was inserted, send bulk notification */ if (table_count == ecs_vec_count(records)) { flecs_notify_on_set(world, table, 0, ecs_table_count(table), &type, true); } else { ecs_record_t **rvec = ecs_vec_first_t(records, ecs_record_t*); for (i = 0; i < record_count; i ++) { ecs_record_t *r = rvec[i]; int32_t row = ECS_RECORD_TO_ROW(r->row); flecs_notify_on_set(world, table, row, 1, &type, true); } } ecs_defer_end(world); return json; error: return NULL; } static const char* flecs_json_parse_result( ecs_world_t *world, ecs_allocator_t *a, const char *json, char *token, ecs_vec_t *records, ecs_vec_t *columns_set, const ecs_from_json_desc_t *desc) { ecs_json_token_t token_kind = 0; const char *ids = NULL, *values = NULL, *entities = NULL; json = flecs_json_expect(json, JsonObjectOpen, token, desc); if (!json) { goto error; } json = flecs_json_expect_member_name(json, token, "ids", desc); if (!json) { goto error; } json = flecs_json_expect(json, JsonArrayOpen, token, desc); if (!json) { goto error; } ids = json; /* store start of ids array */ json = flecs_json_skip_array(json, token, desc); if (!json) { goto error; } json = flecs_json_expect(json, JsonComma, token, desc); if (!json) { goto error; } json = flecs_json_expect_member(json, token, desc); if (!json) { goto error; } ecs_entity_t parent = 0; if (!ecs_os_strcmp(token, "parent")) { json = flecs_json_expect(json, JsonString, token, desc); if (!json) { goto error; } parent = ecs_lookup_fullpath(world, token); json = flecs_json_expect(json, JsonComma, token, desc); if (!json) { goto error; } json = flecs_json_expect_member(json, token, desc); if (!json) { goto error; } } if (ecs_os_strcmp(token, "entities")) { ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expected 'entities'"); goto error; } json = flecs_json_expect(json, JsonArrayOpen, token, desc); if (!json) { goto error; } entities = json; /* store start of entity id array */ json = flecs_json_skip_array(json, token, desc); if (!json) { goto error; } json = flecs_json_parse(json, &token_kind, token); if (token_kind == JsonComma) { json = flecs_json_expect_member_name(json, token, "values", desc); if (!json) { goto error; } json = flecs_json_expect(json, JsonArrayOpen, token, desc); if (!json) { goto error; } values = json; /* store start of entities array */ } else if (token_kind != JsonObjectClose) { ecs_parser_error(desc->name, desc->expr, json - desc->expr, "expected ',' or '}'"); goto error; } /* Find table from ids */ ecs_table_t *table = flecs_json_parse_table(world, ids, token, desc); if (!table) { goto error; } /* Add entities to table */ if (flecs_json_parse_entities(world, a, table, parent, entities, token, records, desc)) { goto error; } /* Parse values */ if (values) { json = flecs_json_parse_values(world, table, values, token, records, columns_set, desc); if (!json) { goto error; } json = flecs_json_expect(json, JsonObjectClose, token, desc); if (!json) { goto error; } } return json; error: return NULL; } const char* ecs_world_from_json( ecs_world_t *world, const char *json, const ecs_from_json_desc_t *desc_arg) { ecs_json_token_t token_kind; char token[ECS_MAX_TOKEN_SIZE]; ecs_from_json_desc_t desc = {0}; ecs_allocator_t *a = &world->allocator; ecs_vec_t records; ecs_vec_t columns_set; ecs_map_t anonymous_ids; ecs_vec_init_t(a, &records, ecs_record_t*, 0); ecs_vec_init_t(a, &columns_set, ecs_id_t, 0); ecs_map_init(&anonymous_ids, a); const char *name = NULL, *expr = json, *lah; if (desc_arg) { desc = *desc_arg; } if (!desc.lookup_action) { desc.lookup_action = (ecs_entity_t(*)( const ecs_world_t*, const char*, void*))flecs_json_ensure_entity; desc.lookup_ctx = &anonymous_ids; } json = flecs_json_expect(json, JsonObjectOpen, token, &desc); if (!json) { goto error; } json = flecs_json_expect_member_name(json, token, "results", &desc); if (!json) { goto error; } json = flecs_json_expect(json, JsonArrayOpen, token, &desc); if (!json) { goto error; } lah = flecs_json_parse(json, &token_kind, token); if (token_kind == JsonArrayClose) { json = lah; goto end; } do { json = flecs_json_parse_result(world, a, json, token, &records, &columns_set, &desc); if (!json) { goto error; } json = flecs_json_parse(json, &token_kind, token); if (token_kind == JsonArrayClose) { break; } else if (token_kind != JsonComma) { ecs_parser_error(name, expr, json - expr, "expected ',' or ']'"); goto error; } } while(json && json[0]); end: ecs_vec_fini_t(a, &records, ecs_record_t*); ecs_vec_fini_t(a, &columns_set, ecs_id_t); ecs_map_fini(&anonymous_ids); json = flecs_json_expect(json, JsonObjectClose, token, &desc); if (!json) { goto error; } return json; error: ecs_vec_fini_t(a, &records, ecs_record_t*); ecs_vec_fini_t(a, &columns_set, ecs_id_t); ecs_map_fini(&anonymous_ids); return NULL; } #endif /** * @file addons/rest.c * @brief Rest addon. */ #ifdef FLECS_REST ECS_TAG_DECLARE(EcsRestPlecs); typedef struct { ecs_world_t *world; ecs_http_server_t *srv; int32_t rc; } ecs_rest_ctx_t; /* Global statistics */ int64_t ecs_rest_request_count = 0; int64_t ecs_rest_entity_count = 0; int64_t ecs_rest_entity_error_count = 0; int64_t ecs_rest_query_count = 0; int64_t ecs_rest_query_error_count = 0; int64_t ecs_rest_query_name_count = 0; int64_t ecs_rest_query_name_error_count = 0; int64_t ecs_rest_query_name_from_cache_count = 0; int64_t ecs_rest_enable_count = 0; int64_t ecs_rest_enable_error_count = 0; int64_t ecs_rest_set_count = 0; int64_t ecs_rest_set_error_count = 0; int64_t ecs_rest_delete_count = 0; int64_t ecs_rest_delete_error_count = 0; int64_t ecs_rest_world_stats_count = 0; int64_t ecs_rest_pipeline_stats_count = 0; int64_t ecs_rest_stats_error_count = 0; static ECS_COPY(EcsRest, dst, src, { ecs_rest_ctx_t *impl = src->impl; if (impl) { impl->rc ++; } ecs_os_strset(&dst->ipaddr, src->ipaddr); dst->port = src->port; dst->impl = impl; }) static ECS_MOVE(EcsRest, dst, src, { *dst = *src; src->ipaddr = NULL; src->impl = NULL; }) static ECS_DTOR(EcsRest, ptr, { ecs_rest_ctx_t *impl = ptr->impl; if (impl) { impl->rc --; if (!impl->rc) { ecs_http_server_fini(impl->srv); ecs_os_free(impl); } } ecs_os_free(ptr->ipaddr); }) static char *rest_last_err; static void flecs_rest_capture_log( int32_t level, const char *file, int32_t line, const char *msg) { (void)file; (void)line; if (!rest_last_err && level < 0) { rest_last_err = ecs_os_strdup(msg); } } static char* flecs_rest_get_captured_log(void) { char *result = rest_last_err; rest_last_err = NULL; return result; } static void flecs_reply_verror( ecs_http_reply_t *reply, const char *fmt, va_list args) { ecs_strbuf_appendlit(&reply->body, "{\"error\":\""); ecs_strbuf_vappend(&reply->body, fmt, args); ecs_strbuf_appendlit(&reply->body, "\"}"); } static void flecs_reply_error( ecs_http_reply_t *reply, const char *fmt, ...) { va_list args; va_start(args, fmt); flecs_reply_verror(reply, fmt, args); va_end(args); } static void flecs_rest_bool_param( const ecs_http_request_t *req, const char *name, bool *value_out) { const char *value = ecs_http_get_param(req, name); if (value) { if (!ecs_os_strcmp(value, "true")) { value_out[0] = true; } else { value_out[0] = false; } } } static void flecs_rest_int_param( const ecs_http_request_t *req, const char *name, int32_t *value_out) { const char *value = ecs_http_get_param(req, name); if (value) { *value_out = atoi(value); } } static void flecs_rest_string_param( const ecs_http_request_t *req, const char *name, char **value_out) { const char *value = ecs_http_get_param(req, name); if (value) { *value_out = (char*)value; } } static void flecs_rest_parse_json_ser_entity_params( ecs_entity_to_json_desc_t *desc, const ecs_http_request_t *req) { flecs_rest_bool_param(req, "path", &desc->serialize_path); flecs_rest_bool_param(req, "label", &desc->serialize_label); flecs_rest_bool_param(req, "brief", &desc->serialize_brief); flecs_rest_bool_param(req, "link", &desc->serialize_link); flecs_rest_bool_param(req, "color", &desc->serialize_color); flecs_rest_bool_param(req, "id_labels", &desc->serialize_id_labels); flecs_rest_bool_param(req, "base", &desc->serialize_base); flecs_rest_bool_param(req, "values", &desc->serialize_values); flecs_rest_bool_param(req, "private", &desc->serialize_private); flecs_rest_bool_param(req, "type_info", &desc->serialize_type_info); } static void flecs_rest_parse_json_ser_iter_params( ecs_iter_to_json_desc_t *desc, const ecs_http_request_t *req) { flecs_rest_bool_param(req, "term_ids", &desc->serialize_term_ids); flecs_rest_bool_param(req, "ids", &desc->serialize_ids); flecs_rest_bool_param(req, "sources", &desc->serialize_sources); flecs_rest_bool_param(req, "variables", &desc->serialize_variables); flecs_rest_bool_param(req, "is_set", &desc->serialize_is_set); flecs_rest_bool_param(req, "values", &desc->serialize_values); flecs_rest_bool_param(req, "entities", &desc->serialize_entities); flecs_rest_bool_param(req, "entity_labels", &desc->serialize_entity_labels); flecs_rest_bool_param(req, "variable_labels", &desc->serialize_variable_labels); flecs_rest_bool_param(req, "variable_ids", &desc->serialize_variable_ids); flecs_rest_bool_param(req, "colors", &desc->serialize_colors); flecs_rest_bool_param(req, "duration", &desc->measure_eval_duration); flecs_rest_bool_param(req, "type_info", &desc->serialize_type_info); flecs_rest_bool_param(req, "serialize_table", &desc->serialize_table); } static bool flecs_rest_reply_entity( ecs_world_t *world, const ecs_http_request_t* req, ecs_http_reply_t *reply) { char *path = &req->path[7]; ecs_dbg_2("rest: request entity '%s'", path); ecs_os_linc(&ecs_rest_entity_count); ecs_entity_t e = ecs_lookup_path_w_sep( world, 0, path, "/", NULL, false); if (!e) { ecs_dbg_2("rest: entity '%s' not found", path); flecs_reply_error(reply, "entity '%s' not found", path); reply->code = 404; ecs_os_linc(&ecs_rest_entity_error_count); return true; } ecs_entity_to_json_desc_t desc = ECS_ENTITY_TO_JSON_INIT; flecs_rest_parse_json_ser_entity_params(&desc, req); ecs_entity_to_json_buf(world, e, &reply->body, &desc); return true; } static bool flecs_rest_reply_world( ecs_world_t *world, const ecs_http_request_t* req, ecs_http_reply_t *reply) { (void)req; ecs_world_to_json_buf(world, &reply->body, NULL); return true; } static ecs_entity_t flecs_rest_entity_from_path( ecs_world_t *world, ecs_http_reply_t *reply, const char *path) { ecs_entity_t e = ecs_lookup_path_w_sep( world, 0, path, "/", NULL, false); if (!e) { flecs_reply_error(reply, "entity '%s' not found", path); reply->code = 404; } return e; } static bool flecs_rest_set( ecs_world_t *world, const ecs_http_request_t* req, ecs_http_reply_t *reply, const char *path) { ecs_os_linc(&ecs_rest_set_count); ecs_entity_t e; if (!(e = flecs_rest_entity_from_path(world, reply, path))) { ecs_os_linc(&ecs_rest_set_error_count); return true; } const char *data = ecs_http_get_param(req, "data"); ecs_from_json_desc_t desc = {0}; desc.expr = data; desc.name = path; if (ecs_entity_from_json(world, e, data, &desc) == NULL) { flecs_reply_error(reply, "invalid request"); reply->code = 400; ecs_os_linc(&ecs_rest_set_error_count); return true; } return true; } static bool flecs_rest_delete( ecs_world_t *world, ecs_http_reply_t *reply, const char *path) { ecs_os_linc(&ecs_rest_set_count); ecs_entity_t e; if (!(e = flecs_rest_entity_from_path(world, reply, path))) { ecs_os_linc(&ecs_rest_delete_error_count); return true; } ecs_delete(world, e); return true; } static bool flecs_rest_enable( ecs_world_t *world, ecs_http_reply_t *reply, const char *path, bool enable) { ecs_os_linc(&ecs_rest_enable_count); ecs_entity_t e; if (!(e = flecs_rest_entity_from_path(world, reply, path))) { ecs_os_linc(&ecs_rest_enable_error_count); return true; } ecs_enable(world, e, enable); return true; } static bool flecs_rest_script( ecs_world_t *world, const ecs_http_request_t* req, ecs_http_reply_t *reply) { (void)world; (void)req; (void)reply; #ifdef FLECS_PLECS const char *data = ecs_http_get_param(req, "data"); if (!data) { flecs_reply_error(reply, "missing data parameter"); return true; } bool prev_color = ecs_log_enable_colors(false); ecs_os_api_log_t prev_log_ = ecs_os_api.log_; ecs_os_api.log_ = flecs_rest_capture_log; ecs_entity_t script = ecs_script(world, { .entity = ecs_entity(world, { .name = "scripts.main" }), .str = data }); if (!script) { char *err = flecs_rest_get_captured_log(); char *escaped_err = ecs_astresc('"', err); flecs_reply_error(reply, escaped_err); ecs_os_linc(&ecs_rest_query_error_count); 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_iter_to_reply( ecs_world_t *world, const ecs_http_request_t* req, ecs_http_reply_t *reply, ecs_iter_t *it) { ecs_iter_to_json_desc_t desc = ECS_ITER_TO_JSON_INIT; flecs_rest_parse_json_ser_iter_params(&desc, req); int32_t offset = 0; int32_t limit = 1000; flecs_rest_int_param(req, "offset", &offset); flecs_rest_int_param(req, "limit", &limit); if (offset < 0 || limit < 0) { flecs_reply_error(reply, "invalid offset/limit parameter"); reply->code = 400; return; } ecs_iter_t pit = ecs_page_iter(it, offset, limit); ecs_iter_to_json_buf(world, &pit, &reply->body, &desc); } static bool flecs_rest_reply_existing_query( ecs_world_t *world, const ecs_http_request_t* req, ecs_http_reply_t *reply, const char *name) { ecs_os_linc(&ecs_rest_query_name_count); ecs_entity_t q = ecs_lookup_fullpath(world, name); if (!q) { flecs_reply_error(reply, "unresolved identifier '%s'", name); reply->code = 404; ecs_os_linc(&ecs_rest_query_name_error_count); return true; } const EcsPoly *poly = ecs_get_pair(world, q, EcsPoly, EcsQuery); if (!poly) { flecs_reply_error(reply, "resolved identifier '%s' is not a query", name); reply->code = 400; ecs_os_linc(&ecs_rest_query_name_error_count); return true; } ecs_iter_t it; ecs_iter_poly(world, poly->poly, &it, NULL); ecs_dbg_2("rest: request query '%s'", q); bool prev_color = ecs_log_enable_colors(false); ecs_os_api_log_t prev_log_ = ecs_os_api.log_; ecs_os_api.log_ = flecs_rest_capture_log; const char *vars = ecs_http_get_param(req, "vars"); if (vars) { if (!ecs_poly_is(poly->poly, ecs_rule_t)) { flecs_reply_error(reply, "variables are only supported for rule queries"); reply->code = 400; ecs_os_linc(&ecs_rest_query_name_error_count); return true; } if (ecs_rule_parse_vars(poly->poly, &it, vars) == NULL) { char *err = flecs_rest_get_captured_log(); char *escaped_err = ecs_astresc('"', err); flecs_reply_error(reply, escaped_err); reply->code = 400; ecs_os_linc(&ecs_rest_query_name_error_count); ecs_os_free(escaped_err); ecs_os_free(err); return true; } } flecs_rest_iter_to_reply(world, req, reply, &it); ecs_os_api.log_ = prev_log_; ecs_log_enable_colors(prev_color); return true; } static bool flecs_rest_reply_query( ecs_world_t *world, const ecs_http_request_t* req, ecs_http_reply_t *reply) { const char *q_name = ecs_http_get_param(req, "name"); if (q_name) { return flecs_rest_reply_existing_query(world, req, reply, q_name); } ecs_os_linc(&ecs_rest_query_count); const char *q = ecs_http_get_param(req, "q"); if (!q) { ecs_strbuf_appendlit(&reply->body, "Missing parameter 'q'"); reply->code = 400; /* bad request */ ecs_os_linc(&ecs_rest_query_error_count); return true; } ecs_dbg_2("rest: request query '%s'", q); bool prev_color = ecs_log_enable_colors(false); ecs_os_api_log_t prev_log_ = ecs_os_api.log_; ecs_os_api.log_ = flecs_rest_capture_log; ecs_rule_t *r = ecs_rule_init(world, &(ecs_filter_desc_t){ .expr = q }); if (!r) { char *err = flecs_rest_get_captured_log(); char *escaped_err = ecs_astresc('"', err); flecs_reply_error(reply, escaped_err); ecs_os_linc(&ecs_rest_query_error_count); reply->code = 400; /* bad request */ ecs_os_free(escaped_err); ecs_os_free(err); } else { ecs_iter_t it = ecs_rule_iter(world, r); flecs_rest_iter_to_reply(world, req, reply, &it); ecs_rule_fini(r); } ecs_os_api.log_ = prev_log_; ecs_log_enable_colors(prev_color); return true; } #ifdef FLECS_MONITOR static void _flecs_rest_array_append( ecs_strbuf_t *reply, const char *field, int32_t field_len, const ecs_float_t *values, int32_t t) { ecs_strbuf_list_appendch(reply, '"'); ecs_strbuf_appendstrn(reply, field, field_len); ecs_strbuf_appendlit(reply, "\":"); ecs_strbuf_list_push(reply, "[", ","); int32_t i; for (i = t + 1; i <= (t + ECS_STAT_WINDOW); i ++) { int32_t index = i % ECS_STAT_WINDOW; ecs_strbuf_list_next(reply); ecs_strbuf_appendflt(reply, (double)values[index], '"'); } ecs_strbuf_list_pop(reply, "]"); } #define flecs_rest_array_append(reply, field, values, t)\ _flecs_rest_array_append(reply, field, sizeof(field) - 1, values, t) static void flecs_rest_gauge_append( ecs_strbuf_t *reply, const ecs_metric_t *m, const char *field, int32_t field_len, int32_t t, const char *brief, int32_t brief_len) { ecs_strbuf_list_appendch(reply, '"'); ecs_strbuf_appendstrn(reply, field, field_len); ecs_strbuf_appendlit(reply, "\":"); ecs_strbuf_list_push(reply, "{", ","); flecs_rest_array_append(reply, "avg", m->gauge.avg, t); flecs_rest_array_append(reply, "min", m->gauge.min, t); flecs_rest_array_append(reply, "max", m->gauge.max, t); if (brief) { ecs_strbuf_list_appendlit(reply, "\"brief\":\""); ecs_strbuf_appendstrn(reply, brief, brief_len); ecs_strbuf_appendch(reply, '"'); } ecs_strbuf_list_pop(reply, "}"); } static void flecs_rest_counter_append( ecs_strbuf_t *reply, const ecs_metric_t *m, const char *field, int32_t field_len, int32_t t, const char *brief, int32_t brief_len) { flecs_rest_gauge_append(reply, m, field, field_len, t, brief, brief_len); } #define ECS_GAUGE_APPEND_T(reply, s, field, t, brief)\ flecs_rest_gauge_append(reply, &(s)->field, #field, sizeof(#field) - 1, t, brief, sizeof(brief) - 1) #define ECS_COUNTER_APPEND_T(reply, s, field, t, brief)\ flecs_rest_counter_append(reply, &(s)->field, #field, sizeof(#field) - 1, t, brief, sizeof(brief) - 1) #define ECS_GAUGE_APPEND(reply, s, field, brief)\ ECS_GAUGE_APPEND_T(reply, s, field, (s)->t, brief) #define ECS_COUNTER_APPEND(reply, s, field, brief)\ ECS_COUNTER_APPEND_T(reply, s, field, (s)->t, brief) static void flecs_world_stats_to_json( ecs_strbuf_t *reply, const EcsWorldStats *monitor_stats) { const ecs_world_stats_t *stats = &monitor_stats->stats; ecs_strbuf_list_push(reply, "{", ","); ECS_GAUGE_APPEND(reply, stats, entities.count, "Alive entity ids in the world"); ECS_GAUGE_APPEND(reply, stats, entities.not_alive_count, "Not alive entity ids in the world"); ECS_GAUGE_APPEND(reply, stats, performance.fps, "Frames per second"); ECS_COUNTER_APPEND(reply, stats, performance.frame_time, "Time spent in frame"); ECS_COUNTER_APPEND(reply, stats, performance.system_time, "Time spent on running systems in frame"); ECS_COUNTER_APPEND(reply, stats, performance.emit_time, "Time spent on notifying observers in frame"); ECS_COUNTER_APPEND(reply, stats, performance.merge_time, "Time spent on merging commands in frame"); ECS_COUNTER_APPEND(reply, stats, performance.rematch_time, "Time spent on revalidating query caches in frame"); ECS_COUNTER_APPEND(reply, stats, commands.add_count, "Add commands executed"); ECS_COUNTER_APPEND(reply, stats, commands.remove_count, "Remove commands executed"); ECS_COUNTER_APPEND(reply, stats, commands.delete_count, "Delete commands executed"); ECS_COUNTER_APPEND(reply, stats, commands.clear_count, "Clear commands executed"); ECS_COUNTER_APPEND(reply, stats, commands.set_count, "Set commands executed"); ECS_COUNTER_APPEND(reply, stats, commands.get_mut_count, "Get_mut commands executed"); ECS_COUNTER_APPEND(reply, stats, commands.modified_count, "Modified commands executed"); ECS_COUNTER_APPEND(reply, stats, commands.other_count, "Misc commands executed"); ECS_COUNTER_APPEND(reply, stats, commands.discard_count, "Commands for already deleted entities"); ECS_COUNTER_APPEND(reply, stats, commands.batched_entity_count, "Entities with batched commands"); ECS_COUNTER_APPEND(reply, stats, commands.batched_count, "Number of commands batched"); ECS_COUNTER_APPEND(reply, stats, frame.merge_count, "Number of merges (sync points)"); ECS_COUNTER_APPEND(reply, stats, frame.pipeline_build_count, "Pipeline rebuilds (happen when systems become active/enabled)"); ECS_COUNTER_APPEND(reply, stats, frame.systems_ran, "Systems ran in frame"); ECS_COUNTER_APPEND(reply, stats, frame.observers_ran, "Number of times an observer was invoked in frame"); ECS_COUNTER_APPEND(reply, stats, frame.event_emit_count, "Events emitted in frame"); ECS_COUNTER_APPEND(reply, stats, frame.rematch_count, "Number of query cache revalidations"); ECS_GAUGE_APPEND(reply, stats, tables.count, "Tables in the world (including empty)"); ECS_GAUGE_APPEND(reply, stats, tables.empty_count, "Empty tables in the world"); ECS_GAUGE_APPEND(reply, stats, tables.tag_only_count, "Tables with only tags"); ECS_GAUGE_APPEND(reply, stats, tables.trivial_only_count, "Tables with only trivial types (no hooks)"); ECS_GAUGE_APPEND(reply, stats, tables.record_count, "Table records registered with search indices"); ECS_GAUGE_APPEND(reply, stats, tables.storage_count, "Component storages for all tables"); ECS_COUNTER_APPEND(reply, stats, tables.create_count, "Number of new tables created"); ECS_COUNTER_APPEND(reply, stats, tables.delete_count, "Number of tables deleted"); ECS_GAUGE_APPEND(reply, stats, ids.count, "Component, tag and pair ids in use"); ECS_GAUGE_APPEND(reply, stats, ids.tag_count, "Tag ids in use"); ECS_GAUGE_APPEND(reply, stats, ids.component_count, "Component ids in use"); ECS_GAUGE_APPEND(reply, stats, ids.pair_count, "Pair ids in use"); ECS_GAUGE_APPEND(reply, stats, ids.wildcard_count, "Wildcard ids in use"); ECS_GAUGE_APPEND(reply, stats, ids.type_count, "Registered component types"); ECS_COUNTER_APPEND(reply, stats, ids.create_count, "Number of new component, tag and pair ids created"); ECS_COUNTER_APPEND(reply, stats, ids.delete_count, "Number of component, pair and tag ids deleted"); ECS_GAUGE_APPEND(reply, stats, queries.query_count, "Queries in the world"); ECS_GAUGE_APPEND(reply, stats, queries.observer_count, "Observers in the world"); ECS_GAUGE_APPEND(reply, stats, queries.system_count, "Systems in the world"); ECS_COUNTER_APPEND(reply, stats, memory.alloc_count, "Allocations by OS API"); ECS_COUNTER_APPEND(reply, stats, memory.realloc_count, "Reallocs by OS API"); ECS_COUNTER_APPEND(reply, stats, memory.free_count, "Frees by OS API"); ECS_GAUGE_APPEND(reply, stats, memory.outstanding_alloc_count, "Outstanding allocations by OS API"); ECS_COUNTER_APPEND(reply, stats, memory.block_alloc_count, "Blocks allocated by block allocators"); ECS_COUNTER_APPEND(reply, stats, memory.block_free_count, "Blocks freed by block allocators"); ECS_GAUGE_APPEND(reply, stats, memory.block_outstanding_alloc_count, "Outstanding block allocations"); ECS_COUNTER_APPEND(reply, stats, memory.stack_alloc_count, "Pages allocated by stack allocators"); ECS_COUNTER_APPEND(reply, stats, memory.stack_free_count, "Pages freed by stack allocators"); ECS_GAUGE_APPEND(reply, stats, memory.stack_outstanding_alloc_count, "Outstanding page allocations"); ECS_COUNTER_APPEND(reply, stats, rest.request_count, "Received requests"); ECS_COUNTER_APPEND(reply, stats, rest.entity_count, "Received entity/ requests"); ECS_COUNTER_APPEND(reply, stats, rest.entity_error_count, "Failed entity/ requests"); ECS_COUNTER_APPEND(reply, stats, rest.query_count, "Received query/ requests"); ECS_COUNTER_APPEND(reply, stats, rest.query_error_count, "Failed query/ requests"); ECS_COUNTER_APPEND(reply, stats, rest.query_name_count, "Received named query/ requests"); ECS_COUNTER_APPEND(reply, stats, rest.query_name_error_count, "Failed named query/ requests"); ECS_COUNTER_APPEND(reply, stats, rest.query_name_from_cache_count, "Named query/ requests from cache"); ECS_COUNTER_APPEND(reply, stats, rest.enable_count, "Received enable/ and disable/ requests"); ECS_COUNTER_APPEND(reply, stats, rest.enable_error_count, "Failed enable/ and disable/ requests"); ECS_COUNTER_APPEND(reply, stats, rest.world_stats_count, "Received world stats requests"); ECS_COUNTER_APPEND(reply, stats, rest.pipeline_stats_count, "Received pipeline stats requests"); ECS_COUNTER_APPEND(reply, stats, rest.stats_error_count, "Failed stats requests"); ECS_COUNTER_APPEND(reply, stats, http.request_received_count, "Received requests"); ECS_COUNTER_APPEND(reply, stats, http.request_invalid_count, "Received invalid requests"); ECS_COUNTER_APPEND(reply, stats, http.request_handled_ok_count, "Requests handled successfully"); ECS_COUNTER_APPEND(reply, stats, http.request_handled_error_count, "Requests handled with error code"); ECS_COUNTER_APPEND(reply, stats, http.request_not_handled_count, "Requests not handled (unknown endpoint)"); ECS_COUNTER_APPEND(reply, stats, http.request_preflight_count, "Preflight requests received"); ECS_COUNTER_APPEND(reply, stats, http.send_ok_count, "Successful replies"); ECS_COUNTER_APPEND(reply, stats, http.send_error_count, "Unsuccessful replies"); ECS_COUNTER_APPEND(reply, stats, http.busy_count, "Dropped requests due to full send queue (503)"); ecs_strbuf_list_pop(reply, "}"); } static void flecs_system_stats_to_json( ecs_world_t *world, ecs_strbuf_t *reply, ecs_entity_t system, const ecs_system_stats_t *stats) { ecs_strbuf_list_push(reply, "{", ","); ecs_strbuf_list_appendlit(reply, "\"name\":\""); ecs_get_path_w_sep_buf(world, 0, system, ".", NULL, reply); ecs_strbuf_appendch(reply, '"'); if (!stats->task) { ECS_GAUGE_APPEND(reply, &stats->query, matched_table_count, ""); ECS_GAUGE_APPEND(reply, &stats->query, matched_entity_count, ""); } ECS_COUNTER_APPEND_T(reply, stats, time_spent, stats->query.t, ""); ecs_strbuf_list_pop(reply, "}"); } static void flecs_pipeline_stats_to_json( ecs_world_t *world, ecs_strbuf_t *reply, const EcsPipelineStats *stats) { ecs_strbuf_list_push(reply, "[", ","); int32_t i, count = ecs_vec_count(&stats->stats.systems); ecs_entity_t *ids = ecs_vec_first_t(&stats->stats.systems, ecs_entity_t); for (i = 0; i < count; i ++) { ecs_entity_t id = ids[i]; ecs_strbuf_list_next(reply); if (id) { ecs_system_stats_t *sys_stats = ecs_map_get_deref( &stats->stats.system_stats, ecs_system_stats_t, id); flecs_system_stats_to_json(world, reply, id, sys_stats); } else { /* Sync point */ ecs_strbuf_list_push(reply, "{", ","); ecs_strbuf_list_pop(reply, "}"); } } ecs_strbuf_list_pop(reply, "]"); } static bool flecs_rest_reply_stats( ecs_world_t *world, const ecs_http_request_t* req, ecs_http_reply_t *reply) { char *period_str = NULL; flecs_rest_string_param(req, "period", &period_str); char *category = &req->path[6]; ecs_entity_t period = EcsPeriod1s; if (period_str) { char *period_name = ecs_asprintf("Period%s", period_str); period = ecs_lookup_child(world, ecs_id(FlecsMonitor), period_name); ecs_os_free(period_name); if (!period) { flecs_reply_error(reply, "bad request (invalid period string)"); reply->code = 400; ecs_os_linc(&ecs_rest_stats_error_count); return false; } } if (!ecs_os_strcmp(category, "world")) { const EcsWorldStats *stats = ecs_get_pair(world, EcsWorld, EcsWorldStats, period); flecs_world_stats_to_json(&reply->body, stats); ecs_os_linc(&ecs_rest_world_stats_count); return true; } else if (!ecs_os_strcmp(category, "pipeline")) { const EcsPipelineStats *stats = ecs_get_pair(world, EcsWorld, EcsPipelineStats, period); flecs_pipeline_stats_to_json(world, &reply->body, stats); ecs_os_linc(&ecs_rest_pipeline_stats_count); return true; } else { flecs_reply_error(reply, "bad request (unsupported category)"); reply->code = 400; ecs_os_linc(&ecs_rest_stats_error_count); return false; } return true; } #else static bool flecs_rest_reply_stats( ecs_world_t *world, const ecs_http_request_t* req, ecs_http_reply_t *reply) { (void)world; (void)req; (void)reply; return false; } #endif static void flecs_rest_reply_table_append_type( ecs_world_t *world, ecs_strbuf_t *reply, const ecs_table_t *table) { ecs_strbuf_list_push(reply, "[", ","); int32_t i, count = table->type.count; ecs_id_t *ids = table->type.array; for (i = 0; i < count; i ++) { ecs_strbuf_list_next(reply); ecs_strbuf_appendch(reply, '"'); ecs_id_str_buf(world, ids[i], reply); ecs_strbuf_appendch(reply, '"'); } ecs_strbuf_list_pop(reply, "]"); } static void flecs_rest_reply_table_append_memory( ecs_strbuf_t *reply, const ecs_table_t *table) { int32_t used = 0, allocated = 0; used += table->data.entities.count * ECS_SIZEOF(ecs_entity_t); used += table->data.records.count * ECS_SIZEOF(ecs_record_t*); allocated += table->data.entities.size * ECS_SIZEOF(ecs_entity_t); allocated += table->data.records.size * ECS_SIZEOF(ecs_record_t*); int32_t i, storage_count = table->storage_count; ecs_type_info_t **ti = table->type_info; ecs_vec_t *storages = table->data.columns; for (i = 0; i < storage_count; i ++) { used += storages[i].count * ti[i]->size; allocated += storages[i].size * ti[i]->size; } ecs_strbuf_list_push(reply, "{", ","); ecs_strbuf_list_append(reply, "\"used\":%d", used); ecs_strbuf_list_append(reply, "\"allocated\":%d", allocated); ecs_strbuf_list_pop(reply, "}"); } static void flecs_rest_reply_table_append( ecs_world_t *world, ecs_strbuf_t *reply, const ecs_table_t *table) { ecs_strbuf_list_next(reply); ecs_strbuf_list_push(reply, "{", ","); ecs_strbuf_list_append(reply, "\"id\":%u", (uint32_t)table->id); ecs_strbuf_list_appendstr(reply, "\"type\":"); flecs_rest_reply_table_append_type(world, reply, table); ecs_strbuf_list_append(reply, "\"count\":%d", ecs_table_count(table)); ecs_strbuf_list_append(reply, "\"memory\":"); flecs_rest_reply_table_append_memory(reply, table); ecs_strbuf_list_append(reply, "\"refcount\":%d", table->refcount); ecs_strbuf_list_pop(reply, "}"); } static bool flecs_rest_reply_tables( ecs_world_t *world, const ecs_http_request_t* req, ecs_http_reply_t *reply) { (void)req; ecs_strbuf_list_push(&reply->body, "[", ","); ecs_sparse_t *tables = &world->store.tables; int32_t i, count = flecs_sparse_count(tables); for (i = 0; i < count; i ++) { ecs_table_t *table = flecs_sparse_get_dense_t(tables, ecs_table_t, i); flecs_rest_reply_table_append(world, &reply->body, table); } ecs_strbuf_list_pop(&reply->body, "]"); return true; } static bool flecs_rest_reply( const ecs_http_request_t* req, ecs_http_reply_t *reply, void *ctx) { ecs_rest_ctx_t *impl = ctx; ecs_world_t *world = impl->world; ecs_os_linc(&ecs_rest_request_count); if (req->path == NULL) { ecs_dbg("rest: bad request (missing path)"); flecs_reply_error(reply, "bad request (missing path)"); reply->code = 400; return false; } if (req->method == EcsHttpGet) { /* Entity endpoint */ if (!ecs_os_strncmp(req->path, "entity/", 7)) { return flecs_rest_reply_entity(world, req, reply); /* Query endpoint */ } else if (!ecs_os_strcmp(req->path, "query")) { return flecs_rest_reply_query(world, req, reply); /* World endpoint */ } else if (!ecs_os_strcmp(req->path, "world")) { return flecs_rest_reply_world(world, req, reply); /* Stats endpoint */ } else if (!ecs_os_strncmp(req->path, "stats/", 6)) { return flecs_rest_reply_stats(world, req, reply); /* Tables endpoint */ } else if (!ecs_os_strncmp(req->path, "tables", 6)) { return flecs_rest_reply_tables(world, req, reply); } } else if (req->method == EcsHttpPut) { /* Set endpoint */ if (!ecs_os_strncmp(req->path, "set/", 4)) { return flecs_rest_set(world, req, reply, &req->path[4]); /* Delete endpoint */ } else if (!ecs_os_strncmp(req->path, "delete/", 7)) { return flecs_rest_delete(world, reply, &req->path[7]); /* Enable endpoint */ } else if (!ecs_os_strncmp(req->path, "enable/", 7)) { return flecs_rest_enable(world, reply, &req->path[7], true); /* Disable endpoint */ } else if (!ecs_os_strncmp(req->path, "disable/", 8)) { return flecs_rest_enable(world, reply, &req->path[8], false); /* Script endpoint */ } else if (!ecs_os_strncmp(req->path, "script", 6)) { return flecs_rest_script(world, req, reply); } } return false; } ecs_http_server_t* ecs_rest_server_init( ecs_world_t *world, const ecs_http_server_desc_t *desc) { ecs_rest_ctx_t *srv_ctx = ecs_os_calloc_t(ecs_rest_ctx_t); ecs_http_server_desc_t private_desc = {0}; if (desc) { private_desc = *desc; } private_desc.callback = flecs_rest_reply; private_desc.ctx = srv_ctx; ecs_http_server_t *srv = ecs_http_server_init(&private_desc); if (!srv) { ecs_os_free(srv_ctx); return NULL; } srv_ctx->world = world; srv_ctx->srv = srv; srv_ctx->rc = 1; srv_ctx->srv = srv; return srv; } void ecs_rest_server_fini( ecs_http_server_t *srv) { ecs_rest_ctx_t *srv_ctx = ecs_http_server_ctx(srv); ecs_os_free(srv_ctx); ecs_http_server_fini(srv); } static void flecs_on_set_rest(ecs_iter_t *it) { EcsRest *rest = it->ptrs[0]; int i; for(i = 0; i < it->count; i ++) { if (!rest[i].port) { rest[i].port = ECS_REST_DEFAULT_PORT; } ecs_http_server_t *srv = ecs_rest_server_init(it->real_world, &(ecs_http_server_desc_t){ .ipaddr = rest[i].ipaddr, .port = rest[i].port }); if (!srv) { const char *ipaddr = rest[i].ipaddr ? rest[i].ipaddr : "0.0.0.0"; ecs_err("failed to create REST server on %s:%u", ipaddr, rest[i].port); continue; } rest[i].impl = ecs_http_server_ctx(srv); ecs_http_server_start(srv); } } static void DequeueRest(ecs_iter_t *it) { EcsRest *rest = ecs_field(it, EcsRest, 1); if (it->delta_system_time > (ecs_ftime_t)1.0) { ecs_warn( "detected large progress interval (%.2fs), REST request may timeout", (double)it->delta_system_time); } int32_t i; for(i = 0; i < it->count; i ++) { ecs_rest_ctx_t *ctx = rest[i].impl; if (ctx) { ecs_http_server_dequeue(ctx->srv, it->delta_time); } } } static void DisableRest(ecs_iter_t *it) { ecs_world_t *world = it->world; ecs_iter_t rit = ecs_term_iter(world, &(ecs_term_t){ .id = ecs_id(EcsRest), .src.flags = EcsSelf }); if (it->event == EcsOnAdd) { /* REST module was disabled */ while (ecs_term_next(&rit)) { EcsRest *rest = ecs_field(&rit, EcsRest, 1); int i; for (i = 0; i < rit.count; i ++) { ecs_rest_ctx_t *ctx = rest[i].impl; ecs_http_server_stop(ctx->srv); } } } else if (it->event == EcsOnRemove) { /* REST module was enabled */ while (ecs_term_next(&rit)) { EcsRest *rest = ecs_field(&rit, EcsRest, 1); int i; for (i = 0; i < rit.count; i ++) { ecs_rest_ctx_t *ctx = rest[i].impl; ecs_http_server_start(ctx->srv); } } } } void FlecsRestImport( ecs_world_t *world) { ECS_MODULE(world, FlecsRest); ECS_IMPORT(world, FlecsPipeline); #ifdef FLECS_PLECS ECS_IMPORT(world, FlecsScript); #endif ecs_set_name_prefix(world, "Ecs"); flecs_bootstrap_component(world, EcsRest); ecs_set_hooks(world, EcsRest, { .ctor = ecs_default_ctor, .move = ecs_move(EcsRest), .copy = ecs_copy(EcsRest), .dtor = ecs_dtor(EcsRest), .on_set = flecs_on_set_rest }); ECS_SYSTEM(world, DequeueRest, EcsPostFrame, EcsRest); ecs_system(world, { .entity = ecs_id(DequeueRest), .no_readonly = true }); ecs_observer(world, { .filter = { .terms = {{ .id = EcsDisabled, .src.id = ecs_id(FlecsRest) }} }, .events = {EcsOnAdd, EcsOnRemove}, .callback = DisableRest }); ecs_set_name_prefix(world, "EcsRest"); ECS_TAG_DEFINE(world, EcsRestPlecs); } #endif /** * @file addons/coredoc.c * @brief Core doc addon. */ #ifdef FLECS_COREDOC #define URL_ROOT "https://www.flecs.dev/flecs/md_docs_Relationships.html/" void FlecsCoreDocImport( ecs_world_t *world) { ECS_MODULE(world, FlecsCoreDoc); ECS_IMPORT(world, FlecsMeta); ECS_IMPORT(world, FlecsDoc); ecs_set_name_prefix(world, "Ecs"); /* Initialize reflection data for core components */ ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_id(EcsComponent), .members = { {.name = (char*)"size", .type = ecs_id(ecs_i32_t)}, {.name = (char*)"alignment", .type = ecs_id(ecs_i32_t)} } }); ecs_struct_init(world, &(ecs_struct_desc_t){ .entity = ecs_id(EcsDocDescription), .members = { {.name = "value", .type = ecs_id(ecs_string_t)} } }); /* Initialize documentation data for core components */ ecs_doc_set_brief(world, EcsFlecs, "Flecs root module"); ecs_doc_set_link(world, EcsFlecs, "https://github.com/SanderMertens/flecs"); ecs_doc_set_brief(world, EcsFlecsCore, "Flecs module with builtin components"); ecs_doc_set_brief(world, EcsWorld, "Entity associated with world"); ecs_doc_set_brief(world, ecs_id(EcsComponent), "Component that is added to all components"); ecs_doc_set_brief(world, EcsModule, "Tag that is added to modules"); ecs_doc_set_brief(world, EcsPrefab, "Tag that is added to prefabs"); ecs_doc_set_brief(world, EcsDisabled, "Tag that is added to disabled entities"); ecs_doc_set_brief(world, ecs_id(EcsIdentifier), "Component used for entity names"); ecs_doc_set_brief(world, EcsName, "Tag used with EcsIdentifier to signal entity name"); ecs_doc_set_brief(world, EcsSymbol, "Tag used with EcsIdentifier to signal entity symbol"); ecs_doc_set_brief(world, EcsTransitive, "Transitive relationship property"); ecs_doc_set_brief(world, EcsReflexive, "Reflexive relationship property"); ecs_doc_set_brief(world, EcsFinal, "Final relationship property"); ecs_doc_set_brief(world, EcsDontInherit, "DontInherit relationship property"); ecs_doc_set_brief(world, EcsTag, "Tag relationship property"); ecs_doc_set_brief(world, EcsAcyclic, "Acyclic relationship property"); ecs_doc_set_brief(world, EcsTraversable, "Traversable relationship property"); ecs_doc_set_brief(world, EcsExclusive, "Exclusive relationship property"); ecs_doc_set_brief(world, EcsSymmetric, "Symmetric relationship property"); ecs_doc_set_brief(world, EcsWith, "With relationship property"); ecs_doc_set_brief(world, EcsOnDelete, "OnDelete relationship cleanup property"); ecs_doc_set_brief(world, EcsOnDeleteTarget, "OnDeleteTarget relationship cleanup property"); ecs_doc_set_brief(world, EcsDefaultChildComponent, "Sets default component hint for children of entity"); ecs_doc_set_brief(world, EcsRemove, "Remove relationship cleanup property"); ecs_doc_set_brief(world, EcsDelete, "Delete relationship cleanup property"); ecs_doc_set_brief(world, EcsPanic, "Panic relationship cleanup property"); ecs_doc_set_brief(world, EcsIsA, "Builtin IsA relationship"); ecs_doc_set_brief(world, EcsChildOf, "Builtin ChildOf relationship"); ecs_doc_set_brief(world, EcsDependsOn, "Builtin DependsOn relationship"); ecs_doc_set_brief(world, EcsOnAdd, "Builtin OnAdd event"); ecs_doc_set_brief(world, EcsOnRemove, "Builtin OnRemove event"); ecs_doc_set_brief(world, EcsOnSet, "Builtin OnSet event"); ecs_doc_set_brief(world, EcsUnSet, "Builtin UnSet event"); ecs_doc_set_link(world, EcsTransitive, URL_ROOT "#transitive-property"); ecs_doc_set_link(world, EcsReflexive, URL_ROOT "#reflexive-property"); ecs_doc_set_link(world, EcsFinal, URL_ROOT "#final-property"); ecs_doc_set_link(world, EcsDontInherit, URL_ROOT "#dontinherit-property"); ecs_doc_set_link(world, EcsTag, URL_ROOT "#tag-property"); ecs_doc_set_link(world, EcsAcyclic, URL_ROOT "#acyclic-property"); ecs_doc_set_link(world, EcsTraversable, URL_ROOT "#traversable-property"); ecs_doc_set_link(world, EcsExclusive, URL_ROOT "#exclusive-property"); ecs_doc_set_link(world, EcsSymmetric, URL_ROOT "#symmetric-property"); ecs_doc_set_link(world, EcsWith, URL_ROOT "#with-property"); ecs_doc_set_link(world, EcsOnDelete, URL_ROOT "#cleanup-properties"); ecs_doc_set_link(world, EcsOnDeleteTarget, URL_ROOT "#cleanup-properties"); ecs_doc_set_link(world, EcsRemove, URL_ROOT "#cleanup-properties"); ecs_doc_set_link(world, EcsDelete, URL_ROOT "#cleanup-properties"); ecs_doc_set_link(world, EcsPanic, URL_ROOT "#cleanup-properties"); ecs_doc_set_link(world, EcsIsA, URL_ROOT "#the-isa-relationship"); ecs_doc_set_link(world, EcsChildOf, URL_ROOT "#the-childof-relationship"); /* Initialize documentation for meta components */ ecs_entity_t meta = ecs_lookup_fullpath(world, "flecs.meta"); ecs_doc_set_brief(world, meta, "Flecs module with reflection components"); ecs_doc_set_brief(world, ecs_id(EcsMetaType), "Component added to types"); ecs_doc_set_brief(world, ecs_id(EcsMetaTypeSerialized), "Component that stores reflection data in an optimized format"); ecs_doc_set_brief(world, ecs_id(EcsPrimitive), "Component added to primitive types"); ecs_doc_set_brief(world, ecs_id(EcsEnum), "Component added to enumeration types"); ecs_doc_set_brief(world, ecs_id(EcsBitmask), "Component added to bitmask types"); ecs_doc_set_brief(world, ecs_id(EcsMember), "Component added to struct members"); ecs_doc_set_brief(world, ecs_id(EcsStruct), "Component added to struct types"); ecs_doc_set_brief(world, ecs_id(EcsArray), "Component added to array types"); ecs_doc_set_brief(world, ecs_id(EcsVector), "Component added to vector types"); ecs_doc_set_brief(world, ecs_id(ecs_bool_t), "bool component"); ecs_doc_set_brief(world, ecs_id(ecs_char_t), "char component"); ecs_doc_set_brief(world, ecs_id(ecs_byte_t), "byte component"); ecs_doc_set_brief(world, ecs_id(ecs_u8_t), "8 bit unsigned int component"); ecs_doc_set_brief(world, ecs_id(ecs_u16_t), "16 bit unsigned int component"); ecs_doc_set_brief(world, ecs_id(ecs_u32_t), "32 bit unsigned int component"); ecs_doc_set_brief(world, ecs_id(ecs_u64_t), "64 bit unsigned int component"); ecs_doc_set_brief(world, ecs_id(ecs_uptr_t), "word sized unsigned int component"); ecs_doc_set_brief(world, ecs_id(ecs_i8_t), "8 bit signed int component"); ecs_doc_set_brief(world, ecs_id(ecs_i16_t), "16 bit signed int component"); ecs_doc_set_brief(world, ecs_id(ecs_i32_t), "32 bit signed int component"); ecs_doc_set_brief(world, ecs_id(ecs_i64_t), "64 bit signed int component"); ecs_doc_set_brief(world, ecs_id(ecs_iptr_t), "word sized signed int component"); ecs_doc_set_brief(world, ecs_id(ecs_f32_t), "32 bit floating point component"); ecs_doc_set_brief(world, ecs_id(ecs_f64_t), "64 bit floating point component"); ecs_doc_set_brief(world, ecs_id(ecs_string_t), "string component"); ecs_doc_set_brief(world, ecs_id(ecs_entity_t), "entity component"); /* Initialize documentation for doc components */ ecs_entity_t doc = ecs_lookup_fullpath(world, "flecs.doc"); ecs_doc_set_brief(world, doc, "Flecs module with documentation components"); ecs_doc_set_brief(world, ecs_id(EcsDocDescription), "Component used to add documentation"); ecs_doc_set_brief(world, EcsDocBrief, "Used as (Description, Brief) to add a brief description"); ecs_doc_set_brief(world, EcsDocDetail, "Used as (Description, Detail) to add a detailed description"); ecs_doc_set_brief(world, EcsDocLink, "Used as (Description, Link) to add a link"); } #endif /** * @file addons/http.c * @brief HTTP addon. * * This is a heavily modified version of the EmbeddableWebServer (see copyright * below). This version has been stripped from everything not strictly necessary * for receiving/replying to simple HTTP requests, and has been modified to use * the Flecs OS API. * * EmbeddableWebServer Copyright (c) 2016, 2019, 2020 Forrest Heller, and * CONTRIBUTORS (see below) - All rights reserved. * * CONTRIBUTORS: * Martin Pulec - bug fixes, warning fixes, IPv6 support * Daniel Barry - bug fix (ifa_addr != NULL) * * Released under the BSD 2-clause license: * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. THIS SOFTWARE IS * PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN * NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifdef FLECS_HTTP #if defined(ECS_TARGET_WINDOWS) #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #pragma comment(lib, "Ws2_32.lib") #include #include #include typedef SOCKET ecs_http_socket_t; #else #include #include #include #include #include #include #include #include typedef int ecs_http_socket_t; #endif #if !defined(MSG_NOSIGNAL) #define MSG_NOSIGNAL (0) #endif /* Max length of request method */ #define ECS_HTTP_METHOD_LEN_MAX (8) /* Timeout (s) before connection purge */ #define ECS_HTTP_CONNECTION_PURGE_TIMEOUT (1.0) /* Number of dequeues before purging */ #define ECS_HTTP_CONNECTION_PURGE_RETRY_COUNT (5) /* Minimum interval between dequeueing requests (ms) */ #define ECS_HTTP_MIN_DEQUEUE_INTERVAL (50) /* Minimum interval between printing statistics (ms) */ #define ECS_HTTP_MIN_STATS_INTERVAL (10 * 1000) /* Max length of headers in reply */ #define ECS_HTTP_REPLY_HEADER_SIZE (1024) /* Receive buffer size */ #define ECS_HTTP_SEND_RECV_BUFFER_SIZE (16 * 1024) /* Max length of request (path + query + headers + body) */ #define ECS_HTTP_REQUEST_LEN_MAX (10 * 1024 * 1024) /* Total number of outstanding send requests */ #define ECS_HTTP_SEND_QUEUE_MAX (256) /* Cache invalidation timeout (s) */ #define ECS_HTTP_CACHE_TIMEOUT ((ecs_ftime_t)1.0) /* Cache entry purge timeout (s) */ #define ECS_HTTP_CACHE_PURGE_TIMEOUT ((ecs_ftime_t)10.0) /* 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; ecs_ftime_t 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; 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 int http_getnameinfo( const struct sockaddr* addr, ecs_size_t addr_len, char *host, ecs_size_t host_len, char *port, ecs_size_t port_len, int flags) { ecs_assert(addr_len > 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(host_len > 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(port_len > 0, ECS_INTERNAL_ERROR, NULL); return getnameinfo(addr, (uint32_t)addr_len, host, (uint32_t)host_len, port, (uint32_t)port_len, flags); } static int http_bind( ecs_http_socket_t sock, const struct sockaddr* addr, ecs_size_t addr_len) { ecs_assert(addr_len > 0, ECS_INTERNAL_ERROR, NULL); return bind(sock, addr, (uint32_t)addr_len); } static bool http_socket_is_valid( ecs_http_socket_t sock) { #if defined(ECS_TARGET_WINDOWS) return sock != INVALID_SOCKET; #else return sock >= 0; #endif } #if defined(ECS_TARGET_WINDOWS) #define HTTP_SOCKET_INVALID INVALID_SOCKET #else #define HTTP_SOCKET_INVALID (-1) #endif static void http_close( ecs_http_socket_t *sock) { ecs_assert(sock != NULL, ECS_INTERNAL_ERROR, NULL); #if defined(ECS_TARGET_WINDOWS) closesocket(*sock); #else ecs_dbg_2("http: closing socket %u", *sock); shutdown(*sock, SHUT_RDWR); close(*sock); #endif *sock = HTTP_SOCKET_INVALID; } static ecs_http_socket_t http_accept( ecs_http_socket_t sock, struct sockaddr* addr, ecs_size_t *addr_len) { socklen_t len = (socklen_t)addr_len[0]; ecs_http_socket_t result = accept(sock, addr, &len); addr_len[0] = (ecs_size_t)len; return result; } static void http_reply_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) { ecs_ftime_t tf = (ecs_ftime_t)ecs_time_measure(&t); if ((tf - entry->time) < ECS_HTTP_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_ftime_t)ecs_time_measure(&t); entry->content_length = ecs_strbuf_written(&reply->body); entry->content = ecs_strbuf_get(&reply->body); 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); 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]]; http_decode_url_str((char*)req->pub.params[i].value); } req->pub.header_count = frag->header_count; req->pub.param_count = frag->param_count; req->res = res; req->req_len = frag->header_offsets[0]; 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->buf.max = ECS_HTTP_METHOD_LEN_MAX; frag->state = HttpFragStateMethod; frag->header_buf_ptr = frag->header_buf; /* fallthrough */ case HttpFragStateMethod: if (c == ' ') { http_parse_method(frag); frag->state = HttpFragStatePath; frag->buf.max = ECS_HTTP_REQUEST_LEN_MAX; } else { ecs_strbuf_appendch(&frag->buf, c); } break; case HttpFragStatePath: if (c == ' ') { frag->state = HttpFragStateVersion; ecs_strbuf_appendch(&frag->buf, '\0'); } else { if (c == '?' || c == '=' || c == '&') { ecs_strbuf_appendch(&frag->buf, '\0'); int32_t offset = ecs_strbuf_written(&frag->buf); if (c == '?' || c == '&') { frag->param_offsets[frag->param_count] = offset; } else { frag->param_value_offsets[frag->param_count] = offset; frag->param_count ++; } } else { ecs_strbuf_appendch(&frag->buf, c); } } break; case HttpFragStateVersion: if (c == '\r') { frag->state = HttpFragStateCR; } /* version is not stored */ break; case HttpFragStateHeaderStart: if (http_header_writable(frag)) { frag->header_offsets[frag->header_count] = ecs_strbuf_written(&frag->buf); } http_header_buf_reset(frag); frag->state = HttpFragStateHeaderName; /* fallthrough */ case HttpFragStateHeaderName: if (c == ':') { frag->state = HttpFragStateHeaderValueStart; http_header_buf_append(frag, '\0'); frag->parse_content_length = !ecs_os_strcmp( frag->header_buf, "Content-Length"); if (http_header_writable(frag)) { ecs_strbuf_appendch(&frag->buf, '\0'); frag->header_value_offsets[frag->header_count] = ecs_strbuf_written(&frag->buf); } } else if (c == '\r') { frag->state = HttpFragStateCR; } else { http_header_buf_append(frag, c); if (http_header_writable(frag)) { ecs_strbuf_appendch(&frag->buf, c); } } break; case HttpFragStateHeaderValueStart: http_header_buf_reset(frag); frag->state = HttpFragStateHeaderValue; if (c == ' ') { /* skip first space */ break; } /* fallthrough */ case HttpFragStateHeaderValue: if (c == '\r') { if (frag->parse_content_length) { http_header_buf_append(frag, '\0'); int32_t len = atoi(frag->header_buf); if (len < 0) { frag->invalid = true; } else { frag->content_length = len; } frag->parse_content_length = false; } if (http_header_writable(frag)) { int32_t cur = ecs_strbuf_written(&frag->buf); if (frag->header_offsets[frag->header_count] < cur && frag->header_value_offsets[frag->header_count] < cur) { ecs_strbuf_appendch(&frag->buf, '\0'); frag->header_count ++; } } frag->state = HttpFragStateCR; } else { if (frag->parse_content_length) { http_header_buf_append(frag, c); } if (http_header_writable(frag)) { ecs_strbuf_appendch(&frag->buf, c); } } break; case HttpFragStateCR: if (c == '\n') { frag->state = HttpFragStateCRLF; } else { frag->state = HttpFragStateHeaderStart; } break; case HttpFragStateCRLF: if (c == '\r') { frag->state = HttpFragStateCRLFCR; } else { frag->state = HttpFragStateHeaderStart; i--; } break; case HttpFragStateCRLFCR: if (c == '\n') { if (frag->content_length != 0) { frag->body_offset = ecs_strbuf_written(&frag->buf); frag->state = HttpFragStateBody; } else { frag->state = HttpFragStateDone; } } else { frag->state = HttpFragStateHeaderStart; } break; case HttpFragStateBody: { ecs_strbuf_appendch(&frag->buf, c); if ((ecs_strbuf_written(&frag->buf) - frag->body_offset) == frag->content_length) { frag->state = HttpFragStateDone; } } break; case HttpFragStateDone: break; } } if (frag->state == HttpFragStateDone) { return true; } else { return false; } } static ecs_http_send_request_t* http_send_queue_post( ecs_http_server_t *srv) { /* This function should only be called while the server is locked. Before * the lock is released, the returned element should be populated. */ ecs_http_send_queue_t *sq = &srv->send_queue; 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; /* Write headers */ ecs_size_t written = http_send(sock, headers, headers_length, 0); if (written != headers_length) { ecs_err("http: failed to write HTTP response headers: %s", ecs_os_strerror(errno)); ecs_os_linc(&ecs_http_send_error_count); error = true; } else if (content_length >= 0) { /* Write content */ written = http_send(sock, content, content_length, 0); if (written != content_length) { ecs_err("http: failed to write HTTP response body: %s", ecs_os_strerror(errno)); ecs_os_linc(&ecs_http_send_error_count); error = true; } } if (!error) { ecs_os_linc(&ecs_http_send_ok_count); } http_close(&sock); } else { ecs_err("http: invalid socket\n"); } ecs_os_free(content); ecs_os_free(headers); } } return NULL; } static void http_append_send_headers( ecs_strbuf_t *hdrs, int code, const char* status, const char* content_type, ecs_strbuf_t *extra_headers, ecs_size_t content_len, bool preflight) { ecs_strbuf_appendlit(hdrs, "HTTP/1.1 "); ecs_strbuf_appendint(hdrs, code); ecs_strbuf_appendch(hdrs, ' '); ecs_strbuf_appendstr(hdrs, status); ecs_strbuf_appendlit(hdrs, "\r\n"); if (content_type) { ecs_strbuf_appendlit(hdrs, "Content-Type: "); ecs_strbuf_appendstr(hdrs, content_type); ecs_strbuf_appendlit(hdrs, "\r\n"); } if (content_len >= 0) { ecs_strbuf_appendlit(hdrs, "Content-Length: "); ecs_strbuf_append(hdrs, "%d", content_len); ecs_strbuf_appendlit(hdrs, "\r\n"); } ecs_strbuf_appendlit(hdrs, "Access-Control-Allow-Origin: *\r\n"); if (preflight) { ecs_strbuf_appendlit(hdrs, "Access-Control-Allow-Private-Network: true\r\n"); ecs_strbuf_appendlit(hdrs, "Access-Control-Allow-Methods: GET, PUT, OPTIONS\r\n"); ecs_strbuf_appendlit(hdrs, "Access-Control-Max-Age: 600\r\n"); } ecs_strbuf_mergebuff(hdrs, extra_headers); ecs_strbuf_appendlit(hdrs, "\r\n"); } static void http_send_reply( ecs_http_connection_impl_t* conn, ecs_http_reply_t* reply, bool preflight) { ecs_strbuf_t hdrs = ECS_STRBUF_INIT; char *content = ecs_strbuf_get(&reply->body); int32_t content_length = reply->body.length - 1; /* Use asynchronous send queue for outgoing data so send operations won't * hold up main thread */ ecs_http_send_request_t *req = NULL; if (!preflight) { req = http_send_queue_post(conn->pub.server); if (!req) { reply->code = 503; /* queue full, server is busy */ ecs_os_linc(&ecs_http_busy_count); } } http_append_send_headers(&hdrs, reply->code, reply->status, reply->content_type, &reply->headers, content_length, preflight); char *headers = ecs_strbuf_get(&hdrs); ecs_size_t headers_length = ecs_strbuf_written(&hdrs); if (!req) { ecs_size_t written = http_send(conn->sock, headers, headers_length, 0); if (written != headers_length) { ecs_err("http: failed to send reply to '%s:%s': %s", conn->pub.host, conn->pub.port, ecs_os_strerror(errno)); ecs_os_linc(&ecs_http_send_error_count); } ecs_os_free(content); ecs_os_free(headers); http_close(&conn->sock); return; } /* Second, enqueue send request for response body */ req->sock = conn->sock; req->headers = headers; req->header_length = headers_length; req->content = content; req->content_length = content_length; /* Take ownership of values */ reply->body.content = NULL; conn->sock = HTTP_SOCKET_INVALID; } static bool 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}; 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 */ return true; } 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 = 200; reply.content_type = NULL; 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); } } return true; } else { ecs_os_linc(&ecs_http_request_invalid_count); } } return false; } 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); /* Create new connection */ ecs_os_mutex_lock(srv->lock); ecs_http_connection_impl_t *conn = flecs_sparse_add_t( &srv->connections, ecs_http_connection_impl_t); uint64_t conn_id = conn->pub.id = flecs_sparse_last_id(&srv->connections); conn->pub.server = srv; conn->sock = sock_conn; ecs_os_mutex_unlock(srv->lock); char *remote_host = conn->pub.host; char *remote_port = conn->pub.port; /* Fetch name & port info */ if (http_getnameinfo((struct sockaddr*) remote_addr, remote_addr_len, remote_host, ECS_SIZEOF(conn->pub.host), remote_port, ECS_SIZEOF(conn->pub.port), NI_NUMERICHOST | NI_NUMERICSERV)) { ecs_os_strcpy(remote_host, "unknown"); ecs_os_strcpy(remote_port, "unknown"); } ecs_dbg_2("http: connection established from '%s:%s' (socket %u)", remote_host, remote_port, sock_conn); return (http_conn_res_t){ .conn = conn, .id = conn_id }; } static void http_accept_connections( ecs_http_server_t* srv, const struct sockaddr* addr, ecs_size_t addr_len) { #ifdef ECS_TARGET_WINDOWS /* If on Windows, test if winsock needs to be initialized */ SOCKET testsocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (SOCKET_ERROR == testsocket && WSANOTINITIALISED == WSAGetLastError()) { WSADATA data = { 0 }; int result = WSAStartup(MAKEWORD(2, 2), &data); if (result) { ecs_warn("http: WSAStartup failed with GetLastError = %d\n", GetLastError()); return; } } else { http_close(&testsocket); } #endif /* Resolve name + port (used for logging) */ char addr_host[256]; char addr_port[20]; ecs_http_socket_t sock = HTTP_SOCKET_INVALID; ecs_assert(srv->sock == HTTP_SOCKET_INVALID, ECS_INTERNAL_ERROR, NULL); if (http_getnameinfo( addr, addr_len, addr_host, ECS_SIZEOF(addr_host), addr_port, ECS_SIZEOF(addr_port), NI_NUMERICHOST | NI_NUMERICSERV)) { ecs_os_strcpy(addr_host, "unknown"); ecs_os_strcpy(addr_port, "unknown"); } ecs_os_mutex_lock(srv->lock); if (srv->should_run) { ecs_dbg_2("http: initializing connection socket"); sock = socket(addr->sa_family, SOCK_STREAM, IPPROTO_TCP); if (!http_socket_is_valid(sock)) { ecs_err("http: unable to