diff --git a/code/vendors/flecs/flecs.c b/code/vendors/flecs/flecs.c index 0a01328..0ed25f0 100644 --- a/code/vendors/flecs/flecs.c +++ b/code/vendors/flecs/flecs.c @@ -1,3 +1,29 @@ +/** + * @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 @@ -26,7 +52,7 @@ #include /** - * @file entity_index.h + * @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 @@ -45,6 +71,7 @@ #define flecs_entities_remove(world, entity) flecs_sparse_remove(ecs_eis(world), 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_alive(ecs_eis(world), entity) #define flecs_entities_exists(world, entity) flecs_sparse_exists(ecs_eis(world), entity) #define flecs_entities_recycle(world) flecs_sparse_new_id(ecs_eis(world)) @@ -59,8 +86,8 @@ #endif /** - * @file stack_allocator.h - * @brief Data structure used for temporary small allocations. + * @file datastructures/stack_allocator.h + * @brief Stack allocator. */ #ifndef FLECS_STACK_ALLOCATOR_H @@ -92,6 +119,9 @@ void* flecs_stack_alloc( 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)) @@ -100,6 +130,9 @@ void* flecs_stack_calloc( 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)) @@ -107,6 +140,9 @@ 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) @@ -124,10 +160,7 @@ void flecs_stack_restore_cursor( /** * @file bitset.h - * @brief Bitset datastructure. - * - * Simple bitset implementation. The bitset allows for storage of arbitrary - * numbers of bits. + * @brief Bitset data structure. */ #ifndef FLECS_BITSET_H @@ -206,25 +239,6 @@ void flecs_bitset_swap( /** * @file switch_list.h * @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 needs to be created with min and max values, so that it can - * allocate an array of headers that can be directly indexed by the value. The - * values are stored in a contiguous array, which allows for the values to be - * iterated without having to follow the linked list nodes. - * - * 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. */ #ifndef FLECS_SWITCH_LIST_H @@ -364,9 +378,9 @@ extern const ecs_entity_t EcsFlag; #define ecs_world_t_tag invalid #define ecs_stage_t_tag invalid #define ecs_query_t_tag EcsQuery -#define ecs_rule_t_tag invalid +#define ecs_rule_t_tag EcsQuery #define ecs_table_t_tag invalid -#define ecs_filter_t_tag invalid +#define ecs_filter_t_tag EcsQuery #define ecs_observer_t_tag EcsObserver /* Mixin kinds */ @@ -435,9 +449,9 @@ typedef struct 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_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 */ }; @@ -448,8 +462,6 @@ struct ecs_data_t { typedef struct ecs_table_diff_t { ecs_type_t added; /* Components added between tables */ ecs_type_t removed; /* Components removed between tables */ - ecs_type_t on_set; /* OnSet from exposing/adding base components */ - ecs_type_t un_set; /* UnSet from hiding/removing base components */ } ecs_table_diff_t; /** Builder for table diff. The table diff type itself doesn't use ecs_vec_t to @@ -458,8 +470,6 @@ typedef struct ecs_table_diff_t { typedef struct ecs_table_diff_builder_t { ecs_vec_t added; ecs_vec_t removed; - ecs_vec_t on_set; - ecs_vec_t un_set; } ecs_table_diff_builder_t; /** Edge linked list (used to keep track of incoming edges) */ @@ -564,6 +574,23 @@ typedef struct flecs_bitset_term_t { 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. @@ -573,7 +600,7 @@ typedef struct ecs_query_table_match_t ecs_query_table_match_t; 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 */ + 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 */ @@ -590,9 +617,7 @@ struct ecs_query_table_match_t { 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_vector_t *sparse_columns; /* Column ids of sparse columns */ - ecs_vector_t *bitset_columns; /* Column ids with disabled flags */ + 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; @@ -676,16 +701,17 @@ struct ecs_query_t { /* 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_world_t *world; ecs_iterable_t iterable; ecs_poly_dtor_t dtor; - ecs_entity_t entity; /* Query-level allocators */ ecs_query_allocators_t allocators; @@ -694,7 +720,11 @@ struct ecs_query_t { /** All observers for a specific (component) id */ typedef struct ecs_event_id_record_t { /* Triggers for Self */ - ecs_map_t observers; /* map */ + 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 */ @@ -706,11 +736,6 @@ typedef struct ecs_event_id_record_t { int32_t observer_count; } ecs_event_id_record_t; -/** All observers for a specific event */ -typedef struct ecs_event_record_t { - ecs_map_t event_ids; /* map */ -} ecs_event_record_t; - /* World level allocators are for operations that are not multithreaded */ typedef struct ecs_world_allocators_t { ecs_map_params_t ptr; @@ -720,6 +745,7 @@ typedef struct ecs_world_allocators_t { 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; @@ -840,6 +866,7 @@ 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 { @@ -874,8 +901,9 @@ struct ecs_world_t { ecs_header_t hdr; /* -- Type metadata -- */ - ecs_map_t id_index; /* map */ - ecs_sparse_t *type_info; /* sparse */ + ecs_sparse_t id_index_lo; /* sparse */ + ecs_map_t id_index_hi; /* map */ + ecs_sparse_t type_info; /* sparse */ /* -- Cached handle to id records -- */ ecs_id_record_t *idr_wildcard; @@ -903,10 +931,10 @@ struct ecs_world_t { ecs_sparse_t *pending_tables; /* sparse */ /* Used to track when cache needs to be updated */ - ecs_monitor_set_t monitors; /* map */ + ecs_monitor_set_t monitors; /* map */ /* -- Systems -- */ - ecs_entity_t pipeline; /* Current pipeline */ + ecs_entity_t pipeline; /* Current pipeline */ /* -- Identifiers -- */ ecs_hashmap_t aliases; @@ -934,6 +962,9 @@ struct ecs_world_t { /* -- 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 */ @@ -1018,17 +1049,33 @@ ecs_table_cache_hdr_t* _flecs_table_cache_next( #define FLECS_ID_RECORD_H /* Payload for id cache */ -typedef struct ecs_table_record_t { +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 */ -} ecs_table_record_t; +}; /* 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. */ @@ -1040,6 +1087,14 @@ struct ecs_id_record_t { /* 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; @@ -1118,6 +1173,10 @@ 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); @@ -1132,6 +1191,27 @@ void flecs_fini_id_records( #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); @@ -1164,6 +1244,25 @@ void flecs_emit( ecs_world_t *stage, ecs_event_desc_t *desc); +bool flecs_default_observer_next_callback( + ecs_iter_t *it); + +void flecs_default_uni_observer_run_callback( + ecs_iter_t *it); + +void flecs_observers_invoke( + ecs_world_t *world, + ecs_map_t *observers, + ecs_iter_t *it, + ecs_table_t *table, + ecs_entity_t trav); + +void flecs_emit_propagate_invalidate( + ecs_world_t *world, + ecs_table_t *table, + int32_t offset, + int32_t count); + #endif /** @@ -1201,8 +1300,8 @@ bool flecs_iter_next_instanced( #endif /** - * @file table.h - * @brief Table functions. + * @file table.c + * @brief Table storage implementation. */ #ifndef FLECS_TABLE_H @@ -1404,6 +1503,11 @@ 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( @@ -1427,9 +1531,7 @@ void flecs_table_diff_build( ecs_table_diff_builder_t *builder, ecs_table_diff_t *diff, int32_t added_offset, - int32_t removed_offset, - int32_t on_set_offset, - int32_t un_set_offset); + int32_t removed_offset); void flecs_table_diff_build_noalloc( ecs_table_diff_builder_t *builder, @@ -1440,19 +1542,6 @@ void flecs_table_diff_build_noalloc( /** * @file poly.h * @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. */ #ifndef FLECS_POLY_H @@ -1657,8 +1746,8 @@ bool flecs_defer_purge( #endif /** - * @file world.h - * @brief World utilities. + * @file world.c + * @brief World-level API. */ #ifndef FLECS_WORLD_H @@ -1800,6 +1889,11 @@ void flecs_resume_readonly( #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. @@ -2000,8 +2094,8 @@ void ecs_qsort( #endif /** - * @file name_index.h - * @brief Data structure used for id <-> name lookups. + * @file datastructures/name_index.h + * @brief Data structure for resolving 64bit keys by string (name). */ #ifndef FLECS_NAME_INDEX_H @@ -2096,7 +2190,7 @@ void flecs_bootstrap_hierarchy(ecs_world_t *world); /* Mark an entity as being watched. This is used to trigger automatic rematching * when entities used in system expressions change their components. */ -void flecs_add_flag( +ecs_record_t* flecs_add_flag( ecs_world_t *world, ecs_entity_t entity, uint32_t flag); @@ -2111,7 +2205,7 @@ void flecs_notify_on_remove( ecs_table_t *other_table, int32_t row, int32_t count, - ecs_table_diff_t *diff); + const ecs_type_t *diff); void flecs_notify_on_set( ecs_world_t *world, @@ -2124,7 +2218,14 @@ void flecs_notify_on_set( int32_t flecs_relation_depth( const ecs_world_t *world, ecs_entity_t r, - ecs_table_t *table); + 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); //////////////////////////////////////////////////////////////////////////////// //// Query API @@ -2231,6 +2332,24 @@ uint64_t _flecs_ito( #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 @@ -2253,16 +2372,6 @@ ecs_record_t flecs_to_row( uint64_t flecs_from_row( ecs_record_t record); -/* Get actual row from record row */ -uint32_t flecs_record_to_row( - uint32_t row, - bool *is_watched_out); - -/* Convert actual row to record row */ -uint32_t flecs_row_to_record( - uint32_t row, - bool is_watched); - /* Convert a symbol name to an entity name by removing the prefix */ const char* flecs_name_from_symbol( ecs_world_t *world, @@ -2304,9 +2413,26 @@ void _assert_func( void flecs_dump_backtrace( FILE *stream); +void ecs_colorize_buf( + char *msg, + bool enable_colors, + ecs_strbuf_t *buf); + bool flecs_isident( char ch); +int32_t flecs_search_relation_w_idr( + const ecs_world_t *world, + const ecs_table_t *table, + int32_t offset, + ecs_id_t id, + ecs_entity_t rel, + ecs_flags32_t flags, + ecs_entity_t *subject_out, + ecs_id_t *id_out, + struct ecs_table_record_t **tr_out, + ecs_id_record_t *idr); + #endif @@ -2395,6 +2521,9 @@ void flecs_table_check_sanity(ecs_table_t *table) { ECS_INTERNAL_ERROR, NULL); } } + + ecs_assert((table->observed_count == 0) || + (table->flags & EcsTableHasObserved), ECS_INTERNAL_ERROR, NULL); } #else #define flecs_table_check_sanity(table) @@ -2986,12 +3115,7 @@ void flecs_table_notify_on_remove( { int32_t count = data->entities.count; if (count) { - ecs_table_diff_t diff = { - .removed = table->type, - .un_set = table->type - }; - - flecs_notify_on_remove(world, table, NULL, 0, count, &diff); + flecs_notify_on_remove(world, table, NULL, 0, count, &table->type); } } @@ -3137,6 +3261,12 @@ void flecs_dtor_all_components( (void)records; + if (is_delete && table->observed_count) { + /* If table contains monitored entities with acyclic relationships, + * make sure to invalidate observer cache */ + flecs_emit_propagate_invalidate(world, table, row, count); + } + /* If table has components with destructors, iterate component columns */ if (table->flags & EcsTableHasDtors) { /* Throw up a lock just to be sure */ @@ -3237,9 +3367,7 @@ void flecs_table_fini_data( return; } - ecs_flags32_t flags = table->flags; - - if (do_on_remove && (flags & EcsTableHasOnRemove)) { + if (do_on_remove) { flecs_table_notify_on_remove(world, table, data); } @@ -3295,6 +3423,7 @@ void flecs_table_fini_data( } table->observed_count = 0; + table->flags &= ~EcsTableHasObserved; } /* Cleanup, no OnRemove, don't update entity index, don't deactivate table */ @@ -3468,6 +3597,19 @@ void flecs_table_reset( 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, @@ -3741,9 +3883,7 @@ int32_t flecs_table_grow_data( /* Initialize entity ids and record ptrs */ int32_t i; if (ids) { - for (i = 0; i < to_add; i ++) { - e[i] = ids[i]; - } + ecs_os_memcpy_n(e, ids, ecs_entity_t, to_add); } else { ecs_os_memset(e, 0, ECS_SIZEOF(ecs_entity_t) * to_add); } @@ -3756,6 +3896,8 @@ int32_t flecs_table_grow_data( 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 */ @@ -4225,11 +4367,11 @@ int32_t flecs_table_appendn( 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; } @@ -4414,6 +4556,7 @@ void flecs_merge_column( 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; @@ -4429,14 +4572,19 @@ void flecs_merge_column( * src into the dst. */ } else { int32_t src_count = src->count; - ecs_vec_set_count(&world->allocator, - dst, size, dst_count + src_count); - /* Construct new values */ if (ti) { - flecs_ctor_component(ti, dst, dst_count, src_count); + 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; @@ -4484,13 +4632,14 @@ void flecs_merge_table_data( /* Merge entities */ flecs_merge_column(world, &dst_data->entities, &src_data->entities, - ECS_SIZEOF(ecs_entity_t), NULL); + 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); + 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]; @@ -4500,8 +4649,10 @@ void flecs_merge_table_data( ecs_assert(size != 0, ECS_INTERNAL_ERROR, NULL); if (dst_id == src_id) { - flecs_merge_column(world, &dst[i_new], &src[i_old], size, dst_ti); + 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 ++; @@ -4623,8 +4774,10 @@ void flecs_table_merge( flecs_table_set_empty(world, dst_table); } flecs_table_set_empty(world, src_table); - dst_table->observed_count += src_table->observed_count; - src_table->observed_count = 0; + + 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); @@ -4819,8 +4972,9 @@ error: } void* ecs_table_get_column( - ecs_table_t *table, - int32_t index) + 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); @@ -4830,7 +4984,13 @@ void* ecs_table_get_column( return NULL; } - return table->data.columns[storage_index].array; + 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; } @@ -4840,6 +5000,10 @@ int32_t ecs_table_get_index( 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; @@ -4851,6 +5015,47 @@ int32_t ecs_table_get_index( } 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( @@ -4889,6 +5094,24 @@ int32_t flecs_table_observed_count( 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[] = { [EcsMixinBase] = "base (should never be requested by application)", @@ -4920,8 +5143,8 @@ ecs_mixins_t ecs_stage_t_mixins = { ecs_mixins_t ecs_query_t_mixins = { .type_name = "ecs_query_t", .elems = { - [EcsMixinWorld] = offsetof(ecs_query_t, world), - [EcsMixinEntity] = offsetof(ecs_query_t, entity), + [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) } @@ -4930,8 +5153,8 @@ ecs_mixins_t ecs_query_t_mixins = { ecs_mixins_t ecs_observer_t_mixins = { .type_name = "ecs_observer_t", .elems = { - [EcsMixinWorld] = offsetof(ecs_observer_t, world), - [EcsMixinEntity] = offsetof(ecs_observer_t, entity), + [EcsMixinWorld] = offsetof(ecs_observer_t, filter.world), + [EcsMixinEntity] = offsetof(ecs_observer_t, filter.entity), [EcsMixinDtor] = offsetof(ecs_observer_t, dtor) } }; @@ -4939,7 +5162,10 @@ ecs_mixins_t ecs_observer_t_mixins = { ecs_mixins_t ecs_filter_t_mixins = { .type_name = "ecs_filter_t", .elems = { - [EcsMixinIterable] = offsetof(ecs_filter_t, iterable) + [EcsMixinWorld] = offsetof(ecs_filter_t, world), + [EcsMixinEntity] = offsetof(ecs_filter_t, entity), + [EcsMixinIterable] = offsetof(ecs_filter_t, iterable), + [EcsMixinDtor] = offsetof(ecs_filter_t, dtor) } }; @@ -5143,23 +5369,31 @@ const ecs_world_t* ecs_get_world( 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); } -#include +/** + * @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. + */ -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, - ecs_table_diff_t *diff, - bool run_on_set); +#include static const ecs_entity_t* flecs_bulk_new( @@ -5179,7 +5413,7 @@ typedef struct { } flecs_component_ptr_t; static -flecs_component_ptr_t get_component_w_index( +flecs_component_ptr_t flecs_get_component_w_index( ecs_table_t *table, int32_t column_index, int32_t row) @@ -5196,7 +5430,7 @@ error: } static -flecs_component_ptr_t get_component_ptr( +flecs_component_ptr_t flecs_get_component_ptr( const ecs_world_t *world, ecs_table_t *table, int32_t row, @@ -5219,23 +5453,23 @@ flecs_component_ptr_t get_component_ptr( return (flecs_component_ptr_t){0}; } - return get_component_w_index(table, tr->column, row); + return flecs_get_component_w_index(table, tr->column, row); error: return (flecs_component_ptr_t){0}; } static -void* get_component( +void* flecs_get_component( const ecs_world_t *world, ecs_table_t *table, int32_t row, ecs_id_t id) { - return get_component_ptr(world, table, row, id).ptr; + return flecs_get_component_ptr(world, table, row, id).ptr; } static -void* get_base_component( +void* flecs_get_base_component( const ecs_world_t *world, ecs_table_t *table, ecs_id_t id, @@ -5291,11 +5525,11 @@ void* get_base_component( } if (!tr) { - ptr = get_base_component(world, table, id, table_index, + ptr = flecs_get_base_component(world, table, id, table_index, recur_depth + 1); } else { int32_t row = ECS_RECORD_TO_ROW(r->row); - ptr = get_component_w_index(table, tr->column, row).ptr; + ptr = flecs_get_component_w_index(table, tr->column, row).ptr; } } while (!ptr && (i < end)); @@ -5304,37 +5538,6 @@ error: return NULL; } -static -void flecs_notify( - ecs_world_t *world, - ecs_table_t *table, - ecs_table_t *other_table, - int32_t row, - int32_t count, - ecs_entity_t event, - ecs_type_t *ids, - ecs_entity_t relationship) -{ - flecs_emit(world, world, &(ecs_event_desc_t){ - .event = event, - .ids = ids, - .table = table, - .other_table = other_table, - .offset = row, - .count = count, - .observable = world, - .relationship = relationship - }); -} - -static -void flecs_instantiate( - ecs_world_t *world, - ecs_entity_t base, - ecs_table_t *table, - int32_t row, - int32_t count); - static void flecs_instantiate_slot( ecs_world_t *world, @@ -5631,7 +5834,6 @@ error: return; } -static void flecs_instantiate( ecs_world_t *world, ecs_entity_t base, @@ -5644,12 +5846,10 @@ void flecs_instantiate( /* Don't instantiate children from base entities that aren't prefabs */ return; } - - ecs_run_aperiodic(world, EcsAperiodicEmptyTables); ecs_id_record_t *idr = flecs_id_record_get(world, ecs_childof(base)); ecs_table_cache_iter_t it; - if (idr && flecs_table_cache_iter((ecs_table_cache_t*)idr, &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( @@ -5658,208 +5858,13 @@ void flecs_instantiate( } } -static -bool override_component( - ecs_world_t *world, - ecs_entity_t component, - ecs_type_t type, - ecs_table_t *table, - ecs_table_t *other_table, - const ecs_type_info_t *ti, - ecs_vec_t *column, - int32_t row, - int32_t count, - bool notify_on_set); - -static -bool override_from_base( - ecs_world_t *world, - ecs_entity_t base, - ecs_entity_t component, - ecs_table_t *table, - ecs_table_t *other_table, - const ecs_type_info_t *ti, - ecs_vec_t *column, - int32_t row, - int32_t count, - bool notify_on_set) -{ - ecs_assert(component != 0, ECS_INTERNAL_ERROR, NULL); - - ecs_record_t *r = flecs_entities_get(world, base); - ecs_table_t *base_table; - if (!r || !(base_table = r->table)) { - return false; - } - - void *base_ptr = get_component( - world, base_table, ECS_RECORD_TO_ROW(r->row), component); - if (base_ptr) { - int32_t index, data_size = ti->size; - void *data_ptr = ecs_vec_get(column, data_size, row); - - ecs_copy_t copy = ti->hooks.copy; - if (copy) { - for (index = 0; index < count; index ++) { - copy(data_ptr, base_ptr, 1, ti); - data_ptr = ECS_OFFSET(data_ptr, data_size); - } - } else { - for (index = 0; index < count; index ++) { - ecs_os_memcpy(data_ptr, base_ptr, data_size); - data_ptr = ECS_OFFSET(data_ptr, data_size); - } - } - - ecs_type_t ids = { - .array = (ecs_id_t[]){ component }, - .count = 1 - }; - - if (notify_on_set) { - /* Check if the component was available for the previous table. If - * the override is caused by an add operation, it does not introduce - * a new component value, and the application should not be - * notified. - * - * If the override is the result if adding a IsA relationship - * with an entity that has components with the OVERRIDE flag, an - * event should be generated, since this represents a new component - * (and component value) for the entity. - * - * Note that this is an edge case, regular (self) triggers won't be - * notified because the event id is not the component but an IsA - * relationship. Superset triggers will not be invoked because the - * component is owned. */ - int32_t c = ecs_search_relation(world, other_table, 0, component, - EcsIsA, EcsUp, 0, 0, 0); - if (c == -1) { - flecs_notify( - world, table, other_table, row, count, EcsOnSet, &ids, 0); - } - } - - return true; - } else { - /* If component not found on base, check if base itself inherits */ - ecs_type_t base_type = base_table->type; - return override_component(world, component, base_type, table, - other_table, ti, column, row, count, notify_on_set); - } -} - -static -bool override_component( - ecs_world_t *world, - ecs_entity_t component, - ecs_type_t type, - ecs_table_t *table, - ecs_table_t *other_table, - const ecs_type_info_t *ti, - ecs_vec_t *column, - int32_t row, - int32_t count, - bool notify_on_set) -{ - ecs_entity_t *type_array = type.array; - int32_t i, type_count = type.count; - - /* Walk prefabs */ - i = type_count - 1; - do { - ecs_entity_t e = type_array[i]; - - if (!(e & ECS_ID_FLAGS_MASK)) { - break; - } - - if (ECS_HAS_RELATION(e, EcsIsA)) { - if (override_from_base(world, ecs_pair_second(world, e), component, - table, other_table, ti, column, row, count, notify_on_set)) - { - return true; - } - } - } while (--i >= 0); - - return false; -} - -static -void components_override( - ecs_world_t *world, - ecs_table_t *table, - ecs_table_t *other_table, - int32_t row, - int32_t count, - ecs_type_t *added, - bool notify_on_set) -{ - ecs_vec_t *columns = table->data.columns; - ecs_type_t type = table->type; - ecs_table_t *storage_table = table->storage_table; - - int i; - for (i = 0; i < added->count; i ++) { - ecs_entity_t id = added->array[i]; - - if (ECS_HAS_RELATION(id, EcsIsA)) { - ecs_entity_t base = ECS_PAIR_SECOND(id); - - /* Cannot inherit from base if base is final */ - ecs_check(!ecs_has_id(world, ecs_get_alive(world, base), EcsFinal), - ECS_CONSTRAINT_VIOLATED, NULL); - ecs_check(base != 0, ECS_INVALID_PARAMETER, NULL); - - if (!world->stages[0].base) { - /* Setting base prevents instantiating the hierarchy multiple - * times. The instantiate function recursively iterates the - * hierarchy to instantiate children. While this is happening, - * new tables are created which end up calling this function, - * which would call instantiate multiple times for the same - * level in the hierarchy. */ - world->stages[0].base = base; - flecs_instantiate(world, base, table, row, count); - world->stages[0].base = 0; - } - } - - if (!storage_table) { - continue; - } - - ecs_table_record_t *tr = flecs_table_record_get( - world, storage_table, id); - if (!tr) { - continue; - } - - ecs_id_record_t *idr = (ecs_id_record_t*)tr->hdr.cache; - if (idr->flags & EcsIdDontInherit) { - continue; - } - - const ecs_type_info_t *ti = idr->type_info; - if (!ti->size) { - continue; - } - - ecs_vec_t *column = &columns[tr->column]; - override_component(world, id, type, table, other_table, ti, - column, row, count, notify_on_set); - } -error: - return; -} - static void flecs_set_union( ecs_world_t *world, ecs_table_t *table, int32_t row, int32_t count, - ecs_type_t *ids, - bool reset) + const ecs_type_t *ids) { ecs_id_t *array = ids->array; int32_t i, id_count = ids->count; @@ -5880,9 +5885,7 @@ void flecs_set_union( int32_t column = tr->column - table->sw_offset; ecs_switch_t *sw = &table->data.sw_columns[column]; ecs_entity_t union_case = 0; - if (!reset) { - union_case = ECS_PAIR_SECOND(id); - } + union_case = ECS_PAIR_SECOND(id); int32_t r; for (r = 0; r < count; r ++) { @@ -5893,20 +5896,63 @@ void flecs_set_union( } static -void flecs_add_remove_union( +void flecs_notify_on_add( ecs_world_t *world, ecs_table_t *table, + ecs_table_t *other_table, int32_t row, int32_t count, - ecs_type_t *added, - ecs_type_t *removed) + const ecs_type_t *added, + ecs_flags32_t flags) { - if (added) { - flecs_set_union(world, table, row, count, added, false); + 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 + }); } - if (removed) { - flecs_set_union(world, table, row, count, removed, true); - } } static @@ -5914,34 +5960,22 @@ ecs_record_t* flecs_new_entity( ecs_world_t *world, ecs_entity_t entity, ecs_record_t *record, - ecs_table_t *new_table, + ecs_table_t *table, ecs_table_diff_t *diff, - bool construct, - bool notify_on_set) + bool ctor, + ecs_flags32_t evt_flags) { - int32_t new_row; - if (!record) { record = flecs_entities_ensure(world, entity); } - new_row = flecs_table_append(world, new_table, entity, record, - construct, true); + 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); - record->table = new_table; - record->row = ECS_ROW_TO_RECORD(new_row, record->row & ECS_ROW_FLAGS_MASK); - - ecs_data_t *new_data = &new_table->data; - ecs_assert(ecs_vec_count(&new_data[0].entities) > new_row, + ecs_assert(ecs_vec_count(&table->data.entities) > row, ECS_INTERNAL_ERROR, NULL); - (void)new_data; - - ecs_flags32_t flags = new_table->flags; - - if (flags & EcsTableHasAddActions) { - flecs_notify_on_add( - world, new_table, NULL, new_row, 1, diff, notify_on_set); - } + flecs_notify_on_add(world, table, NULL, row, 1, &diff->added, evt_flags); return record; } @@ -5953,47 +5987,42 @@ void flecs_move_entity( ecs_record_t *record, ecs_table_t *dst_table, ecs_table_diff_t *diff, - bool construct, - bool notify_on_set) + 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); + 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); - /* Copy entity & components from src_table to dst_table */ - if (src_table->type.count) { - flecs_notify_on_remove(world, src_table, dst_table, - ECS_RECORD_TO_ROW(src_row), 1, diff); + /* Invoke remove actions for removed components */ + flecs_notify_on_remove( + world, src_table, dst_table, src_row, 1, &diff->removed); - flecs_table_move(world, entity, entity, dst_table, dst_row, - src_table, src_row, construct); - } + /* 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); - - /* If components were added, invoke add actions */ - ecs_flags32_t dst_flags = dst_table->flags; - if (src_table != dst_table || diff->added.count) { - if (diff->added.count && (dst_flags & EcsTableHasAddActions)) { - flecs_notify_on_add(world, dst_table, src_table, dst_row, 1, diff, - notify_on_set); - } - } + flecs_notify_on_add( + world, dst_table, src_table, dst_row, 1, &diff->added, evt_flags); error: return; @@ -6009,10 +6038,7 @@ void flecs_delete_entity( int32_t row = ECS_RECORD_TO_ROW(record->row); /* Invoke remove actions before deleting */ - if (table->flags & EcsTableHasRemoveActions) { - flecs_notify_on_remove(world, table, NULL, row, 1, diff); - } - + flecs_notify_on_remove(world, table, NULL, row, 1, &diff->removed); flecs_table_delete(world, table, row, true); } @@ -6023,7 +6049,7 @@ void flecs_delete_entity( * 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 update_component_monitor_w_array( +void flecs_update_component_monitor_w_array( ecs_world_t *world, ecs_type_t *ids) { @@ -6044,13 +6070,13 @@ void update_component_monitor_w_array( } static -void update_component_monitors( +void flecs_update_component_monitors( ecs_world_t *world, ecs_type_t *added, ecs_type_t *removed) { - update_component_monitor_w_array(world, added); - update_component_monitor_w_array(world, removed); + flecs_update_component_monitor_w_array(world, added); + flecs_update_component_monitor_w_array(world, removed); } static @@ -6061,48 +6087,51 @@ void flecs_commit( ecs_table_t *dst_table, ecs_table_diff_t *diff, bool construct, - bool notify_on_set) + 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; - bool observed = false; + int observed = 0; if (record) { src_table = record->table; row_flags = record->row & ECS_ROW_FLAGS_MASK; - observed = row_flags & EcsEntityObservedAcyclic; + observed = (row_flags & EcsEntityObservedAcyclic) != 0; } if (src_table == dst_table) { /* If source and destination table are the same no action is needed * * However, if a component was added in the process of traversing a - * table, this suggests that a case switch could have occured. */ - if (((diff->added.count) || (diff->removed.count)) && src_table) { + * 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, notify_on_set); + 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); - dst_table->observed_count += observed; + 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, notify_on_set); + construct, evt_flags); } else { flecs_delete_entity(world, record, diff); record->table = NULL; } - src_table->observed_count -= observed; + flecs_table_observer_add(src_table, -observed); } else { - dst_table->observed_count += observed; + flecs_table_observer_add(dst_table, observed); if (dst_table->type.count) { flecs_new_entity(world, entity, record, dst_table, diff, - construct, notify_on_set); + construct, evt_flags); } } @@ -6112,7 +6141,7 @@ void flecs_commit( * update the matched tables when the application adds or removes a * component from, for example, a container. */ if (row_flags) { - update_component_monitors(world, &diff->added, &diff->removed); + flecs_update_component_monitors(world, &diff->added, &diff->removed); } if ((!src_table || !src_table->type.count) && world->range_check_enabled) { @@ -6121,7 +6150,9 @@ void flecs_commit( ecs_check(entity >= world->info.min_id, ECS_OUT_OF_RANGE, 0); } + error: + flecs_journal_end(); return; } @@ -6175,9 +6206,8 @@ const ecs_entity_t* flecs_bulk_new( } flecs_defer_begin(world, &world->stages[0]); - - flecs_notify_on_add(world, table, NULL, row, count, diff, - component_data == NULL); + flecs_notify_on_add(world, table, NULL, row, count, &diff->added, + (component_data == NULL) ? 0 : EcsEventNoOnSet); if (component_data) { /* Set components that we're setting in the component mask so the init @@ -6217,7 +6247,6 @@ const ecs_entity_t* flecs_bulk_new( }; flecs_notify_on_set(world, table, row, count, NULL, true); - flecs_notify_on_set(world, table, row, count, &diff->on_set, false); } flecs_defer_end(world, &world->stages[0]); @@ -6253,8 +6282,8 @@ void flecs_add_id_w_record( world, src_table, &id, &diff); flecs_commit(world, entity, record, dst_table, &diff, construct, - false); /* notify_on_set = false, this function is only called from - * functions that are about to set the component. */ + EcsEventNoOnSet); /* No OnSet, this function is only called from + * functions that are about to set the component. */ } static @@ -6275,7 +6304,7 @@ void flecs_add_id( ecs_table_t *dst_table = flecs_table_traverse_add( world, src_table, &id, &diff); - flecs_commit(world, entity, r, dst_table, &diff, true, true); + flecs_commit(world, entity, r, dst_table, &diff, true, 0); flecs_defer_end(world, stage); } @@ -6302,7 +6331,7 @@ void flecs_remove_id( ecs_table_t *dst_table = flecs_table_traverse_remove( world, src_table, &id, &diff); - flecs_commit(world, entity, r, dst_table, &diff, true, true); + flecs_commit(world, entity, r, dst_table, &diff, true, 0); done: flecs_defer_end(world, stage); @@ -6317,14 +6346,15 @@ flecs_component_ptr_t flecs_get_mut( { flecs_component_ptr_t dst = {0}; - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); + 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 = get_component_ptr(world, r->table, ECS_RECORD_TO_ROW(r->row), id); + dst = flecs_get_component_ptr( + world, r->table, ECS_RECORD_TO_ROW(r->row), id); } if (!dst.ptr) { @@ -6332,12 +6362,13 @@ flecs_component_ptr_t flecs_get_mut( flecs_add_id_w_record(world, entity, r, id, true); /* Flush commands so the pointer we're fetching is stable */ - ecs_defer_end(world); - ecs_defer_begin(world); + 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 = get_component_ptr(world, r->table, ECS_RECORD_TO_ROW(r->row), id); + dst = flecs_get_component_ptr( + world, r->table, ECS_RECORD_TO_ROW(r->row), id); return dst; } @@ -6347,69 +6378,6 @@ error: } /* -- Private functions -- */ -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, - ecs_table_diff_t *diff, - bool run_on_set) -{ - ecs_assert(diff != NULL, ECS_INTERNAL_ERROR, NULL); - - if (diff->added.count) { - if (table->flags & EcsTableHasIsA) { - components_override(world, table, other_table, row, count, - &diff->added, run_on_set); - } - - if (table->flags & EcsTableHasUnion) { - flecs_add_remove_union(world, table, row, count, &diff->added, NULL); - } - - if (table->flags & EcsTableHasOnAdd) { - flecs_notify(world, table, other_table, row, count, EcsOnAdd, - &diff->added, 0); - } - } - - /* When a IsA relationship is added to an entity, that entity inherits the - * components from the base. Send OnSet notifications so that an application - * can respond to these new components. */ - if (run_on_set && diff->on_set.count) { - flecs_notify(world, table, other_table, row, count, EcsOnSet, &diff->on_set, - EcsIsA); - } -} - -void flecs_notify_on_remove( - ecs_world_t *world, - ecs_table_t *table, - ecs_table_t *other_table, - int32_t row, - int32_t count, - ecs_table_diff_t *diff) -{ - ecs_assert(diff != NULL, ECS_INTERNAL_ERROR, NULL); - - if (count) { - if (diff->un_set.count) { - flecs_notify(world, table, other_table, row, count, EcsUnSet, &diff->un_set, 0); - } - - if (table->flags & EcsTableHasOnRemove && diff->removed.count) { - flecs_notify(world, table, other_table, row, count, EcsOnRemove, - &diff->removed, 0); - } - - if (table->flags & EcsTableHasIsA && diff->on_set.count) { - flecs_notify(world, table, other_table, row, count, EcsOnSet, - &diff->on_set, 0); - } - } -} void flecs_notify_on_set( ecs_world_t *world, @@ -6476,26 +6444,18 @@ void flecs_notify_on_set( /* Run OnSet notifications */ if (table->flags & EcsTableHasOnSet && ids->count) { - flecs_notify(world, table, NULL, row, count, EcsOnSet, ids, 0); + flecs_emit(world, world, &(ecs_event_desc_t) { + .event = EcsOnSet, + .ids = ids, + .table = table, + .offset = row, + .count = count, + .observable = world + }); } } -uint32_t flecs_record_to_row( - uint32_t row, - bool *is_watched_out) -{ - *is_watched_out = (row & ECS_ROW_FLAGS_MASK) != 0; - return row & ECS_ROW_MASK; -} - -uint32_t flecs_row_to_record( - uint32_t row, - bool is_watched) -{ - return row | (EcsEntityObserved * is_watched); -} - -void flecs_add_flag( +ecs_record_t* flecs_add_flag( ecs_world_t *world, ecs_entity_t entity, uint32_t flag) @@ -6509,12 +6469,13 @@ void flecs_add_flag( if (!(record->row & flag)) { ecs_table_t *table = record->table; if (table) { - table->observed_count ++; + flecs_table_observer_add(table, 1); } } } record->row |= flag; } + return record; } @@ -6545,7 +6506,7 @@ bool ecs_commit( diff.added = *removed; } - flecs_commit(world, entity, record, table, &diff, true, true); + flecs_commit(world, entity, record, table, &diff, true, 0); return src_table != table; error: @@ -6607,6 +6568,8 @@ ecs_entity_t ecs_new_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; @@ -6849,19 +6812,6 @@ int flecs_traverse_add( } } - /* 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); - } - } - /* If a name is provided but not yet assigned, add the Name component */ if (name && !name_assigned) { table = flecs_find_table_add(world, table, @@ -6889,6 +6839,19 @@ int flecs_traverse_add( } } + /* 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 @@ -6906,12 +6869,12 @@ int flecs_traverse_add( /* Commit entity to destination table */ if (src_table != table) { - ecs_defer_begin(world); + 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, true); + flecs_commit(world, result, r, table, &table_diff, true, 0); flecs_table_diff_builder_fini(world, &diff); - ecs_defer_end(world); + flecs_defer_end(world, &world->stages[0]); } /* Set name */ @@ -6971,7 +6934,7 @@ void flecs_deferred_add_remove( bool defer = true; if (ECS_HAS_ID_FLAG(id, PAIR) && ECS_PAIR_FIRST(id) == EcsChildOf) { scope = ECS_PAIR_SECOND(id); - if (!desc->id || (name && !name_assigned)) { + 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; @@ -7235,9 +7198,12 @@ ecs_entity_t ecs_component_init( ecs_suspend_readonly_state_t readonly_state; world = flecs_suspend_readonly(world, &readonly_state); + bool new_component = true; ecs_entity_t result = desc->entity; if (!result) { result = ecs_new_low_id(world); + } else { + new_component = ecs_has(world, result, EcsComponent); } EcsComponent *ptr = ecs_get_mut(world, result, EcsComponent); @@ -7245,12 +7211,14 @@ ecs_entity_t ecs_component_init( ecs_assert(ptr->alignment == 0, ECS_INTERNAL_ERROR, NULL); ptr->size = desc->type.size; ptr->alignment = desc->type.alignment; - 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)); + if (!new_component || ptr->size != desc->type.size) { + if (!ptr->size) { + ecs_trace("#[green]tag#[reset] %s created", + ecs_get_name(world, result)); + } else { + ecs_trace("#[green]component#[reset] %s created", + ecs_get_name(world, result)); + } } } else { if (ptr->size != desc->type.size) { @@ -7342,17 +7310,16 @@ void ecs_clear( ecs_table_t *table = r->table; if (table) { - if (r->row & EcsEntityObservedAcyclic) { - table->observed_count --; - } - ecs_table_diff_t diff = { - .removed = table->type, - .un_set = { table->storage_ids, table->storage_count } + .removed = table->type }; flecs_delete_entity(world, r, &diff); r->table = NULL; + + if (r->row & EcsEntityObservedAcyclic) { + flecs_table_observer_add(table, -1); + } } flecs_defer_end(world, stage); @@ -7378,7 +7345,8 @@ static void flecs_marked_id_push( ecs_world_t *world, ecs_id_record_t* idr, - ecs_entity_t action) + ecs_entity_t action, + bool delete_id) { ecs_marked_id_t *m = ecs_vector_add(&world->store.marked_ids, ecs_marked_id_t); @@ -7386,6 +7354,7 @@ void flecs_marked_id_push( m->idr = idr; m->id = idr->id; m->action = action; + m->delete_id = delete_id; flecs_id_record_claim(world, idr); } @@ -7394,7 +7363,8 @@ static void flecs_id_mark_for_delete( ecs_world_t *world, ecs_id_record_t *idr, - ecs_entity_t action); + ecs_entity_t action, + bool delete_id); static void flecs_targets_mark_for_delete( @@ -7422,21 +7392,21 @@ void flecs_targets_mark_for_delete( if (flags & EcsEntityObservedId) { if ((idr = flecs_id_record_get(world, e))) { flecs_id_mark_for_delete(world, idr, - ECS_ID_ON_DELETE(idr->flags)); + 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)); + ECS_ID_ON_DELETE(idr->flags), true); } } if (flags & EcsEntityObservedTarget) { if ((idr = flecs_id_record_get(world, ecs_pair(EcsWildcard, e)))) { flecs_id_mark_for_delete(world, idr, - ECS_ID_ON_DELETE_OBJECT(idr->flags)); + 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)); + ECS_ID_ON_DELETE_OBJECT(idr->flags), true); } } } @@ -7478,7 +7448,7 @@ void flecs_update_monitors_for_delete( ecs_world_t *world, ecs_id_t id) { - update_component_monitors(world, NULL, &(ecs_type_t){ + flecs_update_component_monitors(world, NULL, &(ecs_type_t){ .array = (ecs_id_t[]){id}, .count = 1 }); @@ -7488,14 +7458,15 @@ static void flecs_id_mark_for_delete( ecs_world_t *world, ecs_id_record_t *idr, - ecs_entity_t action) + ecs_entity_t action, + bool delete_id) { if (idr->flags & EcsIdMarkedForDelete) { return; } idr->flags |= EcsIdMarkedForDelete; - flecs_marked_id_push(world, idr, action); + flecs_marked_id_push(world, idr, action, delete_id); ecs_id_t id = idr->id; @@ -7559,7 +7530,8 @@ static bool flecs_on_delete_mark( ecs_world_t *world, ecs_id_t id, - ecs_entity_t action) + ecs_entity_t action, + bool delete_id) { ecs_id_record_t *idr = flecs_id_record_get(world, id); if (!idr) { @@ -7582,7 +7554,7 @@ bool flecs_on_delete_mark( return false; } - flecs_id_mark_for_delete(world, idr, action); + flecs_id_mark_for_delete(world, idr, action, delete_id); return true; } @@ -7625,23 +7597,20 @@ void flecs_remove_from_table( if (!dst_table->type.count) { /* If this removes all components, clear table */ - ecs_dbg_3("#[red]clear#[reset] entities from table %u", - (uint32_t)table->id); flecs_table_clear_entities(world, table); } else { /* Otherwise, merge table into dst_table */ - ecs_dbg_3("#[red]move#[reset] entities from table %u to %u", - (uint32_t)table->id, (uint32_t)dst_table->id); - if (dst_table != table) { - if (diff.removed.count) { + 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, ecs_table_count(table), &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); } @@ -7685,8 +7654,8 @@ bool flecs_on_delete_clear_tables( } /* Run commands so children get notified before parent is deleted */ - ecs_defer_end(world); - ecs_defer_begin(world); + 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 */ @@ -7718,14 +7687,25 @@ bool flecs_on_delete_clear_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. */ - if (flecs_id_record_release(world, idr)) { - /* If the id record is still alive, release the initial claim */ + int32_t rc = flecs_id_record_release(world, idr); + ecs_assert(rc > 0, ECS_INTERNAL_ERROR, NULL); + (void)rc; + + if (delete_id) { + /* If id should be deleted, release initial claim. This happens when + * a component, tag, or part of a pair is deleted. */ flecs_id_record_release(world, idr); + } else { + /* If id should not be deleted, unmark id record for deletion. This + * happens when all instances *of* an id are deleted, for example + * when calling ecs_remove_all or ecs_delete_with. */ + idr->flags &= ~EcsIdMarkedForDelete; } } @@ -7736,7 +7716,8 @@ static void flecs_on_delete( ecs_world_t *world, ecs_id_t id, - ecs_entity_t action) + 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 @@ -7747,7 +7728,7 @@ void flecs_on_delete( ecs_run_aperiodic(world, EcsAperiodicEmptyTables); /* Collect all ids that need to be deleted */ - flecs_on_delete_mark(world, id, action); + flecs_on_delete_mark(world, id, action, delete_id); /* Only perform cleanup if we're the first stack frame doing it */ if (!count && ecs_vector_count(world->store.marked_ids)) { @@ -7787,26 +7768,34 @@ 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(world, stage, id, EcsDelete)) { return; } - flecs_on_delete(world, id, EcsDelete); + 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(world, stage, id, EcsRemove)) { return; } - flecs_on_delete(world, id, EcsRemove); + flecs_on_delete(world, id, EcsRemove, false); flecs_defer_end(world, stage); + + flecs_journal_end(); } void ecs_delete( @@ -7823,37 +7812,37 @@ void ecs_delete( ecs_record_t *r = flecs_entities_get(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 & EcsEntityObservedAcyclic) { table = r->table; if (table) { - table->observed_count --; + flecs_table_observer_add(table, -1); } } if (row_flags & EcsEntityObservedId) { - flecs_on_delete(world, entity, 0); - flecs_on_delete(world, ecs_pair(entity, EcsWildcard), 0); + flecs_on_delete(world, entity, 0, true); + flecs_on_delete(world, ecs_pair(entity, EcsWildcard), 0, true); } if (row_flags & EcsEntityObservedTarget) { - flecs_on_delete(world, ecs_pair(EcsFlag, entity), 0); - flecs_on_delete(world, ecs_pair(EcsWildcard, entity), 0); + flecs_on_delete(world, ecs_pair(EcsFlag, entity), 0, true); + flecs_on_delete(world, ecs_pair(EcsWildcard, entity), 0, true); + r->idr = NULL; } /* Merge operations before deleting entity */ - ecs_defer_end(world); - ecs_defer_begin(world); + flecs_defer_end(world, stage); + flecs_defer_begin(world, stage); } table = r->table; - /* If entity has components, remove them. Check if table is still alive, - * as delete actions could have deleted the table already. */ if (table) { ecs_table_diff_t diff = { - .removed = table->type, - .un_set = { table->storage_ids, table->storage_count } + .removed = table->type }; flecs_delete_entity(world, r, &diff); @@ -7863,6 +7852,8 @@ void ecs_delete( } flecs_entities_remove(world, entity); + + flecs_journal_end(); } flecs_defer_end(world, stage); @@ -7991,11 +7982,11 @@ const void* ecs_get_id( } if (!tr) { - return get_base_component(world, table, id, idr, 0); + return flecs_get_base_component(world, table, id, idr, 0); } int32_t row = ECS_RECORD_TO_ROW(r->row); - return get_component_w_index(table, tr->column, row).ptr; + return flecs_get_component_w_index(table, tr->column, row).ptr; error: return NULL; } @@ -8110,7 +8101,7 @@ const void* ecs_record_get_id( ecs_id_t id) { const ecs_world_t *world = ecs_get_world(stage); - return get_component(world, r->table, ECS_RECORD_TO_ROW(r->row), id); + return flecs_get_component(world, r->table, ECS_RECORD_TO_ROW(r->row), id); } void* ecs_record_get_mut_id( @@ -8119,7 +8110,7 @@ void* ecs_record_get_mut_id( ecs_id_t id) { const ecs_world_t *world = ecs_get_world(stage); - return get_component(world, r->table, ECS_RECORD_TO_ROW(r->row), id); + return flecs_get_component(world, r->table, ECS_RECORD_TO_ROW(r->row), id); } ecs_ref_t ecs_ref_init_id( @@ -8214,7 +8205,7 @@ void* ecs_ref_get_id( int32_t column = ecs_table_type_to_storage_index(table, tr->column); ecs_assert(column != -1, ECS_INTERNAL_ERROR, NULL); - return get_component_w_index(table, column, row).ptr; + return flecs_get_component_w_index(table, column, row).ptr; error: return NULL; } @@ -8242,7 +8233,7 @@ void* ecs_emplace_id( ecs_record_t *r = flecs_entities_ensure(world, entity); flecs_add_id_w_record(world, entity, r, id, false /* Add without ctor */); - void *ptr = get_component(world, r->table, ECS_RECORD_TO_ROW(r->row), id); + 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); @@ -8252,6 +8243,38 @@ 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_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_modified(world, 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, @@ -8618,8 +8641,26 @@ ecs_entity_t ecs_get_target_for_id( } } +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* get_identifier( +const char* flecs_get_identifier( const ecs_world_t *world, ecs_entity_t entity, ecs_entity_t tag) @@ -8643,14 +8684,19 @@ const char* ecs_get_name( const ecs_world_t *world, ecs_entity_t entity) { - return get_identifier(world, entity, EcsName); + return flecs_get_identifier(world, entity, EcsName); } const char* ecs_get_symbol( const ecs_world_t *world, ecs_entity_t entity) { - return get_identifier(world, entity, EcsSymbol); + 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 @@ -8801,7 +8847,9 @@ ecs_entity_t ecs_get_alive( /* Make sure id does not have generation. This guards against accidentally * "upcasting" a not alive identifier to a alive one. */ - ecs_assert((uint32_t)entity == entity, ECS_INVALID_PARAMETER, NULL); + if ((uint32_t)entity != entity) { + return 0; + } /* Make sure we're not working with a stage */ world = ecs_get_world(world); @@ -8862,9 +8910,13 @@ void ecs_ensure_id( 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 { @@ -8898,26 +8950,13 @@ ecs_table_t* ecs_get_table( world = ecs_get_world(world); ecs_record_t *record = flecs_entities_get(world, entity); - ecs_table_t *table; - if (record && (table = record->table)) { - return table; + if (record) { + return record->table; } error: return NULL; } -ecs_table_t* ecs_get_storage_table( - const ecs_world_t *world, - ecs_entity_t entity) -{ - ecs_table_t *table = ecs_get_table(world, entity); - if (table) { - return table->storage_table; - } - - return NULL; -} - const ecs_type_t* ecs_get_type( const ecs_world_t *world, ecs_entity_t entity) @@ -8960,7 +8999,7 @@ const ecs_type_info_t* ecs_get_type_info( if (idr) { return idr->type_info; } else if (!(id & ECS_ID_FLAGS_MASK)) { - return flecs_sparse_get(world->type_info, ecs_type_info_t, id); + return flecs_type_info_get(world, id); } error: return NULL; @@ -9313,17 +9352,6 @@ void flecs_discard_cmd( } } -static -bool flecs_is_entity_valid( - ecs_world_t *world, - ecs_entity_t e) -{ - if (ecs_exists(world, e) && !ecs_is_alive(world, e)) { - return false; - } - return true; -} - static bool flecs_remove_invalid( ecs_world_t *world, @@ -9331,15 +9359,15 @@ bool flecs_remove_invalid( ecs_id_t *id_out) { if (ECS_HAS_ID_FLAG(id, PAIR)) { - ecs_entity_t rel = ecs_pair_first(world, id); - if (!rel || !flecs_is_entity_valid(world, rel)) { + 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(world, id); - if (!obj || !flecs_is_entity_valid(world, obj)) { + 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)); @@ -9365,7 +9393,7 @@ bool flecs_remove_invalid( } } else { id &= ECS_COMPONENT_MASK; - if (!flecs_is_entity_valid(world, id)) { + 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; @@ -9388,9 +9416,6 @@ void flecs_cmd_batch_for_entity( ecs_table_t *table = NULL; if (r) { table = r->table; - } else if (!flecs_entities_is_alive(world, entity)) { - world->info.cmd.discard_count ++; - return; } world->info.cmd.batched_entity_count ++; @@ -9439,19 +9464,19 @@ void flecs_cmd_batch_for_entity( case EcsOpSet: case EcsOpMut: table = flecs_find_table_add(world, table, id, diff); - /* fallthrough */ + 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 */ - has_set = true; - world->info.cmd.batched_command_count ++; break; case EcsOpRemove: table = flecs_find_table_remove(world, table, id, diff); world->info.cmd.batched_command_count ++; break; case EcsOpClear: - table = NULL; + table = &world->store.root; world->info.cmd.batched_command_count ++; break; default: @@ -9467,9 +9492,9 @@ void flecs_cmd_batch_for_entity( /* Move entity to destination table in single operation */ flecs_table_diff_build_noalloc(diff, &table_diff); - ecs_defer_begin(world); - flecs_commit(world, entity, r, table, &table_diff, true, true); - ecs_defer_end(world); + 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 @@ -9485,8 +9510,7 @@ void flecs_cmd_batch_for_entity( } switch(cmd->kind) { case EcsOpSet: - case EcsOpMut: - case EcsOpEmplace: { + case EcsOpMut: { ecs_id_record_t *idr = cmd->idr; if (!idr) { idr = flecs_id_record_get(world, cmd->id); @@ -9512,8 +9536,8 @@ bool flecs_defer_end( ecs_world_t *world, ecs_stage_t *stage) { - ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_poly_assert(world, ecs_world_t); + ecs_poly_assert(stage, ecs_stage_t); if (stage->defer_suspend) { /* Defer suspending makes it possible to do operations on the storage @@ -9550,21 +9574,23 @@ bool flecs_defer_end( 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 */ - flecs_cmd_batch_for_entity(world, &diff, e, cmds, i); + if (is_alive) { + flecs_cmd_batch_for_entity(world, &diff, e, cmds, i); + } else { + world->info.cmd.discard_count ++; + } } /* If entity is no longer alive, this could be because the queue * contained both a delete and a subsequent add/remove/set which * should be ignored. */ ecs_cmd_kind_t kind = cmd->kind; - if ((kind == EcsOpSkip) || - (e && !ecs_is_alive(world, e) && - flecs_entities_exists(world, e))) - { + if ((kind == EcsOpSkip) || (e && !is_alive)) { world->info.cmd.discard_count ++; flecs_discard_cmd(world, cmd); continue; @@ -9616,9 +9642,7 @@ bool flecs_defer_end( world->info.cmd.get_mut_count ++; break; case EcsOpModified: - if (ecs_has_id(world, e, id)) { - ecs_modified_id(world, e, id); - } + flecs_modified_id_if(world, e, id); world->info.cmd.modified_count ++; break; case EcsOpDelete: { @@ -9631,7 +9655,7 @@ bool flecs_defer_end( world->info.cmd.clear_count ++; break; case EcsOpOnDeleteAction: - flecs_on_delete(world, id, e); + flecs_on_delete(world, id, e, false); world->info.cmd.other_count ++; break; case EcsOpEnable: @@ -9672,7 +9696,6 @@ bool flecs_defer_end( return true; } -error: return false; } @@ -9708,6 +9731,23 @@ 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( @@ -9792,7 +9832,7 @@ void flecs_stages_merge( if (force_merge || stage->auto_merge) { ecs_assert(stage->defer == 1, ECS_INVALID_OPERATION, "mismatching defer_begin/defer_end detected"); - ecs_defer_end((ecs_world_t*)stage); + flecs_defer_end(world, stage); } } else { /* Merge stages. Only merge if the stage has auto_merging turned on, or @@ -9810,14 +9850,14 @@ void flecs_stages_merge( flecs_eval_component_monitors(world); if (measure_frame_time) { - world->info.merge_time_total += (float)ecs_time_measure(&t_start); + 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) { - ecs_defer_begin((ecs_world_t*)stage); + flecs_defer_begin(world, stage); } ecs_log_pop_3(); @@ -9841,6 +9881,8 @@ 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_suspend) return false; return (++ stage->defer) == 1; @@ -10339,7 +10381,7 @@ bool ecs_readonly_begin( 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"); - ecs_defer_begin((ecs_world_t*)stage); + flecs_defer_begin(world, stage); } bool is_readonly = ECS_BIT_IS_SET(world->flags, EcsWorldReadonly); @@ -10448,7 +10490,7 @@ ecs_world_t* ecs_async_stage_new( stage->auto_merge = false; stage->async = true; - ecs_defer_begin((ecs_world_t*)stage); + flecs_defer_begin(world, stage); return (ecs_world_t*)stage; } @@ -10489,39 +10531,72 @@ 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) { - ecs_map_init(&a->sizes, ecs_block_allocator_t, NULL, 0); + flecs_ballocator_init_n(&a->chunks, ecs_block_allocator_t, + FLECS_SPARSE_CHUNK_SIZE); + flecs_sparse_init(&a->sizes, NULL, &a->chunks, ecs_block_allocator_t); } void flecs_allocator_fini( ecs_allocator_t *a) { - ecs_map_iter_t it = ecs_map_iter(&a->sizes); - ecs_block_allocator_t *ba; - while ((ba = ecs_map_next(&it, ecs_block_allocator_t, NULL))) { + int32_t i = 0, count = flecs_sparse_count(&a->sizes); + for (i = 0; i < count; i ++) { + ecs_block_allocator_t *ba = flecs_sparse_get_dense( + &a->sizes, ecs_block_allocator_t, i); flecs_ballocator_fini(ba); } - ecs_map_fini(&a->sizes); + 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_block_allocator_t *result = ecs_map_get(&a->sizes, - ecs_block_allocator_t, size); + 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(&a->sizes, + ecs_block_allocator_t, (uint32_t)hash); + if (!result) { - result = ecs_map_ensure(&a->sizes, ecs_block_allocator_t, size); + result = flecs_sparse_ensure_fast(&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; } @@ -10543,6 +10618,49 @@ void flecs_strfree( flecs_free_n(a, char, len + 1, str); } +void* flecs_dup( + ecs_allocator_t *a, + ecs_size_t size, + const void *src) +{ + ecs_block_allocator_t *ba = flecs_allocator_get(a, size); + if (ba) { + void *dst = flecs_balloc(ba); + ecs_os_memcpy(dst, src, size); + return dst; + } else { + return NULL; + } +} + +/** + * @file datastructures/vector.c + * @brief Vector datastructure. + * + * This is an implementation of a simple vector type. The vector is allocated in + * a single block of memory, with the element count, and allocated number of + * elements encoded in the block. As this vector is used for user-types it has + * been designed to support alignments higher than 8 bytes. This makes the size + * of the vector header variable in size. To reduce the overhead associated with + * retrieving or computing this size, the functions are wrapped in macro calls + * that compute the header size at compile time. + * + * The API provides a number of _t macros, which accept a size and alignment. + * These macros are used when no compile-time type is available. + * + * The vector guarantees contiguous access to its elements. When an element is + * removed from the vector, the last element is copied to the removed element. + * + * The API requires passing in the type of the vector. This type is used to test + * whether the size of the provided type equals the size of the type with which + * the vector was created. In release mode this check is not performed. + * + * When elements are added to the vector, it will automatically resize to the + * next power of two. This can change the pointer of the vector, which is why + * operations that can increase the vector size, accept a double pointer to the + * vector. + */ + struct ecs_vector_t { int32_t count; @@ -10555,7 +10673,7 @@ struct ecs_vector_t { /** Resize the vector buffer */ static -ecs_vector_t* resize( +ecs_vector_t* flecs_vector_resize( ecs_vector_t *vector, int16_t offset, int32_t size) @@ -10676,7 +10794,7 @@ void* _ecs_vector_addn( } max_count = flecs_next_pow_of_2(max_count); - vector = resize(vector, offset, max_count * elem_size); + vector = flecs_vector_resize(vector, offset, max_count * elem_size); vector->size = max_count; *array_inout = vector; } @@ -10707,7 +10825,7 @@ void* _ecs_vector_add( } size = flecs_next_pow_of_2(size); - vector = resize(vector, offset, size * elem_size); + vector = flecs_vector_resize(vector, offset, size * elem_size); *array_inout = vector; vector->size = size; } @@ -10843,7 +10961,7 @@ void _ecs_vector_reclaim( if (count < size) { if (count) { size = count; - vector = resize(vector, offset, size * elem_size); + vector = flecs_vector_resize(vector, offset, size * elem_size); vector->size = size; *array_inout = vector; } else { @@ -10893,7 +11011,7 @@ int32_t _ecs_vector_set_size( if (result < elem_count) { elem_count = flecs_next_pow_of_2(elem_count); - vector = resize(vector, offset, elem_count * elem_size); + vector = flecs_vector_resize(vector, offset, elem_count * elem_size); vector->size = elem_count; *array_inout = vector; result = elem_count; @@ -11040,6 +11158,42 @@ ecs_vector_t* _ecs_vector_copy( return dst; } +/** + * @file datastructures/sparse.c + * @brief Sparse set data structure. + * + * The sparse set data structure allows for fast lookups by 64bit key with + * variable payload size. Lookup operations are guaranteed to be O(1), in + * contrast to ecs_map_t which has O(1) lookup time on average. At a high level + * the sparse set works with two arrays: + * + * dense [ - ][ 3 ][ 1 ][ 4 ] + * sparse [ ][ 2 ][ ][ 1 ][ 3 ] + * + * Indices in the dense array point to the sparse array, and vice versa. The + * dense array is guaranteed to contain no holes. By iterating the dense array, + * all populated elements in the sparse set can be found without having to skip + * empty elements. + * + * The sparse array is paged, which means that it is split up in memory blocks + * (pages, not to be confused with OS pages) of equal size. The page size is + * set to 4096 elements. Paging prevents the sparse array from having to grow + * to N elements, where N is the largest key used in the set. It also ensures + * that payload pointers are stable. + * + * The sparse set recycles deleted keys. It does this by moving not alive keys + * to the end of the dense array, and using a count member that indicates the + * last alive member in the dense array. This approach makes it possible to + * recycle keys in bulk, by increasing the alive count. + * + * When a key is deleted, the sparse set increases its generation count. This + * generation count is used to test whether a key passed to the sparse set is + * still valid, or whether it has been deleted. + * + * The sparse set is used in a number of places, like for retrieving entity + * administration, tables and allocators. + */ + /** Compute the chunk index from an id by stripping the first 12 bits */ #define CHUNK(index) ((int32_t)((uint32_t)index >> 12)) @@ -11330,6 +11484,9 @@ void* flecs_sparse_get_sparse( { flecs_sparse_strip_generation(&index); chunk_t *chunk = flecs_sparse_get_chunk(sparse, CHUNK(index)); + if (!chunk) { + return NULL; + } int32_t offset = OFFSET(index); ecs_assert(chunk != NULL, ECS_INTERNAL_ERROR, NULL); @@ -11566,6 +11723,38 @@ void* _flecs_sparse_ensure( return DATA(chunk->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_vector_count(sparse->dense) > 0, ECS_INTERNAL_ERROR, NULL); + (void)size; + + uint32_t index = (uint32_t)index_long; + chunk_t *chunk = flecs_sparse_get_or_create_chunk(sparse, CHUNK(index)); + int32_t offset = OFFSET(index); + int32_t dense = chunk->sparse[offset]; + int32_t count = sparse->count; + + if (!dense) { + /* Element is not paired yet. Must add a new element to dense array */ + ecs_vector_t *dense_vector = sparse->dense; + sparse->count = count + 1; + if (count == ecs_vector_count(dense_vector)) { + flecs_sparse_grow_dense(sparse); + dense_vector = sparse->dense; + } + + uint64_t *dense_array = ecs_vector_first(dense_vector, uint64_t); + flecs_sparse_assign_index(chunk, dense_array, index, count); + } + + return DATA(chunk->data, sparse->size, offset); +} + void* _flecs_sparse_set( ecs_sparse_t * sparse, ecs_size_t elem_size, @@ -11586,7 +11775,11 @@ void* _flecs_sparse_remove_get( ecs_assert(!size || size == sparse->size, ECS_INVALID_PARAMETER, NULL); (void)size; - chunk_t *chunk = flecs_sparse_get_or_create_chunk(sparse, CHUNK(index)); + chunk_t *chunk = flecs_sparse_get_chunk(sparse, CHUNK(index)); + if (!chunk) { + return NULL; + } + uint64_t gen = flecs_sparse_strip_generation(&index); int32_t offset = OFFSET(index); int32_t dense = chunk->sparse[offset]; @@ -11673,6 +11866,27 @@ bool flecs_sparse_exists( return dense != 0; } +bool flecs_sparse_is_valid( + const ecs_sparse_t *sparse, + uint64_t index) +{ + ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); + chunk_t *chunk = flecs_sparse_get_chunk(sparse, CHUNK(index)); + if (!chunk) { + return true; /* Doesn't exist yet, so is valid */ + } + + flecs_sparse_strip_generation(&index); + int32_t offset = OFFSET(index); + int32_t dense = chunk->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, @@ -11872,25 +12086,31 @@ void* _ecs_sparse_get( return _flecs_sparse_get(sparse, elem_size, id); } -ecs_sparse_iter_t _flecs_sparse_iter( - ecs_sparse_t *sparse, - ecs_size_t elem_size) -{ - ecs_assert(sparse != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(elem_size == sparse->size, ECS_INVALID_PARAMETER, NULL); - ecs_sparse_iter_t result; - result.sparse = sparse; - result.ids = flecs_sparse_ids(sparse); - result.size = elem_size; - result.i = 0; - result.count = sparse->count - 1; - return result; -} +/** + * @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 verify_nodes( +void flecs_switch_verify_nodes( ecs_switch_header_t *hdr, ecs_switch_node_t *nodes) { @@ -11909,11 +12129,11 @@ void verify_nodes( ecs_assert(count == hdr->count, ECS_INTERNAL_ERROR, NULL); } #else -#define verify_nodes(hdr, nodes) +#define flecs_switch_verify_nodes(hdr, nodes) #endif static -ecs_switch_header_t *get_header( +ecs_switch_header_t *flecs_switch_get_header( const ecs_switch_t *sw, uint64_t value) { @@ -11925,7 +12145,7 @@ ecs_switch_header_t *get_header( } static -ecs_switch_header_t *ensure_header( +ecs_switch_header_t *flecs_switch_ensure_header( ecs_switch_t *sw, uint64_t value) { @@ -11933,7 +12153,7 @@ ecs_switch_header_t *ensure_header( return NULL; } - ecs_switch_header_t *node = get_header(sw, value); + ecs_switch_header_t *node = flecs_switch_get_header(sw, value); if (!node) { node = ecs_map_ensure(&sw->hdrs, ecs_switch_header_t, value); node->element = -1; @@ -11943,7 +12163,7 @@ ecs_switch_header_t *ensure_header( } static -void remove_node( +void flecs_switch_remove_node( ecs_switch_header_t *hdr, ecs_switch_node_t *nodes, ecs_switch_node_t *node, @@ -12100,18 +12320,18 @@ void flecs_switch_set( ecs_switch_node_t *nodes = ecs_vec_first(&sw->nodes); ecs_switch_node_t *node = &nodes[element]; - ecs_switch_header_t *dst_hdr = ensure_header(sw, value); - ecs_switch_header_t *cur_hdr = get_header(sw, cur_value); + 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); - verify_nodes(cur_hdr, nodes); - verify_nodes(dst_hdr, nodes); + 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) { - remove_node(cur_hdr, nodes, node, element); + flecs_switch_remove_node(cur_hdr, nodes, node, element); } /* Now update the node itself by adding it as the first node of dst */ @@ -12149,11 +12369,11 @@ void flecs_switch_remove( /* If node is currently assigned to a case, remove it from the list */ if (value != 0) { - ecs_switch_header_t *hdr = get_header(sw, value); + ecs_switch_header_t *hdr = flecs_switch_get_header(sw, value); ecs_assert(hdr != NULL, ECS_INTERNAL_ERROR, NULL); - verify_nodes(hdr, nodes); - remove_node(hdr, nodes, node, elem); + flecs_switch_verify_nodes(hdr, nodes); + flecs_switch_remove_node(hdr, nodes, node, elem); } int32_t last_elem = ecs_vec_count(&sw->nodes) - 1; @@ -12169,7 +12389,7 @@ void flecs_switch_remove( ecs_switch_node_t *n = &nodes[prev]; n->next = elem; } else { - ecs_switch_header_t *hdr = get_header(sw, values[last_elem]); + 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); @@ -12206,7 +12426,7 @@ int32_t flecs_switch_case_count( const ecs_switch_t *sw, uint64_t value) { - ecs_switch_header_t *hdr = get_header(sw, value); + ecs_switch_header_t *hdr = flecs_switch_get_header(sw, value); if (!hdr) { return 0; } @@ -12232,7 +12452,7 @@ int32_t flecs_switch_first( { ecs_assert(sw != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_switch_header_t *hdr = get_header(sw, value); + ecs_switch_header_t *hdr = flecs_switch_get_header(sw, value); if (!hdr) { return -1; } @@ -12253,6 +12473,11 @@ int32_t flecs_switch_next( 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( @@ -12470,6 +12695,11 @@ 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" @@ -12801,9 +13031,19 @@ uint64_t flecs_hash( &h_1, &h_2); - return h_1 | ((uint64_t)h_2 << 32); +#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, @@ -12824,6 +13064,13 @@ void ecs_qsort( 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 @@ -12937,6 +13184,24 @@ 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 /** @@ -13467,6 +13732,18 @@ bool ecs_strbuf_appendstr_zerocpy( 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) @@ -13479,6 +13756,19 @@ bool ecs_strbuf_appendstr_zerocpy_const( 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) @@ -13709,6 +13999,11 @@ int32_t ecs_strbuf_written( } } +/** + * @file datastructures/vec.c + * @brief Vector with allocator support. + */ + ecs_vec_t* ecs_vec_init( ecs_allocator_t *allocator, @@ -13743,6 +14038,20 @@ void ecs_vec_fini( } } +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) { @@ -13899,7 +14208,8 @@ void* ecs_vec_last( const ecs_vec_t *v, ecs_size_t size) { - ecs_dbg_assert(size == v->elem_size, ECS_INVALID_PARAMETER, NULL); + ecs_dbg_assert(!v->elem_size || size == v->elem_size, + ECS_INVALID_PARAMETER, NULL); return ECS_ELEM(v->array, size, v->count - 1); } @@ -13909,6 +14219,13 @@ void* ecs_vec_first( return v->array; } +/** + * @file datastructures/map.c + * @brief Map data structure. + * + * Map data structure for 64bit keys and dynamic payload size. + */ + #include /* The ratio used to determine whether the map should flecs_map_rehash. If @@ -14538,10 +14855,19 @@ ecs_map_t* ecs_map_copy( return result; } +/** + * @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 + +// #ifdef FLECS_SANITIZE // #define FLECS_USE_OS_ALLOC -#endif +// #define FLECS_MEMSET_UNINITIALIZED +// #endif int64_t ecs_block_allocator_alloc_count = 0; int64_t ecs_block_allocator_free_count = 0; @@ -14640,9 +14966,10 @@ void flecs_ballocator_free( void* flecs_balloc( ecs_block_allocator_t *ba) { + void *result; #ifdef FLECS_USE_OS_ALLOC - return ecs_os_malloc(ba->data_size); -#endif + result = ecs_os_malloc(ba->data_size); +#else if (!ba) return NULL; @@ -14650,7 +14977,7 @@ void* flecs_balloc( ba->head = flecs_balloc_block(ba); } - void *result = ba->head; + result = ba->head; ba->head = ba->head->next; #ifdef FLECS_SANITIZE @@ -14658,6 +14985,10 @@ void* flecs_balloc( 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 @@ -14717,15 +15048,15 @@ void* flecs_brealloc( ecs_block_allocator_t *src, void *memory) { + void *result; #ifdef FLECS_USE_OS_ALLOC - return ecs_os_realloc(memory, dst->data_size); -#endif - + result = ecs_os_realloc(memory, dst->data_size); +#else if (dst == src) { return memory; } - void *result = flecs_balloc(dst); + result = flecs_balloc(dst); if (result && src) { ecs_size_t size = src->data_size; if (dst->data_size < size) { @@ -14734,6 +15065,16 @@ void* flecs_brealloc( 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; } @@ -14756,6 +15097,15 @@ void* flecs_bdup( 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( @@ -14996,6 +15346,18 @@ void* _flecs_hashmap_next( 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) @@ -15137,12 +15499,479 @@ void flecs_stack_fini( } 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 -static void ecs_colorize_buf( char *msg, bool enable_colors, @@ -15266,7 +16095,7 @@ void ecs_colorize_buf( } } -void _ecs_logv( +void _ecs_printv( int level, const char *file, int32_t line, @@ -15278,10 +16107,6 @@ void _ecs_logv( ecs_strbuf_t msg_buf = ECS_STRBUF_INIT; - if (level > ecs_os_api.log_level_) { - return; - } - /* 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); @@ -15299,7 +16124,7 @@ void _ecs_logv( } } -void _ecs_log( +void _ecs_print( int level, const char *file, int32_t line, @@ -15308,10 +16133,42 @@ void _ecs_log( { va_list args; va_start(args, fmt); - _ecs_logv(level, file, line, fmt, args); + _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) { @@ -15325,6 +16182,7 @@ void _ecs_log_pop( { if (level <= ecs_os_api.log_level_) { ecs_os_api.log_indent_ --; + ecs_assert(ecs_os_api.log_indent_ >= 0, ECS_INTERNAL_ERROR, NULL); } } @@ -15335,6 +16193,11 @@ void _ecs_parser_errorv( 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) { @@ -15378,6 +16241,7 @@ void _ecs_parser_errorv( for (c = 0; c < column; c ++) { ecs_strbuf_appendch(&msg_buf, ' '); } + ecs_strbuf_appendch(&msg_buf, '^'); } } @@ -15638,6 +16502,16 @@ int ecs_log_last_error(void) 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 @@ -15661,10 +16535,10 @@ typedef struct ecs_system_t { /* Schedule parameters */ bool multi_threaded; - bool no_staging; + bool no_readonly; int64_t invoke_count; /* Number of times system is invoked */ - float time_spent; /* Time spent on running system */ + 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 */ @@ -15706,99 +16580,108 @@ ecs_entity_t ecs_run_intern( #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 and contains - * information about the set of systems that need to be ran before a merge. */ + * This type is the element type in the "ops" vector of a pipeline. */ typedef struct ecs_pipeline_op_t { - int32_t count; /* Number of systems to run before merge */ + 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_staging; /* Whether systems are staged or not */ + bool no_readonly; /* Whether systems are staged or not */ } ecs_pipeline_op_t; -typedef struct { +typedef struct ecs_pipeline_state_t { ecs_query_t *query; /* Pipeline query */ - - ecs_vector_t *ops; /* Pipeline schedule */ + 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_entity_t last_system; /* Last system ran by pipeline */ - - ecs_id_record_t *idr_inactive; /* Cached record for quick inactive test */ - 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 //////////////////////////////////////////////////////////////////////////////// -/** Update a pipeline (internal function). - * Before running a pipeline, it must be updated. During this update phase - * all systems in the pipeline are collected, ordered and sync points are - * inserted where necessary. This operation may only be called when staging is - * disabled. - * - * Because multiple threads may run a pipeline, preparing the pipeline must - * happen synchronously, which is why this function is separate from - * ecs_run_pipeline. Not running the prepare step may cause systems to not get - * ran, or ran in the wrong order. - * - * If 0 is provided for the pipeline id, the default pipeline will be ran (this - * is either the builtin pipeline or the pipeline set with set_pipeline()). - * - * @param world The world. - * @param pipeline The pipeline to run. - * @return The number of elements in the pipeline. - */ -bool ecs_pipeline_update( +bool flecs_pipeline_update( ecs_world_t *world, - ecs_entity_t pipeline, - bool start_of_frame); + ecs_pipeline_state_t *pq, + bool start_of_frame); -void ecs_pipeline_fini_iter( - EcsPipeline *pq); - -void ecs_pipeline_reset_iter( +void flecs_run_pipeline( ecs_world_t *world, - EcsPipeline *q); + ecs_pipeline_state_t *pq, + ecs_ftime_t delta_time); //////////////////////////////////////////////////////////////////////////////// //// Worker API //////////////////////////////////////////////////////////////////////////////// -void ecs_worker_begin( - ecs_world_t *world); - -bool ecs_worker_sync( +bool flecs_worker_begin( ecs_world_t *world, - EcsPipeline *p); + ecs_stage_t *stage, + ecs_pipeline_state_t *pq, + bool start_of_frame); -void ecs_worker_end( - ecs_world_t *world); - -void ecs_workers_progress( +void flecs_worker_end( ecs_world_t *world, - ecs_entity_t pipeline, + 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* worker(void *arg) { - ecs_stage_t *stage = arg; +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 @@ -15817,10 +16700,7 @@ void* worker(void *arg) { ecs_dbg_3("worker %d: run", stage->id); - ecs_run_pipeline( - (ecs_world_t*)stage, - world->pipeline, - world->info.delta_time); + flecs_run_pipeline((ecs_world_t*)stage, pq, world->info.delta_time); ecs_set_scope((ecs_world_t*)stage, old_scope); } @@ -15833,12 +16713,29 @@ void* worker(void *arg) { 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 start_workers( +void flecs_start_workers( ecs_world_t *world, int32_t threads) { @@ -15847,42 +16744,88 @@ void start_workers( ecs_assert(ecs_get_stage_count(world) == threads, ECS_INTERNAL_ERROR, NULL); int32_t i; - for (i = 0; i < threads; i ++) { - ecs_stage_t *stage = (ecs_stage_t*)ecs_get_stage(world, 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); - stage->thread = ecs_os_thread_new(worker, stage); + + 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 wait_for_workers( +void flecs_wait_for_workers( ecs_world_t *world) { - int32_t stage_count = ecs_get_stage_count(world); - bool wait = true; + 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) { + if (world->workers_running == (stage_count - 1)) { wait = false; } ecs_os_mutex_unlock(world->sync_mutex); } while (wait); } -/* Synchronize workers */ +/* Wait until all threads are waiting on sync point */ static -void sync_worker( +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) { + if (++ world->workers_waiting == (stage_count - 1)) { /* Only signal main thread when all threads are waiting */ ecs_os_cond_signal(world->sync_cond); } @@ -15892,34 +16835,16 @@ void sync_worker( ecs_os_mutex_unlock(world->sync_mutex); } -/* Wait until all threads are waiting on sync point */ +/* Signal workers that they can start/resume work */ static -void wait_for_sync( +void flecs_signal_workers( ecs_world_t *world) { int32_t stage_count = ecs_get_stage_count(world); - - ecs_dbg_3("#[bold]pipeline: waiting for worker sync"); - - ecs_os_mutex_lock(world->sync_mutex); - if (world->workers_waiting != stage_count) { - ecs_os_cond_wait(world->sync_cond, world->sync_mutex); + if (stage_count <= 1) { + return; } - - /* We should have been signalled unless all workers are waiting on sync */ - ecs_assert(world->workers_waiting == stage_count, - ECS_INTERNAL_ERROR, NULL); - ecs_os_mutex_unlock(world->sync_mutex); - - ecs_dbg_3("#[bold]pipeline: workers synced"); -} - -/* Signal workers that they can start/resume work */ -static -void signal_workers( - ecs_world_t *world) -{ ecs_dbg_3("#[bold]pipeline: signal workers"); ecs_os_mutex_lock(world->sync_mutex); ecs_os_cond_broadcast(world->worker_cond); @@ -15937,7 +16862,7 @@ bool ecs_stop_threads( * a potential race if threads haven't spun up yet. */ ecs_stage_t *stages = world->stages; int i, count = world->stage_count; - for (i = 0; i < count; i ++) { + for (i = 1; i < count; i ++) { ecs_stage_t *stage = &stages[i]; if (stage->thread) { threads_active = true; @@ -15952,14 +16877,14 @@ bool ecs_stop_threads( } /* Make sure all threads are running, to ensure they catch the signal */ - wait_for_workers(world); + flecs_wait_for_workers(world); /* Signal threads should quit */ world->flags |= EcsWorldQuitWorkers; - signal_workers(world); + flecs_signal_workers(world); /* Join all threads with main */ - for (i = 0; i < count; i ++) { + for (i = 1; i < count; i ++) { ecs_os_thread_join(stages[i].thread); stages[i].thread = 0; } @@ -15974,161 +16899,113 @@ bool ecs_stop_threads( } /* -- Private functions -- */ - -void ecs_worker_begin( - ecs_world_t *world) -{ - flecs_stage_from_world(&world); - int32_t stage_count = ecs_get_stage_count(world); - ecs_assert(stage_count != 0, ECS_INTERNAL_ERROR, NULL); - - if (stage_count == 1) { - ecs_entity_t pipeline = world->pipeline; - const EcsPipeline *pq = ecs_get(world, pipeline, EcsPipeline); - ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); - - ecs_pipeline_op_t *op = ecs_vector_first(pq->ops, ecs_pipeline_op_t); - if (!op || !op->no_staging) { - ecs_readonly_begin(world); - } - } -} - -bool ecs_worker_sync( +bool flecs_worker_begin( ecs_world_t *world, - EcsPipeline *pq) + ecs_stage_t *stage, + ecs_pipeline_state_t *pq, + bool start_of_frame) { - ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(pq->cur_op != NULL, ECS_INTERNAL_ERROR, NULL); - bool rebuild = false; + 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); - int32_t stage_count = ecs_get_stage_count(world); - ecs_assert(stage_count != 0, ECS_INTERNAL_ERROR, NULL); - int64_t build_count = world->info.pipeline_build_count_total; - - /* If there are no threads, merge in place */ - if (stage_count == 1) { - if (!pq->cur_op->no_staging) { + 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; } - ecs_pipeline_update(world, world->pipeline, false); - - /* Synchronize all workers. The last worker to reach the sync point will - * signal the main thread, which will perform the merge. */ - } else { - sync_worker(world); + flecs_pipeline_update(world, pq, start_of_frame); } - if (build_count != world->info.pipeline_build_count_total) { - rebuild = true; - } - - if (stage_count == 1) { - if (!pq->cur_op->no_staging) { + 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); } - return rebuild; + if (main_thread && multi_threaded) { + flecs_signal_workers(world); + } + + return pq->cur_op != NULL; } -void ecs_worker_end( - ecs_world_t *world) +void flecs_worker_end( + ecs_world_t *world, + ecs_stage_t *stage) { - flecs_stage_from_world(&world); + ecs_poly_assert(world, ecs_world_t); + ecs_poly_assert(stage, ecs_stage_t); - int32_t stage_count = ecs_get_stage_count(world); - ecs_assert(stage_count != 0, ECS_INTERNAL_ERROR, NULL); + if (flecs_is_multithreaded(world)) { + if (flecs_is_main_thread(stage)) { + flecs_wait_for_sync(world); + } else { + flecs_sync_worker(world); + } + } - /* If there are no threads, merge in place */ - if (stage_count == 1) { + if (flecs_is_main_thread(stage)) { if (ecs_stage_is_readonly(world)) { ecs_readonly_end(world); } - - /* Synchronize all workers. The last worker to reach the sync point will - * signal the main thread, which will perform the merge. */ - } else { - sync_worker(world); } } -void ecs_workers_progress( +bool flecs_worker_sync( ecs_world_t *world, - ecs_entity_t pipeline, + 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); - int32_t stage_count = ecs_get_stage_count(world); - EcsPipeline *pq = ecs_get_mut(world, pipeline, EcsPipeline); - ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); + /* Make sure workers are running and ready */ + flecs_wait_for_workers(world); - if (stage_count != pq->iter_count) { - pq->iters = ecs_os_realloc_n(pq->iters, ecs_iter_t, stage_count); - pq->iter_count = stage_count; - } - - ecs_pipeline_update(world, pipeline, true); - ecs_vector_t *ops = pq->ops; - ecs_pipeline_op_t *op = ecs_vector_first(ops, ecs_pipeline_op_t); - if (!op) { - ecs_pipeline_fini_iter(pq); - return; - } - - if (stage_count == 1) { - ecs_entity_t old_scope = ecs_set_scope(world, 0); - ecs_world_t *stage = ecs_get_stage(world, 0); - ecs_run_pipeline(stage, pipeline, delta_time); - ecs_set_scope(world, old_scope); - } else { - ecs_pipeline_op_t *op_last = ecs_vector_last(ops, ecs_pipeline_op_t); - - /* Make sure workers are running and ready */ - wait_for_workers(world); - - /* Synchronize n times for each op in the pipeline */ - for (; op <= op_last; op ++) { - bool is_threaded = world->flags & EcsWorldMultiThreaded; - if (!op->no_staging) { - ecs_readonly_begin(world); - } - if (!op->multi_threaded) { - world->flags &= ~EcsWorldMultiThreaded; - } - - /* Signal workers that they should start running systems */ - world->workers_waiting = 0; - signal_workers(world); - - /* Wait until all workers are waiting on sync point */ - wait_for_sync(world); - - /* Merge */ - if (!op->no_staging) { - ecs_readonly_end(world); - } - if (is_threaded) { - world->flags |= EcsWorldMultiThreaded; - } - - if (ecs_pipeline_update(world, pipeline, false)) { - ecs_assert(!ecs_is_deferred(world), ECS_INVALID_OPERATION, NULL); - pq = ecs_get_mut(world, pipeline, EcsPipeline); - /* Refetch, in case pipeline itself has moved */ - op = pq->cur_op - 1; - op_last = ecs_vector_last(pq->ops, ecs_pipeline_op_t); - ecs_assert(op <= op_last, ECS_INTERNAL_ERROR, NULL); - - if (op == op_last) { - ecs_pipeline_fini_iter(pq); - } - } - } - } + /* 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 -- */ @@ -16156,19 +17033,43 @@ void ecs_set_threads( world->worker_cond = ecs_os_cond_new(); world->sync_cond = ecs_os_cond_new(); world->sync_mutex = ecs_os_mutex_new(); - start_workers(world, threads); + 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, { - ecs_vector_free(ptr->ops); - ecs_os_free(ptr->iters); + flecs_pipeline_free(ptr->state); }) typedef enum ecs_write_kind_t { @@ -16373,14 +17274,6 @@ bool flecs_pipeline_check_terms( return needs_merge; } -static -bool flecs_pipeline_is_inactive( - const EcsPipeline *pq, - ecs_table_t *table) -{ - return flecs_id_record_get_table(pq->idr_inactive, table) != NULL; -} - static EcsPoly* flecs_pipeline_term_system( ecs_iter_t *it) @@ -16388,25 +17281,21 @@ EcsPoly* flecs_pipeline_term_system( 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); + EcsPoly *poly = ecs_table_get_column(it->table, index, it->offset); ecs_assert(poly != NULL, ECS_INTERNAL_ERROR, NULL); - poly = &poly[it->offset]; return poly; } static bool flecs_pipeline_build( ecs_world_t *world, - ecs_entity_t pipeline, - EcsPipeline *pq) + ecs_pipeline_state_t *pq) { - (void)pipeline; - ecs_iter_t it = ecs_query_iter(world, pq->query); - ecs_iter_fini(&it); if (pq->match_count == pq->query->match_count) { /* No need to rebuild the pipeline */ + ecs_iter_fini(&it); return false; } @@ -16419,30 +17308,24 @@ bool flecs_pipeline_build( }; ecs_pipeline_op_t *op = NULL; - ecs_vector_t *ops = NULL; - ecs_query_t *query = pq->query; + ecs_allocator_t *a = &world->allocator; - if (pq->ops) { - ecs_vector_free(pq->ops); - } + 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_staging = false; + bool no_readonly = false; bool first = true; /* Iterate systems in pipeline, add ops for running / merging */ - it = ecs_query_iter(world, query); while (ecs_query_next(&it)) { EcsPoly *poly = flecs_pipeline_term_system(&it); bool is_active = ecs_table_get_index(world, it.table, EcsEmpty) == -1; - int i; + 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; - if (!q) { - continue; - } bool needs_merge = false; needs_merge = flecs_pipeline_check_terms( @@ -16451,7 +17334,7 @@ bool flecs_pipeline_build( if (is_active) { if (first) { multi_threaded = sys->multi_threaded; - no_staging = sys->no_staging; + no_readonly = sys->no_readonly; first = false; } @@ -16459,13 +17342,13 @@ bool flecs_pipeline_build( needs_merge = true; multi_threaded = sys->multi_threaded; } - if (sys->no_staging != no_staging) { + if (sys->no_readonly != no_readonly) { needs_merge = true; - no_staging = sys->no_staging; + no_readonly = sys->no_readonly; } } - if (no_staging) { + if (no_readonly) { needs_merge = true; } @@ -16497,84 +17380,101 @@ bool flecs_pipeline_build( } if (!op) { - op = ecs_vector_add(&ops, ecs_pipeline_op_t); + 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_staging = 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_staging = no_staging; + 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_free(ws.ids); ecs_map_free(ws.wildcard_ids); - /* Find the system ran last this frame (helps workers reset iter) */ - ecs_entity_t last_system = 0; - op = ecs_vector_first(ops, ecs_pipeline_op_t); - int32_t i, ran_since_merge = 0, op_index = 0; + op = ecs_vec_first_t(&pq->ops, ecs_pipeline_op_t); if (!op) { ecs_dbg("#[green]pipeline#[reset] is empty"); return true; } else { - ecs_assert(op != NULL, ECS_INTERNAL_ERROR, NULL); - /* 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_staging); + op->multi_threaded, !op->no_readonly); ecs_log_push_1(); - it = ecs_query_iter(world, pq->query); - while (ecs_query_next(&it)) { - if (flecs_pipeline_is_inactive(pq, it.table)) { - continue; + 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(); } - EcsPoly *poly = flecs_pipeline_term_system(&it); - - for (i = 0; i < it.count; i ++) { - ecs_system_t *sys = ecs_poly(poly[i].poly, ecs_system_t); - if (ecs_should_log_1()) { - char *path = ecs_get_fullpath(world, it.entities[i]); - ecs_dbg("#[green]system#[reset] %s", path); - ecs_os_free(path); - } - - 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_vector_count(ops)) { - ecs_dbg( - "#[green]schedule#[reset]: " - "threading: %d, staging: %d:", - op[op_index].multi_threaded, - !op[op_index].no_staging); - } - ecs_log_push_1(); - } - - if (sys->last_frame == (world->info.frame_count_total + 1)) { - last_system = it.entities[i]; - - /* Can't break from loop yet. It's possible that previously - * inactive systems that ran before the last ran system are - * now active. */ + 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; } } } @@ -16583,90 +17483,38 @@ bool flecs_pipeline_build( ecs_log_pop_1(); } - /* Force sort of query as this could increase the match_count */ pq->match_count = pq->query->match_count; - pq->ops = ops; - pq->last_system = last_system; + + ecs_assert(pq->cur_op <= ecs_vec_last_t(&pq->ops, ecs_pipeline_op_t), + ECS_INTERNAL_ERROR, NULL); return true; } -void ecs_pipeline_fini_iter( - EcsPipeline *pq) +static +void flecs_pipeline_next_system( + ecs_pipeline_state_t *pq) { - int32_t i, iter_count = pq->iter_count; - for (i = 0; i < iter_count; i ++) { - ecs_iter_fini(&pq->iters[i]); - } -} - -void ecs_pipeline_reset_iter( - ecs_world_t *world, - EcsPipeline *pq) -{ - ecs_pipeline_op_t *op = ecs_vector_first(pq->ops, ecs_pipeline_op_t); - int32_t i, ran_since_merge = 0, op_index = 0, iter_count = pq->iter_count; - - ecs_pipeline_fini_iter(pq); - - if (!pq->last_system) { - /* It's possible that all systems that were ran were removed entirely - * from the pipeline (they could have been deleted or disabled). In that - * case (which should be very rare) the pipeline can't make assumptions - * about where to continue, so end frame. */ - pq->cur_i = -1; + if (!pq->cur_op) { return; } - /* Create new iterators */ - for (i = 0; i < iter_count; i ++) { - ecs_world_t *stage = ecs_get_stage(world, i); - pq->iters[i] = ecs_query_iter(stage, pq->query); - } - - /* Move iterator to last ran system */ - ecs_iter_t *it = &pq->iters[0]; - while (ecs_query_next(it)) { - /* Progress worker iterators */ - for (i = 1; i < iter_count; i ++) { - bool hasnext = ecs_query_next(&pq->iters[i]); - ecs_assert_var(hasnext, ECS_INTERNAL_ERROR, NULL); - ecs_assert(it->table == pq->iters[i].table, - ECS_INTERNAL_ERROR, NULL); - ecs_assert(it->count == pq->iters[i].count, - ECS_INTERNAL_ERROR, NULL); + 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; } - - if (flecs_pipeline_is_inactive(pq, it->table)) { - continue; - } - - for (i = 0; i < it->count; i ++) { - ran_since_merge ++; - if (ran_since_merge == op[op_index].count) { - ran_since_merge = 0; - op_index ++; - } - - if (it->entities[i] == pq->last_system) { - pq->cur_op = &op[op_index]; - pq->cur_i = i; - return; - } - } - } - - ecs_abort(ECS_INTERNAL_ERROR, NULL); + } } -bool ecs_pipeline_update( +bool flecs_pipeline_update( ecs_world_t *world, - ecs_entity_t pipeline, + 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); - ecs_assert(pipeline != 0, ECS_INTERNAL_ERROR, NULL); /* If any entity mutations happened that could have affected query matching * notify appropriate queries so caches are up to date. This includes the @@ -16675,28 +17523,24 @@ bool ecs_pipeline_update( ecs_run_aperiodic(world, 0); } - EcsPipeline *pq = ecs_get_mut(world, pipeline, EcsPipeline); ecs_assert(pq != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(pq->query != NULL, ECS_INTERNAL_ERROR, NULL); - bool rebuilt = flecs_pipeline_build(world, pipeline, pq); - if (start_of_frame) { + 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_vector_first(pq->ops, ecs_pipeline_op_t); - } else if (rebuilt) { - /* If pipeline got updated and we were mid frame, reset iterators */ - ecs_pipeline_reset_iter(world, pq); - return true; + pq->cur_op = ecs_vec_first_t(&pq->ops, ecs_pipeline_op_t); + pq->cur_i = 0; } else { - pq->cur_op += 1; + flecs_pipeline_next_system(pq); } - return false; + return rebuilt; } void ecs_run_pipeline( @@ -16704,128 +17548,162 @@ void ecs_run_pipeline( ecs_entity_t pipeline, ecs_ftime_t delta_time) { - ecs_assert(world != NULL, ECS_INVALID_OPERATION, NULL); - if (!pipeline) { pipeline = world->pipeline; } - ecs_assert(pipeline != 0, ECS_INVALID_PARAMETER, NULL); - - /* If the world is passed to ecs_run_pipeline, the function will take care - * of staging, so the world should not be in staged mode when called. */ - if (ecs_poly_is(world, ecs_world_t)) { - ecs_assert(!(world->flags & EcsWorldReadonly), - ECS_INVALID_OPERATION, NULL); - - /* Forward to worker_progress. This function handles staging, threading - * and synchronization across workers. */ - ecs_workers_progress(world, pipeline, delta_time); - return; - - /* If a stage is passed, the function could be ran from a worker thread. In - * that case the main thread should manage staging, and staging should be - * enabled. */ - } else { - ecs_poly_assert(world, ecs_stage_t); - } - - ecs_stage_t *stage = flecs_stage_from_world(&world); - 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_vector_t *ops = pq->ops; - ecs_pipeline_op_t *op = ecs_vector_first(ops, ecs_pipeline_op_t); - ecs_pipeline_op_t *op_last = ecs_vector_last(ops, ecs_pipeline_op_t); - int32_t ran_since_merge = 0; - + 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); - ecs_worker_begin(stage->thread_ctx); - - ecs_time_t st = {0}; - bool measure_time = false; - if (!stage_index && (world->flags & EcsWorldMeasureSystemTime)) { - ecs_time_measure(&st); - measure_time = true; + if (!flecs_worker_begin(world, stage, pq, true)) { + return; } - ecs_iter_t *it = &pq->iters[stage_index]; - while (ecs_query_next(it)) { - if (flecs_pipeline_is_inactive(pq, it->table)) { - continue; /* Skip inactive systems */ + 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; } - EcsPoly *poly = flecs_pipeline_term_system(it); - int32_t i, count = it->count; - for(i = 0; i < count; i ++) { - ecs_entity_t e = it->entities[i]; - ecs_system_t *sys = ecs_poly(poly[i].poly, ecs_system_t); + if (measure_time) { + ecs_time_measure(&st); + } - ecs_assert(sys->entity == e, ECS_INTERNAL_ERROR, NULL); + 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); - sys->last_frame = world->info.frame_count_total + 1; + /* 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; - if (!stage_index || op->multi_threaded) { ecs_stage_t *s = NULL; - if (!op->no_staging) { + 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, e, sys, stage_index, + ecs_run_intern(world, s, system, sys, stage_index, stage_count, delta_time, 0, 0, NULL); } - ran_since_merge ++; world->info.systems_ran_frame ++; + ran_since_merge ++; if (ran_since_merge == op->count) { - if (op == op_last) { - ecs_iter_fini(it); - goto done; - } - - if (measure_time) { - /* Don't include merge time in system time */ - world->info.system_time_total += - (ecs_ftime_t)ecs_time_measure(&st); - } - - ran_since_merge = 0; - - /* If the set of matched systems changed as a result of the - * merge, we have to reset the iterator and move it to our - * current position (system). If there are a lot of systems - * in the pipeline this can be an expensive operation, but - * should happen infrequently. */ - bool rebuild = ecs_worker_sync(world, pq); - pq = (EcsPipeline*)ecs_get(world, pipeline, EcsPipeline); - if (rebuild) { - i = pq->cur_i; - count = it->count; - } - - poly = flecs_pipeline_term_system(it); - op = pq->cur_op; - op_last = ecs_vector_last(pq->ops, ecs_pipeline_op_t); - - if (measure_time) { - /* Reset timer after merge */ - ecs_time_measure(&st); - } + /* Merge */ + break; } } - } -done: + 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); } - ecs_worker_end(stage->thread_ctx); + 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( @@ -16834,14 +17712,23 @@ bool ecs_progress( { 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(); - ecs_run_pipeline(world, 0, delta_time); + 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( @@ -16866,7 +17753,14 @@ void ecs_set_pipeline( 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; } @@ -16897,7 +17791,7 @@ ecs_entity_t ecs_pipeline_init( if (!qd.order_by) { qd.order_by = flecs_entity_compare; } - qd.entity = result; + qd.filter.entity = result; ecs_query_t *query = ecs_query_init(world, &qd); if (!query) { @@ -16908,11 +17802,11 @@ ecs_entity_t ecs_pipeline_init( ecs_assert(query->filter.terms[0].id == EcsSystem, ECS_INVALID_PARAMETER, NULL); - ecs_set(world, result, EcsPipeline, { - .query = query, - .match_count = -1, - .idr_inactive = flecs_id_record_ensure(world, EcsEmpty) - }); + 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; } @@ -16958,20 +17852,36 @@ void FlecsPipelineImport( 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, EcsPreFrame); - flecs_bootstrap_phase(world, EcsPostLoad, EcsOnLoad); - flecs_bootstrap_phase(world, EcsPreUpdate, EcsPostLoad); - flecs_bootstrap_phase(world, EcsOnUpdate, EcsPreUpdate); - flecs_bootstrap_phase(world, EcsOnValidate, EcsOnUpdate); - flecs_bootstrap_phase(world, EcsPostUpdate, EcsOnValidate); - flecs_bootstrap_phase(world, EcsPreStore, EcsPostUpdate); - flecs_bootstrap_phase(world, EcsOnStore, EcsPreStore); - flecs_bootstrap_phase(world, EcsPostFrame, EcsOnStore); + 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) + .dtor = ecs_dtor(EcsPipeline), + .move = ecs_move(EcsPipeline) }); world->pipeline = ecs_pipeline_init(world, &(ecs_pipeline_desc_t){ @@ -16979,7 +17889,10 @@ void FlecsPipelineImport( .query = { .filter.terms = { { .id = EcsSystem }, - { .id = EcsPhase, .src.flags = EcsCascade, .src.trav = EcsDependsOn } + { .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 } @@ -16991,6 +17904,11 @@ void FlecsPipelineImport( #endif +/** + * @file addons/monitor.c + * @brief Monitor addon. + */ + #ifdef FLECS_MONITOR @@ -17297,6 +18215,11 @@ void FlecsMonitorImport( #endif +/** + * @file addons/timer.c + * @brief Timer addon. + */ + #ifdef FLECS_TIMER @@ -17590,6 +18513,11 @@ void FlecsTimerImport( #endif +/** + * @file addons/flecs_cpp.c + * @brief Utilities for C++ addon. + */ + #include /* Utilities for C++ API */ @@ -17784,6 +18712,7 @@ 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) @@ -17791,17 +18720,27 @@ void ecs_cpp_component_validate( /* 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 +#ifndef FLECS_NDEBUG char *path = ecs_get_path_w_sep( world, 0, id, "::", NULL); if (ecs_os_strcmp(path, name)) { - ecs_err( + ecs_abort(ECS_INCONSISTENT_NAME, "component '%s' already registered with name '%s'", name, path); - ecs_abort(ECS_INCONSISTENT_NAME, NULL); } ecs_os_free(path); -# endif +#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 */ @@ -17972,7 +18911,7 @@ ecs_entity_t ecs_cpp_component_register_explicit( }); ecs_assert(entity != 0, ECS_INVALID_OPERATION, NULL); } else { - entity = ecs_entity_init(world, &(ecs_entity_desc_t){ + entity = ecs_entity(world, { .id = s_id, .name = name, .sep = "::", @@ -18057,9 +18996,19 @@ int32_t ecs_cpp_reset_count_inc(void) { #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 @@ -18090,6 +19039,12 @@ void* win_thread_join( 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) @@ -18297,6 +19252,7 @@ void ecs_set_os_api_impl(void) { 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; @@ -18324,6 +19280,11 @@ void ecs_set_os_api_impl(void) { } #else +/** + * @file addons/os_api_impl/posix_impl.inl + * @brief Builtin POSIX implementation for OS API. + */ + #include "pthread.h" #if defined(__APPLE__) && defined(__MACH__) @@ -18359,6 +19320,12 @@ void* posix_thread_join( 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) @@ -18589,6 +19556,7 @@ void ecs_set_os_api_impl(void) { 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; @@ -18613,6 +19581,11 @@ void ecs_set_os_api_impl(void) { #endif #endif +/** + * @file addons/plecs.c + * @brief Plecs addon. + */ + #ifdef FLECS_PLECS @@ -19294,7 +20267,7 @@ const char* plecs_parse_const_stmt( const char *ptr, plecs_state_t *state) { - ptr = ecs_parse_token(name, expr, ptr + 5, state->var_name); + ptr = ecs_parse_token(name, expr, ptr + 5, state->var_name, 0); if (!ptr || ptr[0] != '=') { return NULL; } @@ -19803,6 +20776,142 @@ error: #endif +/** + * @file addons/journal.c + * @brief Journal addon. + */ + + +#ifdef FLECS_JOURNAL + +static +char* flecs_journal_entitystr( + ecs_world_t *world, + ecs_entity_t entity) +{ + char *path; + const char *_path = ecs_get_symbol(world, entity); + if (_path && !strchr(_path, '.')) { + path = ecs_asprintf("#[blue]%s", _path); + } else { + uint32_t gen = entity >> 32; + if (gen) { + path = ecs_asprintf("#[normal]_%u_%u", (uint32_t)entity, gen); + } else { + path = ecs_asprintf("#[normal]_%u", (uint32_t)entity); + } + } + return path; +} + +static +char* flecs_journal_idstr( + ecs_world_t *world, + ecs_id_t id) +{ + if (ECS_IS_PAIR(id)) { + char *first_path = flecs_journal_entitystr(world, + ecs_pair_first(world, id)); + char *second_path = flecs_journal_entitystr(world, + ecs_pair_second(world, id)); + char *result = ecs_asprintf("#[cyan]ecs_pair#[normal](%s, %s)", + first_path, second_path); + ecs_os_free(first_path); + ecs_os_free(second_path); + return result; + } else if (!(id & ECS_ID_FLAGS_MASK)) { + return flecs_journal_entitystr(world, id); + } else { + return ecs_id_str(world, id); + } +} + +static int flecs_journal_sp = 0; + +void flecs_journal_begin( + ecs_world_t *world, + ecs_journal_kind_t kind, + ecs_entity_t entity, + ecs_type_t *add, + ecs_type_t *remove) +{ + flecs_journal_sp ++; + + if (ecs_os_api.log_level_ < FLECS_JOURNAL_LOG_LEVEL) { + return; + } + + char *path = NULL; + char *var_id = NULL; + if (entity) { + path = ecs_get_fullpath(world, entity); + var_id = flecs_journal_entitystr(world, entity); + } + + if (kind == EcsJournalNew) { + ecs_print(4, "#[magenta]#ifndef #[normal]_var_%s", var_id); + ecs_print(4, "#[magenta]#define #[normal]_var_%s", var_id); + ecs_print(4, "#[green]ecs_entity_t %s;", var_id); + ecs_print(4, "#[magenta]#endif"); + ecs_print(4, "%s = #[cyan]ecs_new_id#[reset](world); " + "#[grey] // %s = new()", var_id, path); + } + if (add) { + for (int i = 0; i < add->count; i ++) { + char *jidstr = flecs_journal_idstr(world, add->array[i]); + char *idstr = ecs_id_str(world, add->array[i]); + ecs_print(4, "#[cyan]ecs_add_id#[reset](world, %s, %s); " + "#[grey] // add(%s, %s)", var_id, jidstr, + path, idstr); + ecs_os_free(idstr); + ecs_os_free(jidstr); + } + } + if (remove) { + for (int i = 0; i < remove->count; i ++) { + char *jidstr = flecs_journal_idstr(world, remove->array[i]); + char *idstr = ecs_id_str(world, remove->array[i]); + ecs_print(4, "#[cyan]ecs_remove_id#[reset](world, %s, %s); " + "#[grey] // remove(%s, %s)", var_id, jidstr, + path, idstr); + ecs_os_free(idstr); + ecs_os_free(jidstr); + } + } + if (kind == EcsJournalClear) { + ecs_print(4, "#[cyan]ecs_clear#[reset](world, %s); " + "#[grey] // clear(%s)", var_id, path); + } else if (kind == EcsJournalDelete) { + ecs_print(4, "#[cyan]ecs_delete#[reset](world, %s); " + "#[grey] // delete(%s)", var_id, path); + } else if (kind == EcsJournalDeleteWith) { + ecs_print(4, "#[cyan]ecs_delete_with#[reset](world, %s); " + "#[grey] // delete_with(%s)", var_id, path); + } else if (kind == EcsJournalRemoveAll) { + ecs_print(4, "#[cyan]ecs_remove_all#[reset](world, %s); " + "#[grey] // remove_all(%s)", var_id, path); + } else if (kind == EcsJournalTableEvents) { + ecs_print(4, "#[cyan]ecs_run_aperiodic#[reset](world, " + "EcsAperiodicEmptyTables);"); + } + ecs_os_free(var_id); + ecs_os_free(path); + ecs_log_push(); +} + +void flecs_journal_end(void) { + flecs_journal_sp --; + ecs_assert(flecs_journal_sp >= 0, ECS_INTERNAL_ERROR, NULL); + ecs_log_pop(); +} + +#endif + +/** + * @file addons/rules.c + * @brief Rules addon. + */ + #ifdef FLECS_RULES @@ -19965,8 +21074,7 @@ typedef struct ecs_rule_term_vars_t { /* Top-level rule datastructure */ struct ecs_rule_t { ecs_header_t hdr; - - ecs_world_t *world; /* Ref to world so rule can be used by itself */ + ecs_rule_op_t *operations; /* Operations array */ ecs_filter_t filter; /* Filter of rule */ @@ -19988,14 +21096,17 @@ struct ecs_rule_t { int32_t operation_count; /* Number of operations in rule */ ecs_iterable_t iterable; /* Iterable mixin */ + ecs_poly_dtor_t dtor; }; /* ecs_rule_t mixins */ ecs_mixins_t ecs_rule_t_mixins = { .type_name = "ecs_rule_t", .elems = { - [EcsMixinWorld] = offsetof(ecs_rule_t, world), - [EcsMixinIterable] = offsetof(ecs_rule_t, iterable) + [EcsMixinWorld] = offsetof(ecs_rule_t, filter.world), + [EcsMixinEntity] = offsetof(ecs_rule_t, filter.entity), + [EcsMixinIterable] = offsetof(ecs_rule_t, iterable), + [EcsMixinDtor] = offsetof(ecs_rule_t, dtor) } }; @@ -20005,10 +21116,14 @@ void rule_error( const char *fmt, ...) { - char *fstr = ecs_filter_str(rule->world, &rule->filter); + char *fstr = ecs_filter_str(rule->filter.world, &rule->filter); va_list valist; va_start(valist, fmt); - ecs_parser_errorv(rule->filter.name, fstr, -1, fmt, valist); + const char *name = NULL; + if (rule->filter.entity) { + name = ecs_get_name(rule->filter.world, rule->filter.entity); + } + ecs_parser_errorv(name, fstr, -1, fmt, valist); va_end(valist); ecs_os_free(fstr); } @@ -20356,7 +21471,7 @@ void entity_reg_set( (void)rule; ecs_assert(rule->vars[r].kind == EcsRuleVarKindEntity, ECS_INTERNAL_ERROR, NULL); - ecs_check(ecs_is_valid(rule->world, entity), ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_is_valid(rule->filter.world, entity), ECS_INVALID_PARAMETER, NULL); regs[r].entity = entity; error: return; @@ -20374,7 +21489,7 @@ ecs_entity_t entity_reg_get( return EcsWildcard; } - ecs_check(ecs_is_valid(rule->world, e), ECS_INVALID_PARAMETER, NULL); + ecs_check(ecs_is_valid(rule->filter.world, e), ECS_INVALID_PARAMETER, NULL); return e; error: return 0; @@ -20423,7 +21538,7 @@ ecs_entity_t reg_get_entity( /* The subject is referenced from the query string by string identifier. * If subject entity is not valid, it could have been deletd by the * application after the rule was created */ - ecs_check(ecs_is_valid(rule->world, op->subject), + ecs_check(ecs_is_valid(rule->filter.world, op->subject), ECS_INVALID_PARAMETER, NULL); return op->subject; @@ -20438,7 +21553,7 @@ ecs_entity_t reg_get_entity( ecs_assert(entities != NULL, ECS_INTERNAL_ERROR, NULL); ecs_assert(offset < ecs_vec_count(&data->entities), ECS_INTERNAL_ERROR, NULL); - ecs_check(ecs_is_valid(rule->world, entities[offset]), + ecs_check(ecs_is_valid(rule->filter.world, entities[offset]), ECS_INVALID_PARAMETER, NULL); return entities[offset]; @@ -20482,15 +21597,15 @@ ecs_table_range_t reg_get_range( int32_t r) { if (r == UINT8_MAX) { - ecs_check(ecs_is_valid(rule->world, op->subject), + ecs_check(ecs_is_valid(rule->filter.world, op->subject), ECS_INVALID_PARAMETER, NULL); - return table_from_entity(rule->world, op->subject); + return table_from_entity(rule->filter.world, op->subject); } if (rule->vars[r].kind == EcsRuleVarKindTable) { return table_reg_get(rule, regs, r); } if (rule->vars[r].kind == EcsRuleVarKindEntity) { - return table_from_entity(rule->world, entity_reg_get(rule, regs, r)); + return table_from_entity(rule->filter.world, entity_reg_get(rule, regs, r)); } error: return (ecs_table_range_t){0}; @@ -20504,7 +21619,7 @@ void reg_set_entity( ecs_entity_t entity) { if (rule->vars[r].kind == EcsRuleVarKindTable) { - ecs_world_t *world = rule->world; + ecs_world_t *world = rule->filter.world; ecs_check(ecs_is_valid(world, entity), ECS_INVALID_PARAMETER, NULL); regs[r].range = table_from_entity(world, entity); regs[r].entity = entity; @@ -20576,22 +21691,24 @@ ecs_rule_pair_t term_to_pair( /* Test if predicate is transitive. When evaluating the predicate, this * will also take into account transitive relationships */ - if (ecs_has_id(rule->world, pred_id, EcsTransitive)) { + if (ecs_has_id(rule->filter.world, pred_id, EcsTransitive)) { /* Transitive queries must have an object */ if (obj_is_set(term)) { - result.transitive = true; + if (term->second.flags & (EcsUp|EcsTraverseAll)) { + result.transitive = true; + } } } - if (ecs_has_id(rule->world, pred_id, EcsFinal)) { + if (ecs_has_id(rule->filter.world, pred_id, EcsFinal)) { result.final = true; } - if (ecs_has_id(rule->world, pred_id, EcsReflexive)) { + if (ecs_has_id(rule->filter.world, pred_id, EcsReflexive)) { result.reflexive = true; } - if (ecs_has_id(rule->world, pred_id, EcsAcyclic)) { + if (ecs_has_id(rule->filter.world, pred_id, EcsAcyclic)) { result.acyclic = true; } } @@ -20727,7 +21844,7 @@ void reify_variables( ECS_INTERNAL_ERROR, NULL); entity_reg_set(rule, regs, obj_var, - ecs_get_alive(rule->world, ECS_PAIR_SECOND(*elem))); + ecs_get_alive(rule->filter.world, ECS_PAIR_SECOND(*elem))); } if (pred_var != -1) { @@ -20735,7 +21852,7 @@ void reify_variables( ECS_INTERNAL_ERROR, NULL); entity_reg_set(rule, regs, pred_var, - ecs_get_alive(rule->world, + ecs_get_alive(rule->filter.world, ECS_PAIR_FIRST(*elem))); } } @@ -21518,7 +22635,7 @@ void insert_reflexive_set( /* If the object of the filter is not a variable, store literal */ if (!second) { store->r_in = UINT8_MAX; - store->subject = ecs_get_alive(rule->world, pair.second.id); + store->subject = ecs_get_alive(rule->filter.world, pair.second.id); store->filter.second = pair.second; } else { store->r_in = second->id; @@ -21654,7 +22771,7 @@ void set_input_to_subj( op->subject = term->src.id; /* Invalid entities should have been caught during parsing */ - ecs_assert(ecs_is_valid(rule->world, op->subject), + ecs_assert(ecs_is_valid(rule->filter.world, op->subject), ECS_INTERNAL_ERROR, NULL); } else { op->r_in = var->id; @@ -21676,7 +22793,7 @@ void set_output_to_subj( op->subject = term->src.id; /* Invalid entities should have been caught during parsing */ - ecs_assert(ecs_is_valid(rule->world, op->subject), + ecs_assert(ecs_is_valid(rule->filter.world, op->subject), ECS_INTERNAL_ERROR, NULL); } else { op->r_out = var->id; @@ -22399,6 +23516,32 @@ int32_t find_term_var_id( return -1; } +static +void flecs_rule_fini( + ecs_rule_t *rule) +{ + int32_t i; + for (i = 0; i < rule->var_count; i ++) { + ecs_os_free(rule->vars[i].name); + } + + ecs_filter_fini(&rule->filter); + + ecs_os_free(rule->operations); + ecs_os_free(rule); +} + +void ecs_rule_fini( + ecs_rule_t *rule) +{ + if (rule->filter.entity) { + /* If filter is associated with entity, use poly dtor path */ + ecs_delete(rule->filter.world, rule->filter.entity); + } else { + flecs_rule_fini(rule); + } +} + ecs_rule_t* ecs_rule_init( ecs_world_t *world, const ecs_filter_desc_t *const_desc) @@ -22413,8 +23556,6 @@ ecs_rule_t* ecs_rule_init( goto error; } - result->world = world; - /* Rule has no terms */ if (!result->filter.term_count) { rule_error(result, "rule has no terms"); @@ -22480,26 +23621,21 @@ ecs_rule_t* ecs_rule_init( result->iterable.init = rule_iter_init; + ecs_entity_t entity = const_desc->entity; + result->dtor = (ecs_poly_dtor_t)flecs_rule_fini; + + if (entity) { + EcsPoly *poly = ecs_poly_bind(world, entity, ecs_rule_t); + poly->poly = result; + ecs_poly_modified(world, entity, ecs_rule_t); + } + return result; error: ecs_rule_fini(result); return NULL; } -void ecs_rule_fini( - ecs_rule_t *rule) -{ - int32_t i; - for (i = 0; i < rule->var_count; i ++) { - ecs_os_free(rule->vars[i].name); - } - - ecs_filter_fini(&rule->filter); - - ecs_os_free(rule->operations); - ecs_os_free(rule); -} - const ecs_filter_t* ecs_rule_get_filter( const ecs_rule_t *rule) { @@ -22526,7 +23662,7 @@ char* ecs_rule_str( { ecs_check(rule != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_world_t *world = rule->world; + ecs_world_t *world = rule->filter.world; ecs_strbuf_t buf = ECS_STRBUF_INIT; char filter_expr[256]; @@ -22725,7 +23861,7 @@ ecs_iter_t ecs_rule_iter( int i; result.world = (ecs_world_t*)world; - result.real_world = (ecs_world_t*)ecs_get_world(rule->world); + result.real_world = (ecs_world_t*)ecs_get_world(rule->filter.world); flecs_process_pending_tables(result.real_world); @@ -22767,7 +23903,7 @@ ecs_iter_t ecs_rule_iter( result.next = ecs_rule_next; result.fini = ecs_rule_iter_free; ECS_BIT_COND(result.flags, EcsIterIsFilter, - ECS_BIT_IS_SET(rule->filter.flags, EcsFilterIsFilter)); + ECS_BIT_IS_SET(rule->filter.flags, EcsFilterNoData)); flecs_iter_init(world, &result, flecs_iter_cache_ids | @@ -22940,7 +24076,7 @@ void set_term_vars( ecs_id_t id) { if (term != -1) { - ecs_world_t *world = rule->world; + ecs_world_t *world = rule->filter.world; const ecs_rule_term_vars_t *vars = &rule->term_vars[term]; if (vars->first != -1) { regs[vars->first].entity = ecs_pair_first(world, id); @@ -22989,7 +24125,7 @@ bool eval_superset( { ecs_rule_iter_t *iter = &it->priv.iter.rule; const ecs_rule_t *rule = iter->rule; - ecs_world_t *world = rule->world; + ecs_world_t *world = rule->filter.world; ecs_rule_superset_ctx_t *op_ctx = &iter->op_ctx[op_index].is.superset; ecs_rule_superset_frame_t *frame = NULL; ecs_var_t *regs = get_registers(iter, op); @@ -23118,7 +24254,7 @@ bool eval_subset( { ecs_rule_iter_t *iter = &it->priv.iter.rule; const ecs_rule_t *rule = iter->rule; - ecs_world_t *world = rule->world; + ecs_world_t *world = rule->filter.world; ecs_rule_subset_ctx_t *op_ctx = &iter->op_ctx[op_index].is.subset; ecs_rule_subset_frame_t *frame = NULL; ecs_table_record_t table_record; @@ -23257,7 +24393,7 @@ bool eval_select( { ecs_rule_iter_t *iter = &it->priv.iter.rule; const ecs_rule_t *rule = iter->rule; - ecs_world_t *world = rule->world; + ecs_world_t *world = rule->filter.world; ecs_rule_with_ctx_t *op_ctx = &iter->op_ctx[op_index].is.with; ecs_table_record_t table_record; ecs_var_t *regs = get_registers(iter, op); @@ -23406,7 +24542,7 @@ bool eval_with( { ecs_rule_iter_t *iter = &it->priv.iter.rule; const ecs_rule_t *rule = iter->rule; - ecs_world_t *world = rule->world; + ecs_world_t *world = rule->filter.world; ecs_rule_with_ctx_t *op_ctx = &iter->op_ctx[op_index].is.with; ecs_var_t *regs = get_registers(iter, op); @@ -23641,7 +24777,7 @@ bool eval_store( ecs_rule_iter_t *iter = &it->priv.iter.rule; const ecs_rule_t *rule = iter->rule; - ecs_world_t *world = rule->world; + ecs_world_t *world = rule->filter.world; ecs_var_t *regs = get_registers(iter, op); int32_t r_in = op->r_in; int32_t r_out = op->r_out; @@ -23774,7 +24910,7 @@ bool eval_intable( ecs_rule_iter_t *iter = &it->priv.iter.rule; const ecs_rule_t *rule = iter->rule; - ecs_world_t *world = rule->world; + ecs_world_t *world = rule->filter.world; ecs_var_t *regs = get_registers(iter, op); ecs_table_t *table = table_reg_get(rule, regs, op->r_in).table; @@ -23896,7 +25032,7 @@ void populate_iterator( ecs_rule_iter_t *it, ecs_rule_op_t *op) { - ecs_world_t *world = rule->world; + ecs_world_t *world = rule->filter.world; int32_t r = op->r_in; ecs_var_t *regs = get_register_frame(it, op->frame); ecs_table_t *table = NULL; @@ -23917,7 +25053,7 @@ void populate_iterator( table = slice.table; count = slice.count; offset = slice.offset; - if (!count) { + if (!count && table) { count = ecs_table_count(table); } } else { @@ -24206,6 +25342,11 @@ error: #endif +/** + * @file addons/module.c + * @brief Module addon. + */ + #ifdef FLECS_MODULE @@ -24429,6 +25570,16 @@ error: #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 @@ -24717,6 +25868,11 @@ ecs_entity_t ecs_quantity_init( #endif +/** + * @file meta/serialized.c + * @brief Serialize type into flat operations array to speed up deserialization. + */ + #ifdef FLECS_META @@ -25016,6 +26172,11 @@ void ecs_meta_type_serialized_init( #endif +/** + * @file meta/meta.c + * @brief Meta addon. + */ + #ifdef FLECS_META @@ -25290,18 +26451,19 @@ int init_type( } else { const EcsComponent *comp = ecs_get(world, type, EcsComponent); if (comp->size < size) { - ecs_err("computed size for '%s' is larger than actual type", - ecs_get_name(world, type)); + 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 for '%s' is larger than actual type", - ecs_get_name(world, type)); + 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", ecs_get_name(world, type)); + "alignment is different (%d vs. %d)", ecs_get_name(world, type), + alignment, comp->alignment); return -1; } @@ -26336,6 +27498,11 @@ void FlecsMetaImport( #endif +/** + * @file meta/api.c + * @brief API for assigning values of runtime types with reflection. + */ + #include #ifdef FLECS_META @@ -26376,7 +27543,7 @@ const char* flecs_meta_op_kind_str( /* Get current scope */ static -ecs_meta_scope_t* get_scope( +ecs_meta_scope_t* flecs_meta_cursor_get_scope( const ecs_meta_cursor_t *cursor) { ecs_check(cursor != NULL, ECS_INVALID_PARAMETER, NULL); @@ -26385,6 +27552,21 @@ error: return NULL; } +/* Restore scope, if dotmember was used */ +static +void flecs_meta_cursor_restore_scope( + ecs_meta_cursor_t *cursor, + const ecs_meta_scope_t* scope) +{ + ecs_check(cursor != NULL, ECS_INVALID_PARAMETER, NULL); + ecs_check(scope != NULL, ECS_INVALID_PARAMETER, NULL); + if (scope->prev_depth) { + cursor->depth = scope->prev_depth; + } +error: + return; +} + /* Get previous scope */ static ecs_meta_scope_t* get_prev_scope( @@ -26399,7 +27581,7 @@ error: /* Get current operation for scope */ static -ecs_meta_type_op_t* get_op( +ecs_meta_type_op_t* flecs_meta_cursor_get_op( ecs_meta_scope_t *scope) { ecs_assert(scope->ops != NULL, ECS_INVALID_OPERATION, NULL); @@ -26446,17 +27628,17 @@ int32_t get_elem_count( return ecs_vector_count(*(scope->vector)); } - ecs_meta_type_op_t *op = get_op(scope); + 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* get_ptr( +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 = get_op(scope); + ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); ecs_size_t size = get_size(world, scope); if (scope->vector) { @@ -26470,7 +27652,7 @@ ecs_meta_type_op_t* get_ptr( } static -int push_type( +int flecs_meta_cursor_push_type( const ecs_world_t *world, ecs_meta_scope_t *scope, ecs_entity_t type, @@ -26509,7 +27691,7 @@ ecs_meta_cursor_t ecs_meta_cursor( .valid = true }; - if (push_type(world, result.scope, type, ptr) != 0) { + if (flecs_meta_cursor_push_type(world, result.scope, type, ptr) != 0) { result.valid = false; } @@ -26521,14 +27703,15 @@ error: void* ecs_meta_get_ptr( ecs_meta_cursor_t *cursor) { - return get_ptr(cursor->world, get_scope(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 = get_scope(cursor); - ecs_meta_type_op_t *op = get_op(scope); + ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); + ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); if (scope->is_collection) { scope->elem_cur ++; @@ -26554,7 +27737,7 @@ int ecs_meta_elem( ecs_meta_cursor_t *cursor, int32_t elem) { - ecs_meta_scope_t *scope = get_scope(cursor); + 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; @@ -26581,8 +27764,8 @@ int ecs_meta_member( } ecs_meta_scope_t *prev_scope = get_prev_scope(cursor); - ecs_meta_scope_t *scope = get_scope(cursor); - ecs_meta_type_op_t *push_op = get_op(prev_scope); + ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); + ecs_meta_type_op_t *push_op = flecs_meta_cursor_get_op(prev_scope); const ecs_world_t *world = cursor->world; ecs_assert(push_op->kind == EcsOpPush, ECS_INTERNAL_ERROR, NULL); @@ -26605,11 +27788,61 @@ int ecs_meta_member( return 0; } +int ecs_meta_dotmember( + ecs_meta_cursor_t *cursor, + const char *name) +{ +#ifdef FLECS_PARSER + int32_t prev_depth = cursor->depth; + int dotcount = 0; + + char token[ECS_MAX_TOKEN_SIZE]; + const char *ptr = name; + while ((ptr = ecs_parse_token(NULL, NULL, ptr, token, '.'))) { + if (ptr[0] != '.' && ptr[0]) { + ecs_parser_error(NULL, name, ptr - name, + "expected '.' or end of string"); + goto error; + } + + if (dotcount) { + ecs_meta_push(cursor); + } + + if (ecs_meta_member(cursor, token)) { + goto error; + } + + if (!ptr[0]) { + break; + } + + ptr ++; /* Skip . */ + + dotcount ++; + } + + ecs_meta_scope_t *cur_scope = flecs_meta_cursor_get_scope(cursor); + if (dotcount) { + cur_scope->prev_depth = prev_depth; + } + + return 0; +error: + return -1; +#else + (void)cursor; + (void)name; + ecs_err("the FLECS_PARSER addon is required for ecs_meta_dotmember"); + return -1; +#endif +} + int ecs_meta_push( ecs_meta_cursor_t *cursor) { - ecs_meta_scope_t *scope = get_scope(cursor); - ecs_meta_type_op_t *op = get_op(scope); + 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) { @@ -26621,12 +27854,12 @@ int ecs_meta_push( } } - void *ptr = get_ptr(world, scope); + 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 = get_scope(cursor); + 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. @@ -26660,7 +27893,7 @@ int ecs_meta_push( break; case EcsOpArray: { - if (push_type(world, next_scope, op->type, ptr) != 0) { + if (flecs_meta_cursor_push_type(world, next_scope, op->type, ptr) != 0) { goto error; } @@ -26672,7 +27905,7 @@ int ecs_meta_push( case EcsOpVector: next_scope->vector = ptr; - if (push_type(world, next_scope, op->type, NULL) != 0) { + if (flecs_meta_cursor_push_type(world, next_scope, op->type, NULL) != 0) { goto error; } @@ -26707,22 +27940,22 @@ int ecs_meta_pop( return 0; } - ecs_meta_scope_t *scope = get_scope(cursor); + ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); cursor->depth --; if (cursor->depth < 0) { ecs_err("unexpected end of scope"); return -1; } - ecs_meta_scope_t *next_scope = get_scope(cursor); - ecs_meta_type_op_t *op = get_op(next_scope); + 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 = get_op(next_scope); + 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 */ @@ -26736,37 +27969,38 @@ int ecs_meta_pop( ecs_assert(next_scope->op_count > 1, ECS_INTERNAL_ERROR, NULL); } + flecs_meta_cursor_restore_scope(cursor, next_scope); return 0; } bool ecs_meta_is_collection( const ecs_meta_cursor_t *cursor) { - ecs_meta_scope_t *scope = get_scope(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 = get_scope(cursor); - ecs_meta_type_op_t *op = get_op(scope); + 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 = get_scope(cursor); - ecs_meta_type_op_t *op = get_op(scope); + 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 = get_scope(cursor); - ecs_meta_type_op_t *op = get_op(scope); + 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; } @@ -26901,9 +28135,9 @@ int ecs_meta_set_bool( ecs_meta_cursor_t *cursor, bool value) { - ecs_meta_scope_t *scope = get_scope(cursor); - ecs_meta_type_op_t *op = get_op(scope); - void *ptr = get_ptr(cursor->world, scope); + 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); @@ -26913,6 +28147,7 @@ int ecs_meta_set_bool( return -1; } + flecs_meta_cursor_restore_scope(cursor, scope); return 0; } @@ -26920,9 +28155,9 @@ int ecs_meta_set_char( ecs_meta_cursor_t *cursor, char value) { - ecs_meta_scope_t *scope = get_scope(cursor); - ecs_meta_type_op_t *op = get_op(scope); - void *ptr = get_ptr(cursor->world, scope); + 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); @@ -26932,6 +28167,7 @@ int ecs_meta_set_char( return -1; } + flecs_meta_cursor_restore_scope(cursor, scope); return 0; } @@ -26939,9 +28175,9 @@ int ecs_meta_set_int( ecs_meta_cursor_t *cursor, int64_t value) { - ecs_meta_scope_t *scope = get_scope(cursor); - ecs_meta_type_op_t *op = get_op(scope); - void *ptr = get_ptr(cursor->world, scope); + 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); @@ -26955,6 +28191,7 @@ int ecs_meta_set_int( } } + flecs_meta_cursor_restore_scope(cursor, scope); return 0; } @@ -26962,9 +28199,9 @@ int ecs_meta_set_uint( ecs_meta_cursor_t *cursor, uint64_t value) { - ecs_meta_scope_t *scope = get_scope(cursor); - ecs_meta_type_op_t *op = get_op(scope); - void *ptr = get_ptr(cursor->world, scope); + 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); @@ -26977,6 +28214,7 @@ int ecs_meta_set_uint( return -1; } + flecs_meta_cursor_restore_scope(cursor, scope); return 0; } @@ -26984,9 +28222,9 @@ int ecs_meta_set_float( ecs_meta_cursor_t *cursor, double value) { - ecs_meta_scope_t *scope = get_scope(cursor); - ecs_meta_type_op_t *op = get_op(scope); - void *ptr = get_ptr(cursor->world, scope); + 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); @@ -26998,6 +28236,7 @@ int ecs_meta_set_float( return -1; } + flecs_meta_cursor_restore_scope(cursor, scope); return 0; } @@ -27045,15 +28284,16 @@ int ecs_meta_set_value( } else if (mt->kind == EcsBitmaskType) { return ecs_meta_set_int(cursor, *(uint32_t*)value->ptr); } else { - ecs_meta_scope_t *scope = get_scope(cursor); - ecs_meta_type_op_t *op = get_op(scope); - void *ptr = get_ptr(cursor->world, scope); + ecs_meta_scope_t *scope = flecs_meta_cursor_get_scope(cursor); + ecs_meta_type_op_t *op = flecs_meta_cursor_get_op(scope); + void *ptr = flecs_meta_cursor_get_ptr(cursor->world, scope); if (op->type != value->type) { char *type_str = ecs_get_fullpath(cursor->world, value->type); conversion_error(cursor, op, type_str); ecs_os_free(type_str); goto error; } + flecs_meta_cursor_restore_scope(cursor, scope); return ecs_value_copy(cursor->world, value->type, ptr, value->ptr); } @@ -27131,9 +28371,9 @@ int ecs_meta_set_string( ecs_meta_cursor_t *cursor, const char *value) { - ecs_meta_scope_t *scope = get_scope(cursor); - ecs_meta_type_op_t *op = get_op(scope); - void *ptr = get_ptr(cursor->world, scope); + 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: @@ -27244,6 +28484,7 @@ int ecs_meta_set_string( return -1; } + flecs_meta_cursor_restore_scope(cursor, scope); return 0; } @@ -27251,9 +28492,9 @@ int ecs_meta_set_string_literal( ecs_meta_cursor_t *cursor, const char *value) { - ecs_meta_scope_t *scope = get_scope(cursor); - ecs_meta_type_op_t *op = get_op(scope); - void *ptr = get_ptr(cursor->world, scope); + 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] != '\"') { @@ -27285,6 +28526,7 @@ int ecs_meta_set_string_literal( break; } + flecs_meta_cursor_restore_scope(cursor, scope); return 0; } @@ -27292,9 +28534,9 @@ int ecs_meta_set_entity( ecs_meta_cursor_t *cursor, ecs_entity_t value) { - ecs_meta_scope_t *scope = get_scope(cursor); - ecs_meta_type_op_t *op = get_op(scope); - void *ptr = get_ptr(cursor->world, scope); + 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: @@ -27305,15 +28547,16 @@ int ecs_meta_set_entity( return -1; } + flecs_meta_cursor_restore_scope(cursor, scope); return 0; } int ecs_meta_set_null( ecs_meta_cursor_t *cursor) { - ecs_meta_scope_t *scope = get_scope(cursor); - ecs_meta_type_op_t *op = get_op(scope); - void *ptr = get_ptr(cursor->world, scope); + 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); @@ -27324,15 +28567,16 @@ int ecs_meta_set_null( return -1; } + flecs_meta_cursor_restore_scope(cursor, scope); return 0; } bool ecs_meta_get_bool( const ecs_meta_cursor_t *cursor) { - ecs_meta_scope_t *scope = get_scope(cursor); - ecs_meta_type_op_t *op = get_op(scope); - void *ptr = get_ptr(cursor->world, scope); + 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; @@ -27356,6 +28600,7 @@ bool ecs_meta_get_bool( default: ecs_throw(ECS_INVALID_PARAMETER, "invalid element for bool"); } + error: return 0; } @@ -27363,14 +28608,15 @@ error: char ecs_meta_get_char( const ecs_meta_cursor_t *cursor) { - ecs_meta_scope_t *scope = get_scope(cursor); - ecs_meta_type_op_t *op = get_op(scope); - void *ptr = get_ptr(cursor->world, scope); + 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; } @@ -27378,9 +28624,9 @@ error: int64_t ecs_meta_get_int( const ecs_meta_cursor_t *cursor) { - ecs_meta_scope_t *scope = get_scope(cursor); - ecs_meta_type_op_t *op = get_op(scope); - void *ptr = get_ptr(cursor->world, scope); + 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; @@ -27406,6 +28652,7 @@ int64_t ecs_meta_get_int( break; default: ecs_throw(ECS_INVALID_PARAMETER, "invalid element for int"); } + error: return 0; } @@ -27413,9 +28660,9 @@ error: uint64_t ecs_meta_get_uint( const ecs_meta_cursor_t *cursor) { - ecs_meta_scope_t *scope = get_scope(cursor); - ecs_meta_type_op_t *op = get_op(scope); - void *ptr = get_ptr(cursor->world, scope); + 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); @@ -27445,9 +28692,9 @@ error: double ecs_meta_get_float( const ecs_meta_cursor_t *cursor) { - ecs_meta_scope_t *scope = get_scope(cursor); - ecs_meta_type_op_t *op = get_op(scope); - void *ptr = get_ptr(cursor->world, scope); + 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; @@ -27480,9 +28727,9 @@ error: const char* ecs_meta_get_string( const ecs_meta_cursor_t *cursor) { - ecs_meta_scope_t *scope = get_scope(cursor); - ecs_meta_type_op_t *op = get_op(scope); - void *ptr = get_ptr(cursor->world, scope); + 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"); @@ -27494,9 +28741,9 @@ error: ecs_entity_t ecs_meta_get_entity( const ecs_meta_cursor_t *cursor) { - ecs_meta_scope_t *scope = get_scope(cursor); - ecs_meta_type_op_t *op = get_op(scope); - void *ptr = get_ptr(cursor->world, scope); + 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"); @@ -27507,6 +28754,10 @@ error: #endif +/** + * @file expr/serialize.c + * @brief Serialize (component) values to flecs string format. + */ #ifdef FLECS_EXPR @@ -27988,6 +29239,10 @@ int ecs_primitive_to_expr_buf( #endif +/** + * @file expr/vars.c + * @brief Utilities for variable substitution in flecs string expressions. + */ #ifdef FLECS_EXPR @@ -28153,6 +29408,10 @@ ecs_expr_var_t* ecs_vars_lookup( #endif +/** + * @file expr/strutil.c + * @brief String parsing utilities. + */ #ifdef FLECS_EXPR @@ -28326,6 +29585,11 @@ char* ecs_astresc( #endif +/** + * @file expr/deserialize.c + * @brief Deserialize flecs string format into (component) values. + */ + #include #ifdef FLECS_EXPR @@ -28487,7 +29751,7 @@ const char *ecs_parse_expr_token( } } - while ((ptr = ecs_parse_token(name, expr, ptr, token_ptr))) { + 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] = '|'; @@ -29438,6 +30702,10 @@ const char* ecs_parse_expr( #endif +/** + * @file addons/stats.c + * @brief Stats addon. + */ #ifdef FLECS_SYSTEM @@ -29452,10 +30720,10 @@ const char* ecs_parse_expr( flecs_gauge_record(m, t, (ecs_float_t)(value)) #define ECS_COUNTER_RECORD(m, t, value)\ - flecs_counter_record(m, t, (ecs_float_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(int32_t))) + 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))) @@ -29486,19 +30754,19 @@ void flecs_gauge_record( } static -ecs_float_t flecs_counter_record( +double flecs_counter_record( ecs_metric_t *m, int32_t t, - ecs_float_t value) + double value) { int32_t tp = t_prev(t); - ecs_float_t prev = m->counter.value[tp]; + double prev = m->counter.value[tp]; m->counter.value[t] = value; - ecs_float_t gauge_value = value - prev; + double gauge_value = value - prev; if (gauge_value < 0) { gauge_value = 0; /* Counters are monotonically increasing */ } - flecs_gauge_record(m, t, gauge_value); + flecs_gauge_record(m, t, (ecs_float_t)gauge_value); return gauge_value; } @@ -29558,7 +30826,7 @@ void ecs_metric_reduce( dst->gauge.max[t_dst] = src->gauge.max[t]; } } - + dst->counter.value[t_dst] = src->counter.value[t_src]; error: @@ -29686,7 +30954,7 @@ void ecs_world_stats_get( int32_t t = s->t = t_next(s->t); - ecs_ftime_t delta_frame_count = + 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); @@ -29695,7 +30963,7 @@ void ecs_world_stats_get( 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); - ecs_ftime_t delta_world_time = + 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); @@ -29705,7 +30973,7 @@ void ecs_world_stats_get( 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, (ecs_ftime_t)1 / (delta_world_time / (ecs_ftime_t)delta_frame_count)); + ECS_GAUGE_RECORD(&s->performance.fps, t, (double)1 / (delta_world_time / (double)delta_frame_count)); } else { ECS_GAUGE_RECORD(&s->performance.fps, t, 0); } @@ -29718,7 +30986,7 @@ void ecs_world_stats_get( 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_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); @@ -29764,6 +31032,34 @@ void ecs_world_stats_get( 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; } @@ -29947,10 +31243,12 @@ bool ecs_pipeline_stats_get( ecs_check(pipeline != 0, ECS_INVALID_PARAMETER, NULL); const ecs_world_t *world = ecs_get_world(stage); - const EcsPipeline *pq = ecs_get(world, pipeline, EcsPipeline); - if (!pq) { + 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; @@ -29970,10 +31268,10 @@ bool ecs_pipeline_stats_get( } /* Also count synchronization points */ - ecs_vector_t *ops = pq->ops; - ecs_pipeline_op_t *op = ecs_vector_first(ops, ecs_pipeline_op_t); - ecs_pipeline_op_t *op_last = ecs_vector_last(ops, ecs_pipeline_op_t); - int32_t pip_count = active_sys_count + ecs_vector_count(ops); + 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; @@ -30188,6 +31486,10 @@ error: #endif +/** + * @file addons/units.c + * @brief Units addon. + */ #ifdef FLECS_UNITS @@ -30262,6 +31564,7 @@ ECS_DECLARE(EcsLength); ECS_DECLARE(EcsCentiMeters); ECS_DECLARE(EcsKiloMeters); ECS_DECLARE(EcsMiles); + ECS_DECLARE(EcsPixels); ECS_DECLARE(EcsPressure); ECS_DECLARE(EcsPascal); @@ -30312,6 +31615,17 @@ ECS_DECLARE(EcsAngle); 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) { @@ -30743,6 +32057,16 @@ void FlecsUnitsImport( .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 */ @@ -31123,6 +32447,76 @@ void FlecsUnitsImport( .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); @@ -31176,11 +32570,20 @@ void FlecsUnitsImport( 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 @@ -31605,6 +33008,11 @@ void ecs_snapshot_free( #endif +/** + * @file addons/system/system.c + * @brief System addon. + */ + #ifdef FLECS_SYSTEM @@ -31676,13 +33084,23 @@ ecs_entity_t ecs_run_intern( 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; - ecs_defer_begin(thread_ctx); + 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); @@ -31694,14 +33112,6 @@ ecs_entity_t ecs_run_intern( it = &wit; } - 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; - ecs_iter_action_t action = system_data->action; it->callback = action; @@ -31720,15 +33130,16 @@ ecs_entity_t ecs_run_intern( } } else { action(&qit); + ecs_iter_fini(&qit); } if (measure_time) { - system_data->time_spent += (float)ecs_time_measure(&time_start); + system_data->time_spent += (ecs_ftime_t)ecs_time_measure(&time_start); } system_data->invoke_count ++; - ecs_defer_end(thread_ctx); + flecs_defer_end(world, stage); return it->interrupted_by; } @@ -31851,7 +33262,7 @@ ecs_entity_t ecs_system_init( system->entity = entity; ecs_query_desc_t query_desc = desc->query; - query_desc.entity = entity; + query_desc.filter.entity = entity; ecs_query_t *query = ecs_query_init(world, &query_desc); if (!query) { @@ -31860,10 +33271,10 @@ ecs_entity_t ecs_system_init( } /* Prevent the system from moving while we're initializing */ - ecs_defer_begin(world); + flecs_defer_begin(world, &world->stages[0]); system->query = query; - system->query_entity = query->entity; + system->query_entity = query->filter.entity; system->run = desc->run; system->action = desc->callback; @@ -31877,7 +33288,7 @@ ecs_entity_t ecs_system_init( system->tick_source = desc->tick_source; system->multi_threaded = desc->multi_threaded; - system->no_staging = desc->no_staging; + system->no_readonly = desc->no_readonly; if (desc->interval != 0 || desc->rate != 0 || desc->tick_source != 0) { #ifdef FLECS_TIMER @@ -31922,8 +33333,8 @@ ecs_entity_t ecs_system_init( if (desc->multi_threaded) { system->multi_threaded = desc->multi_threaded; } - if (desc->no_staging) { - system->no_staging = desc->no_staging; + if (desc->no_readonly) { + system->no_readonly = desc->no_readonly; } } @@ -31953,6 +33364,16 @@ void FlecsSystemImport( #endif +/** + * @file json/json.c + * @brief JSON serializer utilities. + */ + +/** + * @file json/json.h + * @brief Internal functions for JSON addon. + */ + #ifdef FLECS_JSON @@ -31989,6 +33410,10 @@ 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); @@ -32098,6 +33523,25 @@ void flecs_json_string( 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) @@ -32186,6 +33630,10 @@ ecs_primitive_kind_t flecs_json_op_to_primitive_kind( #endif +/** + * @file json/serialize.c + * @brief Serialize (component) values to JSON strings. + */ #ifdef FLECS_JSON @@ -33022,10 +34470,8 @@ int ecs_entity_to_json_buf( flecs_json_object_push(buf); if (!desc || desc->serialize_path) { - char *path = ecs_get_fullpath(world, entity); flecs_json_memberl(buf, "path"); - flecs_json_string(buf, path); - ecs_os_free(path); + flecs_json_path(buf, world, entity); } #ifdef FLECS_DOC @@ -33033,7 +34479,7 @@ int ecs_entity_to_json_buf( flecs_json_memberl(buf, "label"); const char *doc_name = ecs_doc_get_name(world, entity); if (doc_name) { - flecs_json_string(buf, doc_name); + flecs_json_string_escape(buf, doc_name); } else { char num_buf[20]; ecs_os_sprintf(num_buf, "%u", (uint32_t)entity); @@ -33045,7 +34491,7 @@ int ecs_entity_to_json_buf( const char *doc_brief = ecs_doc_get_brief(world, entity); if (doc_brief) { flecs_json_memberl(buf, "brief"); - flecs_json_string(buf, doc_brief); + flecs_json_string_escape(buf, doc_brief); } } @@ -33053,7 +34499,7 @@ int ecs_entity_to_json_buf( const char *doc_link = ecs_doc_get_link(world, entity); if (doc_link) { flecs_json_memberl(buf, "link"); - flecs_json_string(buf, doc_link); + flecs_json_string_escape(buf, doc_link); } } @@ -33061,7 +34507,7 @@ int ecs_entity_to_json_buf( const char *doc_color = ecs_doc_get_color(world, entity); if (doc_color) { flecs_json_memberl(buf, "color"); - flecs_json_string(buf, doc_color); + flecs_json_string_escape(buf, doc_color); } } #endif @@ -33672,6 +35118,11 @@ char* ecs_iter_to_json( #endif +/** + * @file json/serialize_type_info.c + * @brief Serialize type (reflection) information to JSON. + */ + #ifdef FLECS_JSON @@ -33996,6 +35447,10 @@ char* ecs_type_info_to_json( #endif +/** + * @file json/deserialize.c + * @brief Deserialize JSON strings into (component) values. + */ #ifdef FLECS_JSON @@ -34105,7 +35560,7 @@ const char* ecs_parse_json( token[len - 1] = '\0'; } - if (ecs_meta_member(&cur, token + 1) != 0) { + if (ecs_meta_dotmember(&cur, token + 1) != 0) { goto error; } } else { @@ -34131,18 +35586,304 @@ error: return NULL; } +static +const char* flecs_parse_json_path( + const ecs_world_t *world, + const char *name, + const char *expr, + const char *ptr, + char *token, + ecs_entity_t *out) +{ + /* Start of id. Ids can have two elements, in case of pairs. */ + if (ptr[0] != '"') { + ecs_parser_error(name, expr, ptr - expr, "expected starting '\"'"); + goto error; + } + + const char *token_start = ptr; + ptr = ecs_parse_expr_token(name, expr, ptr, token); + if (!ptr) { + goto error; + } + + ecs_size_t len = ecs_os_strlen(token); + if (token_start[len - 1] != '"') { + ecs_parser_error(name, expr, ptr - expr, "missing closing '\"'"); + goto error; + } + + token[len - 1] = '\0'; + ecs_entity_t result = ecs_lookup_fullpath(world, token + 1); + if (!result) { + ecs_parser_error(name, expr, ptr - expr, + "unresolved identifier '%s'", token); + goto error; + } + + *out = result; + + return ecs_parse_fluff(ptr, NULL); +error: + return NULL; +} + +const char* ecs_parse_json_values( + ecs_world_t *world, + ecs_entity_t e, + const char *ptr, + const ecs_parse_json_desc_t *desc) +{ + char token[ECS_MAX_TOKEN_SIZE]; + const char *name = NULL; + const char *expr = ptr; + if (desc) { + name = desc->name; + expr = desc->expr; + } + + const char *ids = NULL, *values = NULL; + + ptr = ecs_parse_fluff(ptr, NULL); + if (ptr[0] != '{') { + ecs_parser_error(name, expr, ptr - expr, "expected '{'"); + goto error; + } + + /* Find start of ids array */ + ptr = ecs_parse_fluff(ptr + 1, NULL); + if (ecs_os_strncmp(ptr, "\"ids\"", 5)) { + ecs_parser_error(name, expr, ptr - expr, "expected '\"ids\"'"); + goto error; + } + + ptr = ecs_parse_fluff(ptr + 5, NULL); + if (ptr[0] != ':') { + ecs_parser_error(name, expr, ptr - expr, "expected ':'"); + goto error; + } + + ptr = ecs_parse_fluff(ptr + 1, NULL); + if (ptr[0] != '[') { + ecs_parser_error(name, expr, ptr - expr, "expected '['"); + goto error; + } + + ids = ecs_parse_fluff(ptr + 1, NULL); + + /* Find start of values array */ + const char *vptr = ptr; + int32_t bracket_depth = 0; + while (vptr[0]) { + if (vptr[0] == '[') { + bracket_depth ++; + } + if (vptr[0] == ']') { + bracket_depth --; + if (!bracket_depth) { + break; + } + } + vptr ++; + } + + if (!vptr[0]) { + ecs_parser_error(name, expr, ptr - expr, + "found end of string while looking for values"); + goto error; + } + + ptr = ecs_parse_fluff(vptr + 1, NULL); + if (ptr[0] == '}') { + /* String doesn't contain values, which is valid */ + return ptr + 1; + } + + if (ptr[0] != ',') { + ecs_parser_error(name, expr, ptr - expr, "expected ','"); + goto error; + } + + ptr = ecs_parse_fluff(ptr + 1, NULL); + if (ecs_os_strncmp(ptr, "\"values\"", 8)) { + ecs_parser_error(name, expr, ptr - expr, "expected '\"values\"'"); + goto error; + } + + ptr = ecs_parse_fluff(ptr + 8, NULL); + if (ptr[0] != ':') { + ecs_parser_error(name, expr, ptr - expr, "expected ':'"); + goto error; + } + + ptr = ecs_parse_fluff(ptr + 1, NULL); + if (ptr[0] != '[') { + ecs_parser_error(name, expr, ptr - expr, "expected '['"); + goto error; + } + + values = ptr; + + /* Parse ids & values */ + while (ids[0]) { + ecs_entity_t first = 0, second = 0, type_id = 0; + ecs_id_t id; + + if (ids[0] == ']') { + /* Last id found */ + if (values[0] != ']') { + ecs_parser_error(name, expr, values - expr, "expected ']'"); + } + ptr = values; + break; + } else if (ids[0] == '[') { + ids = ecs_parse_fluff(ids + 1, NULL); + ids = flecs_parse_json_path(world, name, expr, ids, token, &first); + if (!ids) { + goto error; + } + + if (ids[0] == ',') { + /* Id is a pair*/ + ids = ecs_parse_fluff(ids + 1, NULL); + ids = flecs_parse_json_path(world, name, expr, ids, token, + &second); + if (!ids) { + goto error; + } + } + + if (ids[0] == ']') { + /* End of id array */ + } else { + ecs_parser_error(name, expr, ids - expr, "expected ',' or ']'"); + goto error; + } + + ids = ecs_parse_fluff(ids + 1, NULL); + if (ids[0] == ',') { + /* Next id */ + ids = ecs_parse_fluff(ids + 1, NULL); + } else if (ids[0] != ']') { + /* End of ids array */ + ecs_parser_error(name, expr, ids - expr, "expected ',' or ']'"); + goto error; + } + } else { + ecs_parser_error(name, expr, ids - expr, "expected '[' or ']'"); + goto error; + } + + if (second) { + id = ecs_pair(first, second); + type_id = ecs_get_typeid(world, id); + if (!type_id) { + ecs_parser_error(name, expr, ids - expr, "id is not a type"); + goto error; + } + } else { + id = first; + type_id = first; + } + + /* Get mutable pointer */ + void *comp_ptr = ecs_get_mut_id(world, e, id); + if (!comp_ptr) { + char *idstr = ecs_id_str(world, id); + ecs_parser_error(name, expr, ptr - expr, + "id '%s' is not a valid component", idstr); + ecs_os_free(idstr); + goto error; + } + + ecs_parse_json_desc_t parse_desc = { + .name = name, + .expr = expr, + }; + values = ecs_parse_json(world, values + 1, type_id, comp_ptr, &parse_desc); + if (!values) { + goto error; + } + + if (values[0] != ']' && values[0] != ',') { + ecs_parser_error(name, expr, ptr - expr, "expected ',' or ']'"); + goto error; + } + + ecs_modified_id(world, e, id); + } + + ptr = ecs_parse_fluff(ptr + 1, NULL); + if (ptr[0] != '}') { + ecs_parser_error(name, expr, ptr - expr, "expected '}'"); + } + + return ptr + 1; +error: + return NULL; +} + #endif +/** + * @file addons/rest.c + * @brief Rest addon. + */ + #ifdef FLECS_REST +/* Time interval used to determine when to return a result from the cache. Use + * a short interval so that clients still get realtime data, but multiple + * clients requesting for the same data can reuse the same result. */ +#define FLECS_REST_CACHE_TIMEOUT ((ecs_ftime_t)0.5) + +typedef struct { + char *content; + int32_t content_length; + ecs_ftime_t time; +} ecs_rest_cached_t; + typedef struct { ecs_world_t *world; ecs_entity_t entity; ecs_http_server_t *srv; + ecs_map_t reply_cache; int32_t rc; + ecs_ftime_t time; } ecs_rest_ctx_t; +/* Global statistics */ +int64_t ecs_rest_request_count = 0; +int64_t ecs_rest_entity_count = 0; +int64_t ecs_rest_entity_error_count = 0; +int64_t ecs_rest_query_count = 0; +int64_t ecs_rest_query_error_count = 0; +int64_t ecs_rest_query_name_count = 0; +int64_t ecs_rest_query_name_error_count = 0; +int64_t ecs_rest_query_name_from_cache_count = 0; +int64_t ecs_rest_enable_count = 0; +int64_t ecs_rest_enable_error_count = 0; +int64_t ecs_rest_set_count = 0; +int64_t ecs_rest_set_error_count = 0; +int64_t ecs_rest_delete_count = 0; +int64_t ecs_rest_delete_error_count = 0; +int64_t ecs_rest_world_stats_count = 0; +int64_t ecs_rest_pipeline_stats_count = 0; +int64_t ecs_rest_stats_error_count = 0; + +static +void flecs_rest_free_reply_cache(ecs_map_t *reply_cache) { + if (ecs_map_is_initialized(reply_cache)) { + ecs_map_iter_t it = ecs_map_iter(reply_cache); + ecs_rest_cached_t *reply; + while ((reply = ecs_map_next(&it, ecs_rest_cached_t, 0))) { + ecs_os_free(reply->content); + } + ecs_map_fini(reply_cache); + } +} + static ECS_COPY(EcsRest, dst, src, { ecs_rest_ctx_t *impl = src->impl; if (impl) { @@ -34166,6 +35907,7 @@ static ECS_DTOR(EcsRest, ptr, { impl->rc --; if (!impl->rc) { ecs_http_server_fini(impl->srv); + flecs_rest_free_reply_cache(&impl->reply_cache); ecs_os_free(impl); } } @@ -34305,12 +36047,15 @@ bool flecs_rest_reply_entity( 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; } @@ -34321,16 +36066,187 @@ bool flecs_rest_reply_entity( return true; } +static +ecs_entity_t flecs_rest_entity_from_path( + ecs_world_t *world, + ecs_http_reply_t *reply, + const char *path) +{ + ecs_entity_t e = ecs_lookup_path_w_sep( + world, 0, path, "/", NULL, false); + if (!e) { + flecs_reply_error(reply, "entity '%s' not found", path); + reply->code = 404; + } + return e; +} + +static +bool flecs_rest_set( + ecs_world_t *world, + const ecs_http_request_t* req, + ecs_http_reply_t *reply, + const char *path) +{ + ecs_os_linc(&ecs_rest_set_count); + + ecs_entity_t e; + if (!(e = flecs_rest_entity_from_path(world, reply, path))) { + ecs_os_linc(&ecs_rest_set_error_count); + return true; + } + + const char *data = ecs_http_get_param(req, "data"); + ecs_parse_json_desc_t desc = {0}; + desc.expr = data; + desc.name = path; + if (ecs_parse_json_values(world, e, data, &desc) == NULL) { + flecs_reply_error(reply, "invalid request"); + reply->code = 400; + ecs_os_linc(&ecs_rest_set_error_count); + return true; + } + + return true; +} + +static +bool flecs_rest_delete( + ecs_world_t *world, + ecs_http_reply_t *reply, + const char *path) +{ + ecs_os_linc(&ecs_rest_set_count); + + ecs_entity_t e; + if (!(e = flecs_rest_entity_from_path(world, reply, path))) { + ecs_os_linc(&ecs_rest_delete_error_count); + return true; + } + + ecs_delete(world, e); + + return true; +} + +static +bool flecs_rest_enable( + ecs_world_t *world, + ecs_http_reply_t *reply, + const char *path, + bool enable) +{ + ecs_os_linc(&ecs_rest_enable_count); + + ecs_entity_t e; + if (!(e = flecs_rest_entity_from_path(world, reply, path))) { + ecs_os_linc(&ecs_rest_enable_error_count); + return true; + } + + ecs_enable(world, e, enable); + + return true; +} + +static +void flecs_rest_iter_to_reply( + ecs_world_t *world, + const ecs_http_request_t* req, + ecs_http_reply_t *reply, + ecs_iter_t *it) +{ + ecs_iter_to_json_desc_t desc = ECS_ITER_TO_JSON_INIT; + flecs_rest_parse_json_ser_iter_params(&desc, req); + + int32_t offset = 0; + int32_t limit = 1000; + + flecs_rest_int_param(req, "offset", &offset); + flecs_rest_int_param(req, "limit", &limit); + + ecs_iter_t pit = ecs_page_iter(it, offset, limit); + ecs_iter_to_json_buf(world, &pit, &reply->body, &desc); +} + +static +bool flecs_rest_reply_existing_query( + ecs_world_t *world, + ecs_rest_ctx_t *impl, + const ecs_http_request_t* req, + ecs_http_reply_t *reply, + const char *name) +{ + ecs_os_linc(&ecs_rest_query_name_count); + + ecs_entity_t q = ecs_lookup_fullpath(world, name); + if (!q) { + flecs_reply_error(reply, "unresolved identifier '%s'", name); + reply->code = 404; + ecs_os_linc(&ecs_rest_query_name_error_count); + return true; + } + + ecs_map_init_if(&impl->reply_cache, ecs_rest_cached_t, NULL, 1); + ecs_rest_cached_t *cached = ecs_map_get(&impl->reply_cache, + ecs_rest_cached_t, q); + if (cached) { + if ((impl->time - cached->time) > FLECS_REST_CACHE_TIMEOUT) { + ecs_os_free(cached->content); + } else { + /* Cache hit */ + ecs_strbuf_appendstr_zerocpyn_const( + &reply->body, cached->content, cached->content_length); + ecs_os_linc(&ecs_rest_query_name_from_cache_count); + return true; + } + } else { + cached = ecs_map_ensure(&impl->reply_cache, ecs_rest_cached_t, q); + } + + /* Cache miss */ + const EcsPoly *poly = ecs_get_pair(world, q, EcsPoly, EcsQuery); + if (!poly) { + flecs_reply_error(reply, + "resolved identifier '%s' is not a query", name); + reply->code = 400; + ecs_os_linc(&ecs_rest_query_name_error_count); + return true; + } + + ecs_iter_t it; + ecs_iter_poly(world, poly->poly, &it, NULL); + flecs_rest_iter_to_reply(world, req, reply, &it); + + cached->content_length = ecs_strbuf_written(&reply->body); + cached->content = ecs_strbuf_get(&reply->body); + ecs_strbuf_reset(&reply->body); + ecs_strbuf_appendstr_zerocpyn_const( + &reply->body, cached->content, cached->content_length); + cached->time = impl->time; + + return true; +} + static bool flecs_rest_reply_query( ecs_world_t *world, + ecs_rest_ctx_t *impl, const ecs_http_request_t* req, ecs_http_reply_t *reply) { + const char *q_name = ecs_http_get_param(req, "name"); + if (q_name) { + return flecs_rest_reply_existing_query(world, impl, req, reply, q_name); + } + + ecs_os_linc(&ecs_rest_query_count); + const char *q = ecs_http_get_param(req, "q"); if (!q) { ecs_strbuf_appendlit(&reply->body, "Missing parameter 'q'"); reply->code = 400; /* bad request */ + ecs_os_linc(&ecs_rest_query_error_count); return true; } @@ -34346,22 +36262,13 @@ bool flecs_rest_reply_query( 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_to_json_desc_t desc = ECS_ITER_TO_JSON_INIT; - flecs_rest_parse_json_ser_iter_params(&desc, req); - - int32_t offset = 0; - int32_t limit = 1000; - - flecs_rest_int_param(req, "offset", &offset); - flecs_rest_int_param(req, "limit", &limit); - ecs_iter_t it = ecs_rule_iter(world, r); - ecs_iter_t pit = ecs_page_iter(&it, offset, limit); - ecs_iter_to_json_buf(world, &pit, &reply->body, &desc); + flecs_rest_iter_to_reply(world, req, reply, &it); ecs_rule_fini(r); } @@ -34521,6 +36428,31 @@ void flecs_world_stats_to_json( 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, "}"); } @@ -34592,6 +36524,7 @@ bool flecs_rest_reply_stats( if (!period) { flecs_reply_error(reply, "bad request (invalid period string)"); reply->code = 400; + ecs_os_linc(&ecs_rest_stats_error_count); return false; } } @@ -34600,17 +36533,20 @@ bool flecs_rest_reply_stats( 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; } @@ -34713,58 +36649,6 @@ bool flecs_rest_reply_tables( return true; } -static -void flecs_rest_reply_id_append( - ecs_world_t *world, - ecs_strbuf_t *reply, - const ecs_id_record_t *idr) -{ - ecs_strbuf_list_next(reply); - ecs_strbuf_list_push(reply, "{", ","); - ecs_strbuf_list_appendstr(reply, "\"id\":\""); - ecs_id_str_buf(world, idr->id, reply); - ecs_strbuf_appendch(reply, '"'); - - if (idr->type_info) { - if (idr->type_info->component != idr->id) { - ecs_strbuf_list_appendstr(reply, "\"component\":\""); - ecs_id_str_buf(world, idr->type_info->component, reply); - ecs_strbuf_appendch(reply, '"'); - } - - ecs_strbuf_list_append(reply, "\"size\":%d", - idr->type_info->size); - ecs_strbuf_list_append(reply, "\"alignment\":%d", - idr->type_info->alignment); - } - - ecs_strbuf_list_append(reply, "\"table_count\":%d", - idr->cache.tables.count); - ecs_strbuf_list_append(reply, "\"empty_table_count\":%d", - idr->cache.empty_tables.count); - - ecs_strbuf_list_pop(reply, "}"); -} - -static -bool flecs_rest_reply_ids( - ecs_world_t *world, - const ecs_http_request_t* req, - ecs_http_reply_t *reply) -{ - (void)req; - - ecs_strbuf_list_push(&reply->body, "[", ","); - ecs_map_iter_t it = ecs_map_iter(&world->id_index); - ecs_id_record_t *idr; - while ((idr = ecs_map_next_ptr(&it, ecs_id_record_t*, NULL))) { - flecs_rest_reply_id_append(world, &reply->body, idr); - } - ecs_strbuf_list_pop(&reply->body, "]"); - - return true; -} - static bool flecs_rest_reply( const ecs_http_request_t* req, @@ -34774,6 +36658,8 @@ bool flecs_rest_reply( 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)"); @@ -34781,16 +36667,14 @@ bool flecs_rest_reply( return false; } - ecs_strbuf_appendlit(&reply->headers, "Access-Control-Allow-Origin: *\r\n"); - 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); + return flecs_rest_reply_query(world, impl, req, reply); /* Stats endpoint */ } else if (!ecs_os_strncmp(req->path, "stats/", 6)) { @@ -34799,21 +36683,32 @@ bool flecs_rest_reply( /* Tables endpoint */ } else if (!ecs_os_strncmp(req->path, "tables", 6)) { return flecs_rest_reply_tables(world, req, reply); - - /* Ids endpoint */ - } else if (!ecs_os_strncmp(req->path, "ids", 3)) { - return flecs_rest_reply_ids(world, req, reply); } - } else if (req->method == EcsHttpOptions) { - return true; + + } else if (req->method == EcsHttpPut) { + /* Set endpoint */ + if (!ecs_os_strncmp(req->path, "set/", 4)) { + return flecs_rest_set(world, req, reply, &req->path[4]); + + /* Delete endpoint */ + } else if (!ecs_os_strncmp(req->path, "delete/", 7)) { + return flecs_rest_delete(world, reply, &req->path[7]); + + /* Enable endpoint */ + } else if (!ecs_os_strncmp(req->path, "enable/", 7)) { + return flecs_rest_enable(world, reply, &req->path[7], true); + + /* Disable endpoint */ + } else if (!ecs_os_strncmp(req->path, "disable/", 8)) { + return flecs_rest_enable(world, reply, &req->path[8], false); + } } return false; } static -void flecs_on_set_rest(ecs_iter_t *it) -{ +void flecs_on_set_rest(ecs_iter_t *it) { EcsRest *rest = it->ptrs[0]; int i; @@ -34822,7 +36717,7 @@ void flecs_on_set_rest(ecs_iter_t *it) rest[i].port = ECS_REST_DEFAULT_PORT; } - ecs_rest_ctx_t *srv_ctx = ecs_os_malloc_t(ecs_rest_ctx_t); + ecs_rest_ctx_t *srv_ctx = ecs_os_calloc_t(ecs_rest_ctx_t); ecs_http_server_t *srv = ecs_http_server_init(&(ecs_http_server_desc_t){ .ipaddr = rest[i].ipaddr, .port = rest[i].port, @@ -34863,11 +36758,44 @@ void DequeueRest(ecs_iter_t *it) { for(i = 0; i < it->count; i ++) { ecs_rest_ctx_t *ctx = rest[i].impl; if (ctx) { + ctx->time += it->delta_time; ecs_http_server_dequeue(ctx->srv, it->delta_time); } } } +static +void DisableRest(ecs_iter_t *it) { + ecs_world_t *world = it->world; + + ecs_iter_t rit = ecs_term_iter(world, &(ecs_term_t){ + .id = ecs_id(EcsRest), + .src.flags = EcsSelf + }); + + if (it->event == EcsOnAdd) { + /* REST module was disabled */ + while (ecs_term_next(&rit)) { + EcsRest *rest = ecs_field(&rit, EcsRest, 1); + int i; + for (i = 0; i < rit.count; i ++) { + ecs_rest_ctx_t *ctx = rest[i].impl; + ecs_http_server_stop(ctx->srv); + } + } + } else if (it->event == EcsOnRemove) { + /* REST module was enabled */ + while (ecs_term_next(&rit)) { + EcsRest *rest = ecs_field(&rit, EcsRest, 1); + int i; + for (i = 0; i < rit.count; i ++) { + ecs_rest_ctx_t *ctx = rest[i].impl; + ecs_http_server_start(ctx->srv); + } + } + } +} + void FlecsRestImport( ecs_world_t *world) { @@ -34886,15 +36814,27 @@ void FlecsRestImport( }); ECS_SYSTEM(world, DequeueRest, EcsPostFrame, EcsRest); + + ecs_observer(world, { + .filter = { + .terms = {{ .id = EcsDisabled, .src.id = ecs_id(FlecsRest) }} + }, + .events = {EcsOnAdd, EcsOnRemove}, + .callback = DisableRest + }); } #endif +/** + * @file addons/coredoc.c + * @brief Core doc addon. + */ #ifdef FLECS_COREDOC -#define URL_ROOT "https://flecs.docsforge.com/master/relationships-manual/" +#define URL_ROOT "https://www.flecs.dev/flecs/md_docs_Relationships.html/" void FlecsCoreDocImport( ecs_world_t *world) @@ -35024,12 +36964,16 @@ void FlecsCoreDocImport( #endif -/* This is a heavily modified version of the EmbeddableWebServer (see copyright +/** + * @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 + * the Flecs OS API. + * + * EmbeddableWebServer Copyright (c) 2016, 2019, 2020 Forrest Heller, and * CONTRIBUTORS (see below) - All rights reserved. * * CONTRIBUTORS: @@ -35093,7 +37037,7 @@ typedef int ecs_http_socket_t; #define ECS_HTTP_CONNECTION_PURGE_RETRY_COUNT (5) /* Minimum interval between dequeueing requests (ms) */ -#define ECS_HTTP_MIN_DEQUEUE_INTERVAL (100) +#define ECS_HTTP_MIN_DEQUEUE_INTERVAL (50) /* Minimum interval between printing statistics (ms) */ #define ECS_HTTP_MIN_STATS_INTERVAL (10 * 1000) @@ -35110,9 +37054,22 @@ typedef int ecs_http_socket_t; /* Total number of outstanding send requests */ #define ECS_HTTP_SEND_QUEUE_MAX (256) +/* Global statistics */ +int64_t ecs_http_request_received_count = 0; +int64_t ecs_http_request_invalid_count = 0; +int64_t ecs_http_request_handled_ok_count = 0; +int64_t ecs_http_request_handled_error_count = 0; +int64_t ecs_http_request_not_handled_count = 0; +int64_t ecs_http_request_preflight_count = 0; +int64_t ecs_http_send_ok_count = 0; +int64_t ecs_http_send_error_count = 0; +int64_t ecs_http_busy_count = 0; + /* Send request queue */ typedef struct ecs_http_send_request_t { ecs_http_socket_t sock; + char *headers; + int32_t header_length; char *content; int32_t content_length; } ecs_http_send_request_t; @@ -35145,11 +37102,11 @@ struct ecs_http_server_t { uint16_t port; const char *ipaddr; - ecs_ftime_t dequeue_timeout; /* used to not lock request queue too often */ - ecs_ftime_t stats_timeout; /* used for periodic reporting of statistics */ + double dequeue_timeout; /* used to not lock request queue too often */ + double stats_timeout; /* used for periodic reporting of statistics */ - ecs_ftime_t request_time; /* time spent on requests in last stats interval */ - ecs_ftime_t request_time_total; /* total time spent on requests */ + 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 */ @@ -35202,7 +37159,7 @@ typedef struct { /* 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 */ - ecs_ftime_t dequeue_timeout; + double dequeue_timeout; int32_t dequeue_retries; } ecs_http_connection_impl_t; @@ -35503,6 +37460,8 @@ void http_enqueue_request( req->pub.header_count = frag->header_count; req->pub.param_count = frag->param_count; req->res = res; + + ecs_os_linc(&ecs_http_request_received_count); } } @@ -35725,17 +37684,41 @@ void* http_server_send_queue(void* arg) { ecs_os_sleep(0, wait_ms); } } else { - ecs_size_t written = http_send( - r->sock, r->content, r->content_length, 0); - if (written != r->content_length) { - ecs_err("http: failed to write HTTP response body: %s", - ecs_os_strerror(errno)); - } - ecs_os_free(r->content); - if (http_socket_is_valid(r->sock)) { - http_close(&r->sock); - } + ecs_http_socket_t sock = r->sock; + char *headers = r->headers; + int32_t headers_length = r->header_length; + char *content = r->content; + int32_t content_length = r->content_length; ecs_os_mutex_unlock(srv->lock); + + if (http_socket_is_valid(sock)) { + bool error = false; + + /* Write headers */ + ecs_size_t written = http_send(sock, headers, headers_length, 0); + if (written != headers_length) { + ecs_err("http: failed to write HTTP response headers: %s", + ecs_os_strerror(errno)); + ecs_os_linc(&ecs_http_send_error_count); + error = true; + } else if (content_length >= 0) { + /* Write content */ + written = http_send(sock, content, content_length, 0); + if (written != content_length) { + ecs_err("http: failed to write HTTP response body: %s", + ecs_os_strerror(errno)); + ecs_os_linc(&ecs_http_send_error_count); + error = true; + } + } + if (!error) { + ecs_os_linc(&ecs_http_send_ok_count); + } + http_close(&sock); + } + + ecs_os_free(content); + ecs_os_free(headers); } } return NULL; @@ -35748,7 +37731,8 @@ void http_append_send_headers( const char* status, const char* content_type, ecs_strbuf_t *extra_headers, - ecs_size_t content_len) + ecs_size_t content_len, + bool preflight) { ecs_strbuf_appendlit(hdrs, "HTTP/1.1 "); ecs_strbuf_appendint(hdrs, code); @@ -35756,13 +37740,24 @@ void http_append_send_headers( ecs_strbuf_appendstr(hdrs, status); ecs_strbuf_appendlit(hdrs, "\r\n"); - ecs_strbuf_appendlit(hdrs, "Content-Type: "); - ecs_strbuf_appendstr(hdrs, content_type); - 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"); + } - ecs_strbuf_appendlit(hdrs, "Content-Length: "); - ecs_strbuf_append(hdrs, "%d", content_len); - ecs_strbuf_appendlit(hdrs, "\r\n"); + if (content_len >= 0) { + ecs_strbuf_appendlit(hdrs, "Content-Length: "); + ecs_strbuf_append(hdrs, "%d", content_len); + ecs_strbuf_appendlit(hdrs, "\r\n"); + } + + ecs_strbuf_appendlit(hdrs, "Access-Control-Allow-Origin: *\r\n"); + if (preflight) { + ecs_strbuf_appendlit(hdrs, "Access-Control-Allow-Private-Network: true\r\n"); + ecs_strbuf_appendlit(hdrs, "Access-Control-Allow-Methods: GET, PUT, OPTIONS\r\n"); + ecs_strbuf_appendlit(hdrs, "Access-Control-Max-Age: 600\r\n"); + } ecs_strbuf_appendlit(hdrs, "Server: flecs\r\n"); @@ -35774,51 +37769,53 @@ void http_append_send_headers( static void http_send_reply( ecs_http_connection_impl_t* conn, - ecs_http_reply_t* reply) + ecs_http_reply_t* reply, + bool preflight) { - char hdrs[ECS_HTTP_REPLY_HEADER_SIZE]; - ecs_strbuf_t hdr_buf = ECS_STRBUF_INIT; - hdr_buf.buf = hdrs; - hdr_buf.max = ECS_HTTP_REPLY_HEADER_SIZE; - hdr_buf.buf = hdrs; - + 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 (content_length > 0) { + + 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); } } - /* First, send the response HTTP headers */ - http_append_send_headers(&hdr_buf, reply->code, reply->status, - reply->content_type, &reply->headers, content_length); + 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); - ecs_size_t hdrs_len = ecs_strbuf_written(&hdr_buf); - hdrs[hdrs_len] = '\0'; - ecs_size_t written = http_send(conn->sock, hdrs, hdrs_len, 0); - - if (written != hdrs_len) { - ecs_err("http: failed to write HTTP response headers to '%s:%s': %s", - conn->pub.host, conn->pub.port, ecs_os_strerror(errno)); + 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 */ - if (req) { - req->sock = conn->sock; - req->content = content; - req->content_length = content_length; + 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; - } + /* Take ownership of values */ + reply->body.content = NULL; + conn->sock = HTTP_SOCKET_INVALID; } static @@ -35842,8 +37839,21 @@ void http_recv_request( } if (http_parse_request(&frag, recv_buf, bytes_read)) { - http_enqueue_request(conn, conn_id, &frag); + if (frag.method == EcsHttpOptions) { + ecs_http_reply_t reply; + reply.body = ECS_STRBUF_INIT; + reply.code = 200; + reply.content_type = NULL; + reply.headers = ECS_STRBUF_INIT; + reply.status = "OK"; + http_send_reply(conn, &reply, true); + ecs_os_linc(&ecs_http_request_preflight_count); + } else { + http_enqueue_request(conn, conn_id, &frag); + } return; + } else { + ecs_os_linc(&ecs_http_request_invalid_count); } } @@ -35892,8 +37902,8 @@ void http_init_connection( ecs_os_strcpy(remote_port, "unknown"); } - ecs_dbg_2("http: connection established from '%s:%s'", - remote_host, remote_port); + ecs_dbg_2("http: connection established from '%s:%s' (socket %u)", + remote_host, remote_port, sock_conn); http_recv_request(srv, conn, conn_id, sock_conn); @@ -35975,6 +37985,8 @@ void http_accept_connections( goto done; } + http_sock_set_timeout(sock, 1000); + srv->sock = sock; result = listen(srv->sock, SOMAXCONN); @@ -35999,7 +38011,7 @@ void http_accept_connections( sock_conn = http_accept(srv->sock, (struct sockaddr*) &remote_addr, &remote_addr_len); - if (sock_conn == -1) { + if (!http_socket_is_valid(sock_conn)) { if (srv->should_run) { ecs_dbg("http: connection attempt failed: %s", ecs_os_strerror(errno)); @@ -36049,13 +38061,24 @@ void http_handle_request( ecs_http_connection_impl_t *conn = (ecs_http_connection_impl_t*)req->pub.conn; - if (srv->callback((ecs_http_request_t*)req, &reply, srv->ctx) == false) { - reply.code = 404; - reply.status = "Resource not found"; - } + if (req->pub.method != EcsHttpOptions) { + if (srv->callback((ecs_http_request_t*)req, &reply, srv->ctx) == false) { + reply.code = 404; + reply.status = "Resource not found"; + ecs_os_linc(&ecs_http_request_not_handled_count); + } else { + if (reply.code >= 400) { + ecs_os_linc(&ecs_http_request_handled_error_count); + } else { + ecs_os_linc(&ecs_http_request_handled_ok_count); + } + } - http_send_reply(conn, &reply); - ecs_dbg_2("http: reply sent to '%s:%s'", conn->pub.host, conn->pub.port); + http_send_reply(conn, &reply, false); + ecs_dbg_2("http: reply sent to '%s:%s'", conn->pub.host, conn->pub.port); + } else { + /* Already taken care of */ + } http_reply_free(&reply); http_request_free(req); @@ -36065,7 +38088,7 @@ void http_handle_request( static int32_t http_dequeue_requests( ecs_http_server_t *srv, - ecs_ftime_t delta_time) + double delta_time) { ecs_os_mutex_lock(srv->lock); @@ -36085,7 +38108,7 @@ int32_t http_dequeue_requests( conn->dequeue_retries ++; if ((conn->dequeue_timeout > - (ecs_ftime_t)ECS_HTTP_CONNECTION_PURGE_TIMEOUT) && + (double)ECS_HTTP_CONNECTION_PURGE_TIMEOUT) && (conn->dequeue_retries > ECS_HTTP_CONNECTION_PURGE_RETRY_COUNT)) { ecs_dbg("http: purging connection '%s:%s' (sock = %d)", @@ -36255,12 +38278,10 @@ void ecs_http_server_dequeue( ecs_check(srv->initialized, ECS_INVALID_PARAMETER, NULL); ecs_check(srv->should_run, ECS_INVALID_PARAMETER, NULL); - srv->dequeue_timeout += delta_time; - srv->stats_timeout += delta_time; + srv->dequeue_timeout += (double)delta_time; + srv->stats_timeout += (double)delta_time; - if ((1000 * srv->dequeue_timeout) > - (ecs_ftime_t)ECS_HTTP_MIN_DEQUEUE_INTERVAL) - { + if ((1000 * srv->dequeue_timeout) > (double)ECS_HTTP_MIN_DEQUEUE_INTERVAL) { srv->dequeue_timeout = 0; ecs_time_t t = {0}; @@ -36268,19 +38289,17 @@ void ecs_http_server_dequeue( int32_t request_count = http_dequeue_requests(srv, srv->dequeue_timeout); srv->requests_processed += request_count; srv->requests_processed_total += request_count; - ecs_ftime_t time_spent = (ecs_ftime_t)ecs_time_measure(&t); + double time_spent = ecs_time_measure(&t); srv->request_time += time_spent; srv->request_time_total += time_spent; srv->dequeue_count ++; } - if ((1000 * srv->stats_timeout) > - (ecs_ftime_t)ECS_HTTP_MIN_STATS_INTERVAL) - { + if ((1000 * srv->stats_timeout) > (double)ECS_HTTP_MIN_STATS_INTERVAL) { srv->stats_timeout = 0; ecs_dbg("http: processed %d requests in %.3fs (avg %.3fs / dequeue)", - srv->requests_processed, (double)srv->request_time, - (double)(srv->request_time / (ecs_ftime_t)srv->dequeue_count)); + srv->requests_processed, srv->request_time, + (srv->request_time / (double)srv->dequeue_count)); srv->requests_processed = 0; srv->request_time = 0; srv->dequeue_count = 0; @@ -36292,6 +38311,10 @@ error: #endif +/** + * @file addons/doc.c + * @brief Doc addon. + */ #ifdef FLECS_DOC @@ -36451,6 +38474,11 @@ void FlecsDocImport( #endif +/** + * @file addons/parser.c + * @brief Parser addon. + */ + #ifdef FLECS_PARSER @@ -36656,7 +38684,8 @@ const char* ecs_parse_token( const char *name, const char *expr, const char *ptr, - char *token_out) + char *token_out, + char delim) { int64_t column = ptr - expr; @@ -36700,6 +38729,9 @@ const char* ecs_parse_token( if (!flecs_valid_token_char(ch) && !in_str) { break; } + if (delim && (ch == delim)) { + break; + } tptr[0] = ch; tptr ++; @@ -36737,7 +38769,7 @@ const char* ecs_parse_identifier( return NULL; } - ptr = ecs_parse_token(name, expr, ptr, token_out); + ptr = ecs_parse_token(name, expr, ptr, token_out, 0); return ptr; } @@ -37201,6 +39233,15 @@ parse_pair: goto error; } + if (ptr[0] == TOK_COLON) { + ptr = ecs_parse_whitespace(ptr + 1); + ptr = flecs_parse_term_flags(world, name, expr, (ptr - expr), ptr, + NULL, &term.first, TOK_PAREN_CLOSE); + if (!ptr) { + goto error; + } + } + if (ptr[0] == TOK_AND) { ptr ++; term.src.id = EcsThis; @@ -37229,6 +39270,15 @@ parse_pair_predicate: goto error; } + if (ptr[0] == TOK_COLON) { + ptr = ecs_parse_whitespace(ptr + 1); + ptr = flecs_parse_term_flags(world, name, expr, (ptr - expr), ptr, + NULL, &term.second, TOK_PAREN_CLOSE); + if (!ptr) { + goto error; + } + } + if (ptr[0] == TOK_PAREN_CLOSE) { ptr ++; goto parse_pair_object; @@ -37404,7 +39454,7 @@ char* ecs_parse_term( term->first.name = NULL; } - /* Cannot combine EcsNothing with operators other than AND */ + /* Cannot combine EcsIsEntity/0 with operators other than AND */ if (term->oper != EcsAnd && ecs_term_match_0(term)) { ecs_parser_error(name, expr, (ptr - expr), "invalid operator for empty source"); @@ -37456,6 +39506,11 @@ error: #endif +/** + * @file addons/meta.c + * @brief C utilities for meta addon. + */ + #ifdef FLECS_META_C @@ -38270,6 +40325,11 @@ error: #endif +/** + * @file addons/app.c + * @brief App addon. + */ + #ifdef FLECS_APP @@ -38386,6 +40446,11 @@ int ecs_app_set_frame_action( #endif +/** + * @file world.c + * @brief World-level API. + */ + /* Id flags */ const ecs_id_t ECS_PAIR = (1ull << 63); @@ -38490,8 +40555,9 @@ const ecs_entity_t EcsDefaultChildComponent = ECS_HI_COMPONENT_ID + 55; /* Systems */ const ecs_entity_t EcsMonitor = ECS_HI_COMPONENT_ID + 61; -const ecs_entity_t EcsEmpty = ECS_HI_COMPONENT_ID + 63; -const ecs_entity_t ecs_id(EcsPipeline) = ECS_HI_COMPONENT_ID + 64; +const ecs_entity_t EcsEmpty = ECS_HI_COMPONENT_ID + 62; +const ecs_entity_t ecs_id(EcsPipeline) = ECS_HI_COMPONENT_ID + 63; +const ecs_entity_t EcsOnStart = ECS_HI_COMPONENT_ID + 64; const ecs_entity_t EcsPreFrame = ECS_HI_COMPONENT_ID + 65; const ecs_entity_t EcsOnLoad = ECS_HI_COMPONENT_ID + 66; const ecs_entity_t EcsPostLoad = ECS_HI_COMPONENT_ID + 67; @@ -38605,8 +40671,8 @@ ecs_world_t* flecs_suspend_readonly( ecs_dbg_3("suspending readonly mode"); /* Cannot suspend when running with multiple threads */ - ecs_assert(ecs_get_stage_count(world) <= 1, - ECS_INVALID_WHILE_READONLY, NULL); + ecs_assert(!(world->flags & EcsWorldReadonly) || + (ecs_get_stage_count(world) <= 1), ECS_INVALID_WHILE_READONLY, NULL); state->is_readonly = is_readonly; state->is_deferred = is_deferred; @@ -38700,6 +40766,9 @@ void flecs_monitor_mark_dirty( if (ecs_map_is_initialized(monitors)) { ecs_monitor_t *m = ecs_map_get(monitors, ecs_monitor_t, id); if (m) { + if (!world->monitors.is_dirty) { + world->monitor_generation ++; + } m->is_dirty = true; world->monitors.is_dirty = true; } @@ -38839,7 +40908,7 @@ void flecs_fini_roots(ecs_world_t *world) { * regular entities first, which reduces the chance of components getting * destructed in random order because it got deleted before entities, * thereby bypassing the OnDeleteTarget policy. */ - ecs_defer_begin(world); + flecs_defer_begin(world, &world->stages[0]); const ecs_table_record_t *tr; while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { @@ -38866,7 +40935,7 @@ void flecs_fini_roots(ecs_world_t *world) { } } - ecs_defer_end(world); + flecs_defer_end(world, &world->stages[0]); } static @@ -38887,6 +40956,7 @@ bool flecs_world_iter_next( { if (ECS_BIT_IS_SET(it->flags, EcsIterIsValid)) { ECS_BIT_CLEAR(it->flags, EcsIterIsValid); + ecs_iter_fini(it); return false; } @@ -38937,6 +41007,7 @@ void flecs_world_allocators_init( flecs_ballocator_init_n(&a->graph_edge_lo, ecs_graph_edge_t, ECS_HI_COMPONENT_ID); flecs_ballocator_init_t(&a->graph_edge, ecs_graph_edge_t); flecs_ballocator_init_t(&a->id_record, ecs_id_record_t); + flecs_ballocator_init_n(&a->id_record_chunk, ecs_id_record_t, FLECS_SPARSE_CHUNK_SIZE); flecs_ballocator_init_t(&a->table_diff, ecs_table_diff_t); flecs_ballocator_init_n(&a->sparse_chunk, int32_t, FLECS_SPARSE_CHUNK_SIZE); flecs_ballocator_init_t(&a->hashmap, ecs_hashmap_t); @@ -38957,6 +41028,7 @@ void flecs_world_allocators_fini( flecs_ballocator_fini(&a->graph_edge_lo); flecs_ballocator_fini(&a->graph_edge); flecs_ballocator_fini(&a->id_record); + flecs_ballocator_fini(&a->id_record_chunk); flecs_ballocator_fini(&a->table_diff); flecs_ballocator_fini(&a->sparse_chunk); flecs_ballocator_fini(&a->hashmap); @@ -39026,6 +41098,9 @@ void flecs_log_addons(void) { #ifdef FLECS_LOG ecs_trace("FLECS_LOG"); #endif + #ifdef FLECS_JOURNAL + ecs_trace("FLECS_JOURNAL"); + #endif #ifdef FLECS_APP ecs_trace("FLECS_APP"); #endif @@ -39093,10 +41168,11 @@ ecs_world_t *ecs_mini(void) { flecs_world_allocators_init(world); world->self = world; - world->type_info = flecs_sparse_new( - &world->allocator, &world->allocators.sparse_chunk, - ecs_type_info_t); - ecs_map_init_w_params(&world->id_index, &world->allocators.ptr); + flecs_sparse_init(&world->type_info, &world->allocator, + &world->allocators.sparse_chunk, ecs_type_info_t); + ecs_map_init_w_params(&world->id_index_hi, &world->allocators.ptr); + flecs_sparse_init(&world->id_index_lo, NULL, + &world->allocators.id_record_chunk, ecs_id_record_t); flecs_observable_init(&world->observable); world->iterable.init = flecs_world_iter_init; world->pending_tables = flecs_sparse_new( @@ -39522,14 +41598,14 @@ static void flecs_fini_type_info( ecs_world_t *world) { - int32_t i, count = flecs_sparse_count(world->type_info); - ecs_sparse_t *type_info = world->type_info; + int32_t i, count = flecs_sparse_count(&world->type_info); + ecs_sparse_t *type_info = &world->type_info; for (i = 0; i < count; i ++) { ecs_type_info_t *ti = flecs_sparse_get_dense(type_info, ecs_type_info_t, i); flecs_type_info_fini(ti); } - flecs_sparse_free(world->type_info); + flecs_sparse_fini(&world->type_info); } ecs_entity_t flecs_get_oneof( @@ -39579,7 +41655,7 @@ int ecs_fini( /* Operations invoked during UnSet/OnRemove/destructors are deferred and * will be discarded after world cleanup */ - ecs_defer_begin(world); + flecs_defer_begin(world, &world->stages[0]); /* Run UnSet/OnRemove actions for components while the store is still * unmodified by cleanup. */ @@ -39748,7 +41824,7 @@ const ecs_type_info_t* flecs_type_info_get( ecs_assert(component != 0, ECS_INTERNAL_ERROR, NULL); ecs_assert(!(component & ECS_ID_FLAGS_MASK), ECS_INTERNAL_ERROR, NULL); - return flecs_sparse_get(world->type_info, ecs_type_info_t, component); + return flecs_sparse_get(&world->type_info, ecs_type_info_t, component); } ecs_type_info_t* flecs_type_info_ensure( @@ -39762,7 +41838,7 @@ ecs_type_info_t* flecs_type_info_ensure( ecs_type_info_t *ti_mut = NULL; if (!ti) { ti_mut = flecs_sparse_ensure( - world->type_info, ecs_type_info_t, component); + &world->type_info, ecs_type_info_t, component); ecs_assert(ti_mut != NULL, ECS_INTERNAL_ERROR, NULL); ti_mut->component = component; const char *sym = ecs_get_symbol(world, component); @@ -39792,7 +41868,7 @@ bool flecs_type_info_init_id( ecs_assert(size == 0 && alignment == 0, ECS_INVALID_COMPONENT_SIZE, NULL); ecs_assert(li == NULL, ECS_INCONSISTENT_COMPONENT_ACTION, NULL); - flecs_sparse_remove(world->type_info, component); + flecs_sparse_remove(&world->type_info, component); } else { ti = flecs_type_info_ensure(world, component); ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); @@ -39868,7 +41944,7 @@ void flecs_type_info_free( return; } ecs_type_info_t *ti = flecs_sparse_remove_get( - world->type_info, ecs_type_info_t, component); + &world->type_info, ecs_type_info_t, component); if (ti) { flecs_type_info_fini(ti); } @@ -40028,19 +42104,25 @@ void flecs_notify_queries( if (!idr) { return; } - - ecs_run_aperiodic(world, EcsAperiodicEmptyTables); ecs_table_cache_iter_t it; const ecs_table_record_t *tr; - if (flecs_table_cache_iter(&idr->cache, &it)) { + if (flecs_table_cache_all_iter(&idr->cache, &it)) { while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { ecs_table_t *table = tr->hdr.table; - EcsPoly *queries = ecs_table_get_column(table, tr->column); int32_t i, count = ecs_table_count(table); + if (!count) { + continue; + } + EcsPoly *queries = ecs_table_get_column(table, tr->column, 0); for (i = 0; i < count; i ++) { ecs_query_t *query = queries[i].poly; + if (!ecs_poly_is(query, ecs_query_t)) { + /* EcsQuery can also contain filters or rules */ + continue; + } + if (query->flags & EcsQueryIsSubquery) { continue; } @@ -40083,7 +42165,7 @@ void flecs_process_empty_queries( if (flecs_table_cache_iter(&idr->cache, &it)) { while ((tr = flecs_table_cache_next(&it, ecs_table_record_t))) { ecs_table_t *table = tr->hdr.table; - EcsPoly *queries = ecs_table_get_column(table, tr->column); + EcsPoly *queries = ecs_table_get_column(table, tr->column, 0); int32_t i, count = ecs_table_count(table); for (i = 0; i < count; i ++) { @@ -40136,6 +42218,8 @@ void flecs_process_pending_tables( return; } + flecs_journal_begin(world, EcsJournalTableEvents, 0, 0, 0); + do { ecs_sparse_t *pending_tables = world->pending_tables; world->pending_tables = world->pending_buffer; @@ -40144,7 +42228,7 @@ void flecs_process_pending_tables( /* Make sure that any ECS operations that occur while delivering the * events does not cause inconsistencies, like sending an Empty * notification for a table that just became non-empty. */ - ecs_defer_begin(world); + flecs_defer_begin(world, &world->stages[0]); for (i = 0; i < count; i ++) { ecs_table_t *table = flecs_sparse_get_dense( @@ -40163,25 +42247,35 @@ void flecs_process_pending_tables( * became empty again. By the time we run this code, no changes * in the administration would actually be made. */ int32_t table_count = ecs_table_count(table); + ecs_entity_t evt = table_count ? EcsOnTableFill : EcsOnTableEmpty; + if (ecs_should_log_3()) { + ecs_dbg_3("table %u state change (%s)", (uint32_t)table->id, + table_count ? "non-empty" : "empty"); + } + + ecs_log_push_3(); + flecs_emit(world, world, &(ecs_event_desc_t){ - .event = table_count - ? EcsOnTableFill - : EcsOnTableEmpty - , + .event = evt, .table = table, .ids = &table->type, .observable = world, - .table_event = true + .flags = EcsEventTableOnly }); + ecs_log_pop_3(); + world->info.empty_table_count += (table_count == 0) * 2 - 1; } } + flecs_sparse_clear(pending_tables); ecs_defer_end(world); world->pending_buffer = pending_tables; } while ((count = flecs_sparse_count(world->pending_tables))); + + flecs_journal_end(); } void flecs_table_set_empty( @@ -40196,8 +42290,7 @@ void flecs_table_set_empty( table->generation = 0; } - flecs_sparse_set_generation(world->pending_tables, (uint32_t)table->id); - flecs_sparse_ensure(world->pending_tables, ecs_table_t*, + flecs_sparse_ensure_fast(world->pending_tables, ecs_table_t*, (uint32_t)table->id)[0] = table; } @@ -40304,95 +42397,962 @@ done: return delete_count; } +/** + * @file observable.c + * @brief Observable implementation. + * + * The observable implementation contains functions that find the set of + * observers to invoke for an event. The code also contains the implementation + * of a reachable id cache, which is used to speedup event propagation when + * relationships are added/removed to/from entities. + */ + void flecs_observable_init( ecs_observable_t *observable) { observable->events = ecs_sparse_new(ecs_event_record_t); + observable->on_add.event = EcsOnAdd; + observable->on_remove.event = EcsOnRemove; + observable->on_set.event = EcsOnSet; + observable->un_set.event = EcsUnSet; } void flecs_observable_fini( ecs_observable_t *observable) { ecs_sparse_t *triggers = observable->events; - int32_t i, count = flecs_sparse_count(triggers); + ecs_assert(!ecs_map_is_initialized(&observable->on_add.event_ids), + ECS_INTERNAL_ERROR, NULL); + ecs_assert(!ecs_map_is_initialized(&observable->on_remove.event_ids), + ECS_INTERNAL_ERROR, NULL); + ecs_assert(!ecs_map_is_initialized(&observable->on_set.event_ids), + ECS_INTERNAL_ERROR, NULL); + ecs_assert(!ecs_map_is_initialized(&observable->un_set.event_ids), + ECS_INTERNAL_ERROR, NULL); + + int32_t i, count = flecs_sparse_count(triggers); for (i = 0; i < count; i ++) { - ecs_event_record_t *et = + ecs_event_record_t *er = ecs_sparse_get_dense(triggers, ecs_event_record_t, i); - ecs_assert(et != NULL, ECS_INTERNAL_ERROR, NULL); - (void)et; + ecs_assert(er != NULL, ECS_INTERNAL_ERROR, NULL); + (void)er; /* All triggers should've unregistered by now */ - ecs_assert(!ecs_map_is_initialized(&et->event_ids), + ecs_assert(!ecs_map_is_initialized(&er->event_ids), ECS_INTERNAL_ERROR, NULL); } flecs_sparse_free(observable->events); } -static -void notify_subset( - ecs_world_t *world, - ecs_iter_t *it, - ecs_observable_t *observable, - ecs_entity_t entity, - ecs_entity_t event, - const ecs_type_t *ids) +ecs_event_record_t* flecs_event_record_get( + const ecs_observable_t *o, + ecs_entity_t event) { - ecs_id_t pair = ecs_pair(EcsWildcard, entity); - ecs_id_record_t *idr = flecs_id_record_get(world, pair); - if (!idr) { - return; + ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL); + + /* Builtin events*/ + if (event == EcsOnAdd) return (ecs_event_record_t*)&o->on_add; + else if (event == EcsOnRemove) return (ecs_event_record_t*)&o->on_remove; + else if (event == EcsOnSet) return (ecs_event_record_t*)&o->on_set; + else if (event == EcsUnSet) return (ecs_event_record_t*)&o->un_set; + else if (event == EcsWildcard) return (ecs_event_record_t*)&o->on_wildcard; + + /* User events */ + return flecs_sparse_get(o->events, ecs_event_record_t, event); +} + +ecs_event_record_t* flecs_event_record_ensure( + ecs_observable_t *o, + ecs_entity_t event) +{ + ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_event_record_t *er = flecs_event_record_get(o, event); + if (er) { + return er; + } + er = flecs_sparse_ensure(o->events, ecs_event_record_t, event); + er->event = event; + return er; +} + +static +ecs_event_record_t* flecs_event_record_get_if( + const ecs_observable_t *o, + ecs_entity_t event) +{ + ecs_assert(o != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_event_record_t *er = flecs_event_record_get(o, event); + if (er) { + if (ecs_map_is_initialized(&er->event_ids)) { + return er; + } + if (er->any) { + return er; + } + if (er->wildcard) { + return er; + } + if (er->wildcard_pair) { + return er; + } } - /* Iterate acyclic relationships */ - ecs_id_record_t *cur = idr; + return NULL; +} + +ecs_event_id_record_t* flecs_event_id_record_get( + const ecs_event_record_t *er, + ecs_id_t id) +{ + if (!er) { + return NULL; + } + + if (id == EcsAny) return er->any; + else if (id == EcsWildcard) return er->wildcard; + else if (id == ecs_pair(EcsWildcard, EcsWildcard)) return er->wildcard_pair; + return ecs_map_get_ptr(&er->event_ids, ecs_event_id_record_t*, id); +} + +static +ecs_event_id_record_t* flecs_event_id_record_get_if( + const ecs_event_record_t *er, + ecs_id_t id) +{ + ecs_event_id_record_t *ider = flecs_event_id_record_get(er, id); + if (!ider) { + return NULL; + } + + if (ider->observer_count) { + return ider; + } + + return NULL; +} + +ecs_event_id_record_t* flecs_event_id_record_ensure( + ecs_world_t *world, + ecs_event_record_t *er, + ecs_id_t id) +{ + ecs_event_id_record_t *ider = flecs_event_id_record_get(er, id); + if (ider) { + return ider; + } + + ider = ecs_os_calloc_t(ecs_event_id_record_t); + + if (id == EcsAny) { + return er->any = ider; + } else if (id == EcsWildcard) { + return er->wildcard = ider; + } else if (id == ecs_pair(EcsWildcard, EcsWildcard)) { + return er->wildcard_pair = ider; + } + + ecs_map_init_w_params_if(&er->event_ids, &world->allocators.ptr); + ecs_event_id_record_t **idt = ecs_map_ensure( + &er->event_ids, ecs_event_id_record_t*, id); + if (!idt[0]) { + idt[0] = ider; + } + + return ider; +} + +void flecs_event_id_record_remove( + ecs_event_record_t *er, + ecs_id_t id) +{ + if (id == EcsAny) { + er->any = NULL; + } else if (id == EcsWildcard) { + er->wildcard = NULL; + } else if (id == ecs_pair(EcsWildcard, EcsWildcard)) { + er->wildcard_pair = NULL; + } else { + if (ecs_map_remove(&er->event_ids, id) == 0) { + ecs_map_fini(&er->event_ids); + } + } +} + +static +int32_t flecs_event_observers_get( + const ecs_event_record_t *er, + ecs_id_t id, + ecs_event_id_record_t **iders) +{ + if (!er) { + return 0; + } + + /* Populate array with observer sets matching the id */ + int32_t count = 0; + iders[0] = flecs_event_id_record_get_if(er, EcsAny); + count += iders[count] != 0; + + iders[count] = flecs_event_id_record_get_if(er, id); + count += iders[count] != 0; + + if (ECS_IS_PAIR(id)) { + ecs_id_t id_fwc = ecs_pair(EcsWildcard, ECS_PAIR_SECOND(id)); + ecs_id_t id_swc = ecs_pair(ECS_PAIR_FIRST(id), EcsWildcard); + ecs_id_t id_pwc = ecs_pair(EcsWildcard, EcsWildcard); + iders[count] = flecs_event_id_record_get_if(er, id_fwc); + count += iders[count] != 0; + iders[count] = flecs_event_id_record_get_if(er, id_swc); + count += iders[count] != 0; + iders[count] = flecs_event_id_record_get_if(er, id_pwc); + count += iders[count] != 0; + } else { + iders[count] = flecs_event_id_record_get_if(er, EcsWildcard); + count += iders[count] != 0; + } + + return count; +} + +bool flecs_check_observers_for_event( + const ecs_poly_t *object, + ecs_id_t id, + ecs_entity_t event) +{ + ecs_observable_t *observable = ecs_get_observable(object); + ecs_event_record_t *er = flecs_event_record_get_if(observable, event); + if (!er) { + return false; + } + + return flecs_event_id_record_get_if(er, id) != NULL; +} + +static +void flecs_emit_propagate( + ecs_world_t *world, + ecs_iter_t *it, + ecs_id_record_t *idr, + ecs_id_record_t *tgt_idr, + ecs_event_id_record_t **iders, + int32_t ider_count) +{ + ecs_assert(tgt_idr != NULL, ECS_INTERNAL_ERROR, NULL); + + if (ecs_should_log_3()) { + char *idstr = ecs_id_str(world, tgt_idr->id); + ecs_dbg_3("propagate events/invalidate cache for %s", idstr); + ecs_os_free(idstr); + } + ecs_log_push_3(); + + /* Propagate to records of acyclic relationships */ + ecs_id_record_t *cur = tgt_idr; while ((cur = cur->acyclic.next)) { - flecs_process_pending_tables(world); + cur->reachable.generation ++; /* Invalidate cache */ ecs_table_cache_iter_t idt; - if (!flecs_table_cache_iter(&cur->cache, &idt)) { - return; + if (!flecs_table_cache_all_iter(&cur->cache, &idt)) { + continue; } - ecs_entity_t rel = ECS_PAIR_FIRST(cur->id); + /* Get traversed relationship */ + ecs_entity_t trav = ECS_PAIR_FIRST(cur->id); + const ecs_table_record_t *tr; while ((tr = flecs_table_cache_next(&idt, ecs_table_record_t))) { ecs_table_t *table = tr->hdr.table; + if (!ecs_table_count(table)) { + continue; + } + + bool owned = flecs_id_record_get_table(idr, table); int32_t e, entity_count = ecs_table_count(table); it->table = table; it->other_table = NULL; it->offset = 0; it->count = entity_count; + if (entity_count) { + it->entities = ecs_vec_first(&table->data.entities); + } /* Treat as new event as this could invoke observers again for - * different tables. */ + * different tables. */ world->event_id ++; - flecs_set_observers_notify(it, observable, ids, event, - ecs_pair(rel, EcsWildcard)); + int32_t ider_i; + for (ider_i = 0; ider_i < ider_count; ider_i ++) { + ecs_event_id_record_t *ider = iders[ider_i]; + flecs_observers_invoke(world, &ider->up, it, table, trav); + + if (!owned) { + /* Owned takes precedence */ + flecs_observers_invoke( + world, &ider->self_up, it, table, trav); + } + } if (!table->observed_count) { continue; } - ecs_entity_t *entities = ecs_vec_first(&table->data.entities); + ecs_record_t **records = ecs_vec_first(&table->data.records); + for (e = 0; e < entity_count; e ++) { + ecs_id_record_t *idr_t = records[e]->idr; + if (idr_t) { + /* Only notify for entities that are used in pairs with + * acyclic relationships */ + flecs_emit_propagate(world, it, idr, idr_t, + iders, ider_count); + } + } + } + } + + ecs_log_pop_3(); +} + +static +void flecs_emit_propagate_invalidate_tables( + ecs_world_t *world, + ecs_id_record_t *tgt_idr) +{ + ecs_assert(tgt_idr != NULL, ECS_INTERNAL_ERROR, NULL); + + if (ecs_should_log_3()) { + char *idstr = ecs_id_str(world, tgt_idr->id); + ecs_dbg_3("invalidate reachable cache for %s", idstr); + ecs_os_free(idstr); + } + + /* Invalidate records of acyclic relationships */ + ecs_id_record_t *cur = tgt_idr; + while ((cur = cur->acyclic.next)) { + ecs_reachable_cache_t *rc = &cur->reachable; + if (rc->current != rc->generation) { + /* Subtree is already marked invalid */ + continue; + } + + rc->generation ++; + + ecs_table_cache_iter_t idt; + if (!flecs_table_cache_all_iter(&cur->cache, &idt)) { + continue; + } + + const ecs_table_record_t *tr; + while ((tr = flecs_table_cache_next(&idt, ecs_table_record_t))) { + ecs_table_t *table = tr->hdr.table; + if (!table->observed_count) { + continue; + } + + int32_t e, entity_count = ecs_table_count(table); ecs_record_t **records = ecs_vec_first(&table->data.records); for (e = 0; e < entity_count; e ++) { - uint32_t flags = ECS_RECORD_TO_ROW_FLAGS(records[e]->row); - if (flags & EcsEntityObservedAcyclic) { + ecs_id_record_t *idr_t = records[e]->idr; + if (idr_t) { /* Only notify for entities that are used in pairs with * acyclic relationships */ - notify_subset(world, it, observable, entities[e], event, ids); + flecs_emit_propagate_invalidate_tables(world, idr_t); } } } } } +void flecs_emit_propagate_invalidate( + ecs_world_t *world, + ecs_table_t *table, + int32_t offset, + int32_t count) +{ + ecs_record_t **recs = ecs_vec_get_t(&table->data.records, + ecs_record_t*, offset); + int32_t i; + for (i = 0; i < count; i ++) { + ecs_record_t *record = recs[i]; + if (!record) { + /* If the event is emitted after a bulk operation, it's possible + * that it hasn't been populated with entities yet. */ + continue; + } + + ecs_id_record_t *idr_t = record->idr; + if (idr_t) { + /* Event is used as target in acyclic relationship, propagate */ + flecs_emit_propagate_invalidate_tables(world, idr_t); + } + } +} + +static +void flecs_override_copy( + const ecs_type_info_t *ti, + void *dst, + const void *src, + int32_t count) +{ + ecs_copy_t copy = ti->hooks.copy; + ecs_size_t size = ti->size; + int32_t i; + if (copy) { + for (i = 0; i < count; i ++) { + copy(dst, src, count, ti); + dst = ECS_OFFSET(dst, size); + } + } else { + for (i = 0; i < count; i ++) { + ecs_os_memcpy(dst, src, size); + dst = ECS_OFFSET(dst, size); + } + } +} + +static +void* flecs_override( + ecs_iter_t *it, + const ecs_type_t *emit_ids, + ecs_id_t id, + ecs_table_t *table, + ecs_id_record_t *idr) +{ + if (it->event != EcsOnAdd || (it->flags & EcsEventNoOnSet)) { + return NULL; + } + + int32_t i = 0, count = emit_ids->count; + ecs_id_t *ids = emit_ids->array; + for (i = 0; i < count; i ++) { + if (ids[i] == id) { + /* If an id was both inherited and overridden in the same event + * (like what happens during an auto override), we need to copy the + * value of the inherited component to the new component. + * Also flag to the callee that this component was overridden, so + * that an OnSet event can be emmitted for it. + * Note that this is different from a component that was overridden + * after it was inherited, as this does not change the actual value + * of the component for the entity (it is copied from the existing + * overridden component), and does not require an OnSet event. */ + const ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); + if (!tr) { + continue; + } + + int32_t column = tr->column; + column = ecs_table_type_to_storage_index(table, column); + ecs_assert(column != -1, ECS_INTERNAL_ERROR, NULL); + + const ecs_type_info_t *ti = idr->type_info; + ecs_assert(ti != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_size_t size = ti->size; + ecs_vec_t *vec = &table->data.columns[column]; + return ecs_vec_get(vec, size, it->offset); + } + } + + return NULL; +} + +static +void flecs_emit_forward_up( + ecs_world_t *world, + ecs_event_record_t *er, + ecs_event_record_t *er_onset, + const ecs_type_t *emit_ids, + ecs_iter_t *it, + ecs_table_t *table, + ecs_id_record_t *idr, + ecs_vec_t *stack, + ecs_vec_t *reachable_ids); + +static +void flecs_emit_forward_id( + ecs_world_t *world, + ecs_event_record_t *er, + ecs_event_record_t *er_onset, + const ecs_type_t *emit_ids, + ecs_iter_t *it, + ecs_table_t *table, + ecs_id_record_t *idr, + ecs_entity_t tgt, + ecs_table_t *tgt_table, + int32_t column, + int32_t offset, + ecs_entity_t trav) +{ + ecs_id_t id = idr->id; + ecs_entity_t event = er ? er->event : 0; + bool inherit = trav == EcsIsA; + bool may_override = inherit && (event == EcsOnAdd) && (emit_ids->count > 1); + ecs_event_id_record_t *iders[5]; + ecs_event_id_record_t *iders_onset[5]; + + /* Skip id if there are no observers for it */ + int32_t ider_i, ider_count = flecs_event_observers_get(er, id, iders); + int32_t ider_onset_i, ider_onset_count = 0; + if (er_onset) { + ider_onset_count = flecs_event_observers_get( + er_onset, id, iders_onset); + } + + if (!may_override && (!ider_count && !ider_onset_count)) { + return; + } + + it->ids[0] = id; + it->sources[0] = tgt; + it->event_id = id; + it->ptrs[0] = NULL; + it->sizes[0] = 0; + + int32_t storage_i = ecs_table_type_to_storage_index(tgt_table, column); + if (storage_i != -1) { + ecs_assert(idr->type_info != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_vec_t *vec = &tgt_table->data.columns[storage_i]; + ecs_size_t size = idr->type_info->size; + it->ptrs[0] = ecs_vec_get(vec, size, offset); + it->sizes[0] = size; + } + + const ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); + bool owned = tr != NULL; + + for (ider_i = 0; ider_i < ider_count; ider_i ++) { + ecs_event_id_record_t *ider = iders[ider_i]; + flecs_observers_invoke(world, &ider->up, it, table, trav); + + /* Owned takes precedence */ + if (!owned) { + flecs_observers_invoke(world, &ider->self_up, it, table, trav); + } + } + + /* Emit OnSet events for newly inherited components */ + if (storage_i != -1) { + bool override = false; + + /* If component was added together with IsA relationship, still emit + * OnSet event, as it's a new value for the entity. */ + void *base_ptr = it->ptrs[0]; + void *ptr = flecs_override(it, emit_ids, id, table, idr); + if (ptr) { + override = true; + it->ptrs[0] = ptr; + } + + if (ider_onset_count) { + it->event = er_onset->event; + + for (ider_onset_i = 0; ider_onset_i < ider_onset_count; ider_onset_i ++) { + ecs_event_id_record_t *ider = iders_onset[ider_onset_i]; + flecs_observers_invoke(world, &ider->up, it, table, trav); + + /* Owned takes precedence */ + if (!owned) { + flecs_observers_invoke( + world, &ider->self_up, it, table, trav); + } else if (override) { + ecs_entity_t src = it->sources[0]; + it->sources[0] = 0; + flecs_observers_invoke(world, &ider->self, it, table, 0); + flecs_observers_invoke(world, &ider->self_up, it, table, 0); + it->sources[0] = src; + } + } + + it->event = event; + it->ptrs[0] = base_ptr; + } + } +} + +static +void flecs_emit_forward_and_cache_id( + ecs_world_t *world, + ecs_event_record_t *er, + ecs_event_record_t *er_onset, + const ecs_type_t *emit_ids, + ecs_iter_t *it, + ecs_table_t *table, + ecs_id_record_t *idr, + ecs_entity_t tgt, + ecs_record_t *tgt_record, + ecs_table_t *tgt_table, + const ecs_table_record_t *tgt_tr, + int32_t column, + int32_t offset, + ecs_vec_t *reachable_ids, + ecs_entity_t trav) +{ + /* Cache forwarded id for (rel, tgt) pair */ + ecs_reachable_elem_t *elem = ecs_vec_append_t(&world->allocator, + reachable_ids, ecs_reachable_elem_t); + elem->tr = tgt_tr; + elem->record = tgt_record; + elem->src = tgt; + elem->id = idr->id; +#ifndef NDEBUG + elem->table = tgt_table; +#endif + ecs_assert(tgt_table == tgt_record->table, ECS_INTERNAL_ERROR, NULL); + + flecs_emit_forward_id(world, er, er_onset, emit_ids, it, table, idr, + tgt, tgt_table, column, offset, trav); +} + +static +int32_t flecs_emit_stack_at( + ecs_vec_t *stack, + ecs_id_record_t *idr) +{ + int32_t sp = 0, stack_count = ecs_vec_count(stack); + ecs_table_t **stack_elems = ecs_vec_first(stack); + + for (sp = 0; sp < stack_count; sp ++) { + ecs_table_t *elem = stack_elems[sp]; + if (flecs_id_record_get_table(idr, elem)) { + break; + } + } + + return sp; +} + +static +bool flecs_emit_stack_has( + ecs_vec_t *stack, + ecs_id_record_t *idr) +{ + return flecs_emit_stack_at(stack, idr) != ecs_vec_count(stack); +} + +static +void flecs_emit_forward_cached_ids( + ecs_world_t *world, + ecs_event_record_t *er, + ecs_event_record_t *er_onset, + const ecs_type_t *emit_ids, + ecs_iter_t *it, + ecs_table_t *table, + ecs_reachable_cache_t *rc, + ecs_vec_t *reachable_ids, + ecs_vec_t *stack, + ecs_entity_t trav) +{ + ecs_reachable_elem_t *elems = ecs_vec_first_t(&rc->ids, + ecs_reachable_elem_t); + int32_t i, count = ecs_vec_count(&rc->ids); + for (i = 0; i < count; i ++) { + ecs_reachable_elem_t *rc_elem = &elems[i]; + const ecs_table_record_t *rc_tr = rc_elem->tr; + ecs_assert(rc_tr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_id_record_t *rc_idr = (ecs_id_record_t*)rc_tr->hdr.cache; + ecs_record_t *rc_record = rc_elem->record; + + ecs_assert(rc_idr->id == rc_elem->id, ECS_INTERNAL_ERROR, NULL); + ecs_assert(rc_record != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(flecs_entities_get(world, rc_elem->src) == + rc_record, ECS_INTERNAL_ERROR, NULL); + ecs_dbg_assert(rc_record->table == rc_elem->table, + ECS_INTERNAL_ERROR, NULL); + + if (flecs_emit_stack_has(stack, rc_idr)) { + continue; + } + + int32_t rc_offset = ECS_RECORD_TO_ROW(rc_record->row); + flecs_emit_forward_and_cache_id(world, er, er_onset, emit_ids, + it, table, rc_idr, rc_elem->src, + rc_record, rc_record->table, rc_tr, rc_tr->column, + rc_offset, reachable_ids, trav); + } +} + +static +void flecs_emit_dump_cache( + ecs_world_t *world, + const ecs_vec_t *vec) +{ + ecs_reachable_elem_t *elems = ecs_vec_first_t(vec, ecs_reachable_elem_t); + for (int i = 0; i < ecs_vec_count(vec); i ++) { + ecs_reachable_elem_t *elem = &elems[i]; + char *idstr = ecs_id_str(world, elem->id); + char *estr = ecs_id_str(world, elem->src); + ecs_dbg_3("- id: %s (%u), src: %s (%u), table: %p", + idstr, (uint32_t)elem->id, + estr, (uint32_t)elem->src, + elem->table); + ecs_os_free(idstr); + ecs_os_free(estr); + } + if (!ecs_vec_count(vec)) { + ecs_dbg_3("- no entries"); + } +} + +static +void flecs_emit_forward_table_up( + ecs_world_t *world, + ecs_event_record_t *er, + ecs_event_record_t *er_onset, + const ecs_type_t *emit_ids, + ecs_iter_t *it, + ecs_table_t *table, + ecs_entity_t tgt, + ecs_table_t *tgt_table, + ecs_record_t *tgt_record, + ecs_id_record_t *tgt_idr, + ecs_vec_t *stack, + ecs_vec_t *reachable_ids) +{ + ecs_allocator_t *a = &world->allocator; + int32_t i, id_count = tgt_table->type.count; + ecs_id_t *ids = tgt_table->type.array; + int32_t offset = ECS_RECORD_TO_ROW(tgt_record->row); + int32_t rc_child_offset = ecs_vec_count(reachable_ids); + int32_t stack_count = ecs_vec_count(stack); + + /* If tgt_idr is out of sync but is not the current id record being updated, + * keep track so that we can update two records for the cost of one. */ + ecs_reachable_cache_t *rc = &tgt_idr->reachable; + bool parent_revalidate = (reachable_ids != &rc->ids) && + (rc->current != rc->generation); + if (parent_revalidate) { + ecs_vec_reset_t(a, &rc->ids, ecs_reachable_elem_t); + } + + if (ecs_should_log_3()) { + char *idstr = ecs_id_str(world, tgt_idr->id); + ecs_dbg_3("forward events from %s", idstr); + ecs_os_free(idstr); + } + ecs_log_push_3(); + + /* Function may have to copy values from overridden components if an IsA + * relationship was added together with other components. */ + ecs_entity_t trav = ECS_PAIR_FIRST(tgt_idr->id); + bool inherit = trav == EcsIsA; + + for (i = 0; i < id_count; i ++) { + ecs_id_t id = ids[i]; + ecs_table_record_t *tgt_tr = &tgt_table->records[i]; + ecs_id_record_t *idr = (ecs_id_record_t*)tgt_tr->hdr.cache; + if (inherit && (idr->flags & EcsIdDontInherit)) { + continue; + } + + /* Id has the same relationship, traverse to find ids for forwarding */ + if (ECS_PAIR_FIRST(id) == trav) { + ecs_table_t **t = ecs_vec_append_t(&world->allocator, stack, + ecs_table_t*); + t[0] = tgt_table; + + ecs_reachable_cache_t *idr_rc = &idr->reachable; + if (idr_rc->current == idr_rc->generation) { + /* Cache hit, use cached ids to prevent traversing the same + * hierarchy multiple times. This especially speeds up code + * where (deep) hierarchies are created. */ + if (ecs_should_log_3()) { + char *idstr = ecs_id_str(world, id); + ecs_dbg_3("forward cached for %s", idstr); + ecs_os_free(idstr); + } + ecs_log_push_3(); + flecs_emit_forward_cached_ids(world, er, er_onset, emit_ids, it, + table, idr_rc, reachable_ids, stack, trav); + ecs_log_pop_3(); + } else { + /* Cache is dirty, traverse upwards */ + do { + flecs_emit_forward_up(world, er, er_onset, emit_ids, it, + table, idr, stack, reachable_ids); + if (++i >= id_count) { + break; + } + + id = ids[i]; + if (ECS_PAIR_FIRST(id) != trav) { + break; + } + } while (true); + } + + ecs_vec_remove_last(stack); + continue; + } + + int32_t stack_at = flecs_emit_stack_at(stack, idr); + if (parent_revalidate && (stack_at == (stack_count - 1))) { + /* If parent id record needs to be revalidated, add id */ + ecs_reachable_elem_t *elem = ecs_vec_append_t(a, &rc->ids, + ecs_reachable_elem_t); + elem->tr = tgt_tr; + elem->record = tgt_record; + elem->src = tgt; + elem->id = idr->id; +#ifndef NDEBUG + elem->table = tgt_table; +#endif + } + + /* Skip id if it's masked by a lower table in the tree */ + if (stack_at != stack_count) { + continue; + } + + flecs_emit_forward_and_cache_id(world, er, er_onset, emit_ids, it, + table, idr, tgt, tgt_record, tgt_table, tgt_tr, i, + offset, reachable_ids, trav); + } + + if (parent_revalidate) { + /* If this is not the current cache being updated, but it's marked + * as out of date, use intermediate results to populate cache. */ + int32_t rc_parent_offset = ecs_vec_count(&rc->ids); + + /* Only add ids that were added for this table */ + int32_t count = ecs_vec_count(reachable_ids); + count -= rc_child_offset; + + /* Append ids to any ids that already were added /*/ + if (count) { + ecs_vec_grow_t(a, &rc->ids, ecs_reachable_elem_t, count); + ecs_reachable_elem_t *dst = ecs_vec_get_t(&rc->ids, + ecs_reachable_elem_t, rc_parent_offset); + ecs_reachable_elem_t *src = ecs_vec_get_t(reachable_ids, + ecs_reachable_elem_t, rc_child_offset); + ecs_os_memcpy_n(dst, src, ecs_reachable_elem_t, count); + } + + if (it->event == EcsOnAdd || it->event == EcsOnRemove) { + /* Only OnAdd/OnRemove events can validate a cache */ + rc->current = rc->generation; + } + + if (ecs_should_log_3()) { + char *idstr = ecs_id_str(world, tgt_idr->id); + ecs_dbg_3("cache revalidated for %s:", idstr); + ecs_os_free(idstr); + flecs_emit_dump_cache(world, &rc->ids); + } + } + + ecs_log_pop_3(); +} + +static +void flecs_emit_forward_up( + ecs_world_t *world, + ecs_event_record_t *er, + ecs_event_record_t *er_onset, + const ecs_type_t *emit_ids, + ecs_iter_t *it, + ecs_table_t *table, + ecs_id_record_t *idr, + ecs_vec_t *stack, + ecs_vec_t *reachable_ids) +{ + ecs_id_t id = idr->id; + ecs_entity_t tgt = ECS_PAIR_SECOND(id); + tgt = flecs_entities_get_current(world, tgt); + ecs_assert(tgt != 0, ECS_INTERNAL_ERROR, NULL); + ecs_record_t *tgt_record = flecs_entities_get(world, tgt); + ecs_table_t *tgt_table; + if (!tgt_record || !(tgt_table = tgt_record->table)) { + return; + } + + flecs_emit_forward_table_up(world, er, er_onset, emit_ids, it, table, + tgt, tgt_table, tgt_record, idr, stack, reachable_ids); +} + +static +void flecs_emit_forward( + ecs_world_t *world, + ecs_event_record_t *er, + ecs_event_record_t *er_onset, + const ecs_type_t *emit_ids, + ecs_iter_t *it, + ecs_table_t *table, + ecs_id_record_t *idr) +{ + ecs_reachable_cache_t *rc = &idr->reachable; + + if (rc->current != rc->generation) { + /* Cache miss, iterate the tree to find ids to forward */ + if (ecs_should_log_3()) { + char *idstr = ecs_id_str(world, idr->id); + ecs_dbg_3("reachable cache miss for %s", idstr); + ecs_os_free(idstr); + } + ecs_log_push_3(); + + ecs_vec_t stack; + ecs_vec_init_t(&world->allocator, &stack, ecs_table_t*, 0); + ecs_vec_reset_t(&world->allocator, &rc->ids, ecs_reachable_elem_t); + flecs_emit_forward_up(world, er, er_onset, emit_ids, it, table, + idr, &stack, &rc->ids); + it->sources[0] = 0; + ecs_vec_fini_t(&world->allocator, &stack, ecs_table_t*); + + if (it->event == EcsOnAdd || it->event == EcsOnRemove) { + /* Only OnAdd/OnRemove events can validate a cache */ + rc->current = rc->generation; + } + + if (ecs_should_log_3()) { + ecs_dbg_3("cache after rebuild:"); + flecs_emit_dump_cache(world, &rc->ids); + } + + ecs_log_pop_3(); + } else { + /* Cache hit, use cached values instead of walking the tree */ + if (ecs_should_log_3()) { + char *idstr = ecs_id_str(world, idr->id); + ecs_dbg_3("reachable cache hit for %s", idstr); + ecs_os_free(idstr); + flecs_emit_dump_cache(world, &rc->ids); + } + + ecs_entity_t trav = ECS_PAIR_FIRST(idr->id); + ecs_reachable_elem_t *elems = ecs_vec_first_t(&rc->ids, + ecs_reachable_elem_t); + int32_t i, count = ecs_vec_count(&rc->ids); + for (i = 0; i < count; i ++) { + ecs_reachable_elem_t *elem = &elems[i]; + const ecs_table_record_t *tr = elem->tr; + ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_id_record_t *rc_idr = (ecs_id_record_t*)tr->hdr.cache; + ecs_record_t *r = elem->record; + + ecs_assert(rc_idr->id == elem->id, ECS_INTERNAL_ERROR, NULL); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(flecs_entities_get(world, elem->src) == r, + ECS_INTERNAL_ERROR, NULL); + ecs_dbg_assert(r->table == elem->table, ECS_INTERNAL_ERROR, NULL); + + int32_t offset = ECS_RECORD_TO_ROW(r->row); + flecs_emit_forward_id(world, er, er_onset, emit_ids, it, table, + rc_idr, elem->src, r->table, tr->column, offset, trav); + } + } +} + +/* The emit function is responsible for finding and invoking the observers + * matching the emitted event. The function is also capable of forwarding events + * for newly reachable ids (after adding a relationship) and propagating events + * downwards. Both capabilities are not just useful in application logic, but + * are also an important building block for keeping query caches in sync. */ void flecs_emit( ecs_world_t *world, ecs_world_t *stage, @@ -40407,30 +43367,41 @@ void flecs_emit( ecs_check(desc->table != NULL, ECS_INVALID_PARAMETER, NULL); ecs_check(desc->observable != NULL, ECS_INVALID_PARAMETER, NULL); - const ecs_type_t *ids = desc->ids; - ecs_entity_t event = desc->event; - ecs_table_t *table = desc->table; - int32_t row = desc->offset; - int32_t i, count = desc->count; - ecs_entity_t relationship = desc->relationship; ecs_time_t t = {0}; bool measure_time = world->flags & EcsWorldMeasureSystemTime; if (measure_time) { ecs_time_measure(&t); } - if (!count) { - count = ecs_table_count(table) - row; + const ecs_type_t *ids = desc->ids; + ecs_entity_t event = desc->event; + ecs_table_t *table = desc->table; + int32_t offset = desc->offset; + int32_t i, r, count = desc->count; + ecs_flags32_t table_flags = table->flags; + + /* Table events are emitted for internal table operations only, and do not + * provide component data and/or entity ids. */ + bool table_event = desc->flags & EcsEventTableOnly; + if (!count && !table_event) { + /* If no count is provided, forward event for all entities in table */ + count = ecs_table_count(table) - offset; } + /* When the NoOnSet flag is provided, no OnSet/UnSet events should be + * generated when new components are inherited. */ + bool no_on_set = desc->flags & EcsEventNoOnSet; + ecs_id_t ids_cache = 0; void *ptrs_cache = NULL; ecs_size_t sizes_cache = 0; int32_t columns_cache = 0; ecs_entity_t sources_cache = 0; + ecs_iter_t it = { .world = stage, .real_world = world, + .event = event, .table = table, .field_count = 1, .ids = &ids_cache, @@ -40439,48 +43410,311 @@ void flecs_emit( .columns = &columns_cache, .sources = &sources_cache, .other_table = desc->other_table, - .offset = row, + .offset = offset, .count = count, .param = (void*)desc->param, - .flags = desc->table_event ? EcsIterTableOnly : 0 + .flags = desc->flags | EcsIterIsValid }; + /* The world event id is used to determine if an observer has already been + * triggered for an event. Observers for multiple components are split up + * into multiple observers for a single component, and this counter is used + * to make sure a multi observer only triggers once, even if multiple of its + * single-component observers trigger. */ world->event_id ++; ecs_observable_t *observable = ecs_get_observable(desc->observable); ecs_check(observable != NULL, ECS_INVALID_PARAMETER, NULL); - if (!desc->relationship) { - flecs_observers_notify(&it, observable, ids, event); - } else { - flecs_set_observers_notify(&it, observable, ids, event, - ecs_pair(relationship, EcsWildcard)); + /* Event records contain all observers for a specific event. In addition to + * the emitted event, also request data for the Wildcard event (for + * observers subscribing to the wildcard event), OnSet and UnSet events. The + * latter to are used for automatically emitting OnSet/UnSet events for + * inherited components, for example when an IsA relationship is added to an + * entity. This doesn't add much overhead, as fetching records is cheap for + * builtin event types. */ + ecs_event_record_t *er = flecs_event_record_get_if(observable, event); + ecs_event_record_t *wcer = flecs_event_record_get_if(observable, EcsWildcard); + ecs_event_record_t *er_onset = flecs_event_record_get_if(observable, EcsOnSet); + ecs_event_record_t *er_unset = flecs_event_record_get_if(observable, EcsUnSet); + + ecs_data_t *storage = NULL; + ecs_vec_t *columns = NULL; + if (count) { + storage = &table->data; + columns = storage->columns; + it.entities = ecs_vec_get_t(&storage->entities, ecs_entity_t, offset); } - if (count && !desc->table_event) { - if (!table->observed_count) { - goto done; + int32_t id_count = ids->count; + ecs_id_t *id_array = ids->array; + + /* If a table has IsA relationships, OnAdd/OnRemove events can trigger + * (un)overriding a component. When a component is overridden its value is + * initialized with the value of the overridden component. */ + bool can_override = count && (table_flags & EcsTableHasIsA) && ( + (event == EcsOnAdd) || (event == EcsOnRemove)); + + /* When a new (acyclic) relationship is added (emitting an OnAdd/OnRemove + * event) this will cause the components of the target entity to be + * propagated to the source entity. This makes it possible for observers to + * get notified of any new reachable components though the relationship. */ + bool can_forward = event != EcsOnSet; + + /* Set if event has been propagated */ + bool propagated = false; + + /* Does table has observed entities */ + bool has_observed = table_flags & EcsTableHasObserved; + + /* When a relationship is removed, the events reachable through that + * relationship should emit UnSet events. This is part of the behavior that + * allows observers to be agnostic of whether a component is inherited. */ + bool can_unset = count && (event == EcsOnRemove) && !no_on_set; + + ecs_event_id_record_t *iders[5] = {0}; + int32_t unset_count = 0; + +repeat_event: + /* This is the core event logic, which is executed for each event. By + * default this is just the event kind from the ecs_event_desc_t struct, but + * can also include the Wildcard and UnSet events. The latter is emitted as + * counterpart to OnSet, for any removed ids associated with data. */ + for (i = 0; i < id_count; i ++) { + /* Emit event for each id passed to the function. In most cases this + * will just be one id, like a component that was added, removed or set. + * In some cases events are emitted for multiple ids. + * + * One example is when an id was added with a "With" property, or + * inheriting from a prefab with overrides. In these cases an entity is + * moved directly to the archetype with the additional components. */ + ecs_id_record_t *idr = NULL; + const ecs_type_info_t *ti = NULL; + ecs_id_t id = id_array[i]; + int32_t ider_i, ider_count = 0; + bool is_pair = ECS_IS_PAIR(id); + void *override_ptr = NULL; + ecs_entity_t base = 0; + + /* Check if this id is a pair of an acyclic relationship. If so, we may + * have to forward ids from the pair's target. */ + if ((can_forward && is_pair) || can_override) { + idr = flecs_query_id_record_get(world, id); + ecs_flags32_t idr_flags = idr->flags; + + if (is_pair && (idr_flags & EcsIdAcyclic)) { + ecs_event_record_t *er_fwd = NULL; + if (ECS_PAIR_FIRST(id) == EcsIsA) { + if (event == EcsOnAdd) { + if (!world->stages[0].base) { + /* Adding an IsA relationship can trigger prefab + * instantiation, which can instantiate prefab + * hierarchies for the entity to which the + * relationship was added. */ + ecs_entity_t tgt = ECS_PAIR_SECOND(id); + + /* Setting this value prevents flecs_instantiate + * from being called recursively, in case prefab + * children also have IsA relationships. */ + world->stages[0].base = tgt; + flecs_instantiate(world, tgt, table, offset, count); + world->stages[0].base = 0; + } + + /* Adding an IsA relationship will emit OnSet events for + * any new reachable components. */ + er_fwd = er_onset; + } else if (event == EcsOnRemove) { + /* Vice versa for removing an IsA relationship. */ + er_fwd = er_unset; + } + } + + /* Forward events for components from pair target */ + flecs_emit_forward(world, er, er_fwd, ids, &it, table, idr); + } + + if (can_override && (!(idr_flags & EcsIdDontInherit))) { + /* Initialize overridden components with value from base */ + ti = idr->type_info; + if (ti) { + ecs_table_record_t *base_tr = NULL; + int32_t base_column = ecs_search_relation(world, table, + 0, id, EcsIsA, EcsUp, &base, NULL, &base_tr); + if (base_column != -1) { + /* Base found with component */ + ecs_table_t *base_table = base_tr->hdr.table; + base_column = ecs_table_type_to_storage_index( + base_table, base_tr->column); + ecs_assert(base_column != -1, ECS_INTERNAL_ERROR, NULL); + ecs_record_t *base_r = flecs_entities_get(world, base); + ecs_assert(base_r != NULL, ECS_INTERNAL_ERROR, NULL); + int32_t base_row = ECS_RECORD_TO_ROW(base_r->row); + ecs_vec_t *base_v = &base_table->data.columns[base_column]; + override_ptr = ecs_vec_get(base_v, ti->size, base_row); + } + } + } } - ecs_record_t **recs = ecs_vec_get_t( - &table->data.records, ecs_record_t*, row); - for (i = 0; i < count; i ++) { - ecs_record_t *r = recs[i]; - if (!r) { + if (er) { + /* Get observer sets for id. There can be multiple sets of matching + * observers, in case an observer matches for wildcard ids. For + * example, both observers for (ChildOf, p) and (ChildOf, *) would + * match an event for (ChildOf, p). */ + ider_count = flecs_event_observers_get(er, id, iders); + idr = idr ? idr : flecs_query_id_record_get(world, id); + ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); + } + + if (can_unset) { + /* Increase UnSet count in case this is a component (has data). This + * will cause the event loop to be ran again as UnSet event. */ + idr = idr ? idr : flecs_query_id_record_get(world, id); + ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); + unset_count += (idr->type_info != NULL); + } + + if (!ider_count && !override_ptr) { + /* If nothing more to do for this id, early out */ + continue; + } + + ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); + const ecs_table_record_t *tr = flecs_id_record_get_table(idr, table); + if (tr == NULL) { + /* When a single batch contains multiple add's for an exclusive + * relationship, it's possible that an id was in the added list + * that is no longer available for the entity. */ + continue; + } + + int32_t column = tr->column, storage_i = -1; + it.columns[0] = column + 1; + it.ptrs[0] = NULL; + it.sizes[0] = 0; + it.event_id = id; + it.ids[0] = id; + + if (count) { + storage_i = ecs_table_type_to_storage_index(table, column); + if (storage_i != -1) { + /* If this is a component, fetch pointer & size */ + ecs_assert(idr->type_info != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_vec_t *vec = &columns[storage_i]; + ecs_size_t size = idr->type_info->size; + void *ptr = ecs_vec_get(vec, size, offset); + it.sizes[0] = size; + + if (override_ptr) { + if (event == EcsOnAdd) { + /* If this is a new override, initialize the component + * with the value of the overridden component. */ + flecs_override_copy(ti, ptr, override_ptr, count); + } else if (er_onset) { + /* If an override was removed, this re-exposes the + * overridden component. Because this causes the actual + * (now inherited) value of the component to change, an + * OnSet event must be emitted for the base component.*/ + ecs_assert(event == EcsOnRemove, ECS_INTERNAL_ERROR, NULL); + ecs_event_id_record_t *iders_set[5] = {0}; + int32_t ider_set_i, ider_set_count = + flecs_event_observers_get(er_onset, id, iders_set); + if (ider_set_count) { + /* Set the source temporarily to the base and base + * component pointer. */ + it.sources[0] = base; + it.ptrs[0] = ptr; + for (ider_set_i = 0; ider_set_i < ider_set_count; ider_set_i ++) { + ecs_event_id_record_t *ider = iders_set[ider_set_i]; + flecs_observers_invoke(world, &ider->self_up, &it, table, EcsIsA); + flecs_observers_invoke(world, &ider->up, &it, table, EcsIsA); + } + it.sources[0] = 0; + } + } + } + + it.ptrs[0] = ptr; + } else { + if (it.event == EcsUnSet) { + /* Only valid for components, not tags */ + continue; + } + } + } + + /* Actually invoke observers for this event/id */ + for (ider_i = 0; ider_i < ider_count; ider_i ++) { + ecs_event_id_record_t *ider = iders[ider_i]; + flecs_observers_invoke(world, &ider->self, &it, table, 0); + flecs_observers_invoke(world, &ider->self_up, &it, table, 0); + } + + if (!ider_count || !count || !has_observed) { + continue; + } + + /* If event is propagated, we don't have to manually invalidate entities + * lower in the tree(s). */ + propagated = true; + + /* The table->observed_count value indicates if the table contains any + * entities that are used as targets of acyclic relationships. If the + * entity/entities for which the event was generated is used as such a + * target, events must be propagated downwards. */ + ecs_entity_t *entities = it.entities; + it.entities = NULL; + + ecs_record_t **recs = ecs_vec_get_t(&storage->records, + ecs_record_t*, offset); + for (r = 0; r < count; r ++) { + ecs_record_t *record = recs[r]; + if (!record) { /* If the event is emitted after a bulk operation, it's possible * that it hasn't been populated with entities yet. */ continue; } - uint32_t flags = ECS_RECORD_TO_ROW_FLAGS(recs[i]->row); - if (flags & EcsEntityObservedAcyclic) { - notify_subset(world, &it, observable, ecs_vec_first_t( - &table->data.entities, ecs_entity_t)[row + i], event, ids); + ecs_id_record_t *idr_t = record->idr; + if (idr_t) { + /* Entity is used as target in acyclic pairs, propagate */ + ecs_entity_t e = entities[r]; + it.sources[0] = e; + + flecs_emit_propagate(world, &it, idr, idr_t, iders, ider_count); } } + + it.table = table; + it.entities = entities; + it.count = count; + it.sources[0] = 0; + } + + if (count && can_forward && has_observed && !propagated) { + flecs_emit_propagate_invalidate(world, table, offset, count); + } + + can_override = false; /* Don't override twice */ + can_unset = false; /* Don't unset twice */ + can_forward = false; /* Don't forward twice */ + + if (unset_count && er_unset && (er != er_unset)) { + /* Repeat event loop for UnSet event */ + unset_count = 0; + er = er_unset; + it.event = EcsUnSet; + goto repeat_event; + } + + if (wcer && er != wcer) { + /* Repeat event loop for Wildcard event */ + er = wcer; + it.event = event; + goto repeat_event; } -done: error: if (measure_time) { world->info.emit_time_total += (ecs_ftime_t)ecs_time_measure(&t); @@ -40493,9 +43727,44 @@ void ecs_emit( ecs_event_desc_t *desc) { ecs_world_t *world = (ecs_world_t*)ecs_get_world(stage); + if (desc->entity) { + ecs_assert(desc->table == NULL, ECS_INVALID_PARAMETER, NULL); + ecs_assert(desc->offset == 0, ECS_INVALID_PARAMETER, NULL); + ecs_assert(desc->count == 0, ECS_INVALID_PARAMETER, NULL); + ecs_record_t *r = flecs_entities_get(world, desc->entity); + ecs_table_t *table; + if (!r || !(table = r->table)) { + /* Empty entities can't trigger observers */ + return; + } + desc->table = table; + desc->offset = ECS_RECORD_TO_ROW(r->row); + desc->count = 1; + } + if (!desc->observable) { + desc->observable = world; + } flecs_emit(world, stage, desc); } +/** + * @file filter.c + * @brief Uncached query implementation. + * + * Uncached queries (filters) are stateless objects that do not cache their + * results. This file contains the creation and validation of uncached queries + * and code for query iteration. + * + * There file contains the implementation for term queries and filters. Term + * queries are uncached queries that only apply to a single term. Filters are + * uncached queries that support multiple terms. Filters are built on top of + * term queries: before iteration a filter will first find a "pivot" term (the + * term with the smallest number of elements), and create a term iterator for + * it. The output of that term iterator is then evaluated against the rest of + * the terms of the filter. + * + * Cached queries and observers are built using filters. + */ #include @@ -40533,9 +43802,9 @@ void flecs_filter_error( } else { expr = ecs_term_str(ctx->world, ctx->term); } - char *name = NULL; - if (ctx->filter) { - name = ctx->filter->name; + const char *name = NULL; + if (ctx->filter && ctx->filter->entity) { + name = ecs_get_name(ctx->filter->world, ctx->filter->entity); } ecs_parser_errorv(name, expr, term_start, fmt, args); ecs_os_free(expr); @@ -41050,6 +44319,7 @@ int flecs_term_finalize( ecs_term_id_t *second = &term->second; ecs_flags32_t first_flags = first->flags; ecs_flags32_t src_flags = src->flags; + ecs_flags32_t second_flags = second->flags; if (term->id) { if (flecs_term_populate_from_id(world, term, ctx)) { @@ -41084,8 +44354,8 @@ int flecs_term_finalize( first_id = term->first.id; } - /* If component id is final, don't attempt component inheritance */ if (first_id) { + /* If component id is final, don't attempt component inheritance */ if (ecs_has_id(world, first_id, EcsFinal)) { if (first_flags & EcsDown) { flecs_filter_error(ctx, "final id cannot be traversed down"); @@ -41094,6 +44364,7 @@ int flecs_term_finalize( first->flags &= ~EcsDown; first->trav = 0; } + /* Don't traverse ids that cannot be inherited */ if (ecs_has_id(world, first_id, EcsDontInherit)) { if (src_flags & (EcsUp | EcsDown)) { flecs_filter_error(ctx, @@ -41103,6 +44374,13 @@ int flecs_term_finalize( src->flags &= ~(EcsUp | EcsDown); src->trav = 0; } + /* Add traversal flags for transitive relationships */ + if (!(second_flags & EcsTraverseFlags)) { + if (ecs_has_id(world, first_id, EcsTransitive)) { + second->flags |= EcsSelf|EcsUp|EcsTraverseAll; + second->trav = first_id; + } + } } if (first->id == EcsVariable) { @@ -41128,6 +44406,8 @@ int flecs_term_finalize( return -1; } + term->idr = flecs_query_id_record_get(world, term->id); + return 0; } @@ -41375,7 +44655,6 @@ int ecs_filter_finalize( int32_t i, term_count = f->term_count, field_count = 0; ecs_term_t *terms = f->terms; bool is_or = false, prev_or = false; - ecs_flags32_t prev_src_flags = 0; ecs_entity_t prev_src_id = 0; int32_t filter_terms = 0; @@ -41396,17 +44675,12 @@ int ecs_filter_finalize( term->field_index = field_count - 1; if (prev_or && is_or) { - if (prev_src_flags != term->src.flags) { - flecs_filter_error(&ctx, "mismatching src.flags for OR terms"); - return -1; - } if (prev_src_id != term->src.id) { flecs_filter_error(&ctx, "mismatching src.id for OR terms"); return -1; } } - prev_src_flags = term->src.flags; prev_src_id = term->src.id; prev_or = is_or; @@ -41419,11 +44693,11 @@ int ecs_filter_finalize( if (term->id == EcsPrefab) { ECS_BIT_SET(f->flags, EcsFilterMatchPrefab); } - if (term->id == EcsDisabled) { + if (term->id == EcsDisabled && (term->src.flags & EcsSelf)) { ECS_BIT_SET(f->flags, EcsFilterMatchDisabled); } - if (ECS_BIT_IS_SET(f->flags, EcsFilterIsFilter)) { + if (ECS_BIT_IS_SET(f->flags, EcsFilterNoData)) { term->inout = EcsInOutNone; } @@ -41434,12 +44708,20 @@ int ecs_filter_finalize( if (term->oper != EcsNot || !ecs_term_match_this(term)) { ECS_BIT_CLEAR(f->flags, EcsFilterMatchAnything); } + + if (term->idr) { + if (ecs_os_has_threading()) { + ecs_os_ainc(&term->idr->keep_alive); + } else { + term->idr->keep_alive ++; + } + } } f->field_count = field_count; if (filter_terms == term_count) { - ECS_BIT_SET(f->flags, EcsFilterIsFilter); + ECS_BIT_SET(f->flags, EcsFilterNoData); } return 0; @@ -41463,14 +44745,56 @@ void flecs_filter_iter_init( } } +/* Implementation for dtor mixin */ +static +void flecs_filter_fini( + ecs_filter_t *filter) +{ + if (filter->terms) { + int i, count = filter->term_count; + for (i = 0; i < count; i ++) { + ecs_term_t *term = &filter->terms[i]; + if (term->idr) { + if (ecs_os_has_threading()) { + ecs_os_adec(&term->idr->keep_alive); + } else { + term->idr->keep_alive --; + } + } + ecs_term_fini(&filter->terms[i]); + } + + if (filter->terms_owned) { + ecs_os_free(filter->terms); + } + } + + filter->terms = NULL; + + if (filter->owned) { + ecs_os_free(filter); + } +} + +void ecs_filter_fini( + ecs_filter_t *filter) +{ + if (filter->owned && filter->entity) { + /* If filter is associated with entity, use poly dtor path */ + ecs_delete(filter->world, filter->entity); + } else { + flecs_filter_fini(filter); + } +} + ecs_filter_t* ecs_filter_init( - const ecs_world_t *stage, + ecs_world_t *world, const ecs_filter_desc_t *desc) { - ecs_check(stage != NULL, ECS_INVALID_PARAMETER, NULL); + 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); - const ecs_world_t *world = ecs_get_world(stage); + flecs_stage_from_world(&world); ecs_filter_t *f = desc->storage; int32_t i, term_count = desc->terms_buffer_count, storage_count = 0, expr_count = 0; @@ -41511,12 +44835,19 @@ ecs_filter_t* ecs_filter_init( } /* If expr is set, parse query expression */ - const char *name = desc->name, *expr = desc->expr; + const char *expr = desc->expr; + ecs_entity_t entity = desc->entity; if (expr) { #ifdef FLECS_PARSER + const char *name = NULL; const char *ptr = desc->expr; ecs_term_t term = {0}; int32_t expr_size = 0; + + if (entity) { + name = ecs_get_name(world, entity); + } + while (ptr[0] && (ptr = ecs_parse_term(world, name, expr, ptr, &term))){ if (!ecs_term_is_initialized(&term)) { break; @@ -41587,10 +44918,19 @@ ecs_filter_t* ecs_filter_init( f->terms[i].move = false; } - f->name = ecs_os_strdup(name); f->variable_names[0] = (char*)"."; f->iterable.init = flecs_filter_iter_init; + f->dtor = (ecs_poly_dtor_t)flecs_filter_fini; + f->world = world; + f->entity = entity; + + if (entity && f->owned) { + EcsPoly *poly = ecs_poly_bind(world, entity, ecs_filter_t); + poly->poly = f; + ecs_poly_modified(world, entity, ecs_filter_t); + } + return f; error: ecs_filter_fini(f); @@ -41643,30 +44983,6 @@ void ecs_filter_move( } } -void ecs_filter_fini( - ecs_filter_t *filter) -{ - if (filter->terms) { - int i, count = filter->term_count; - for (i = 0; i < count; i ++) { - ecs_term_fini(&filter->terms[i]); - } - - if (filter->terms_owned) { - ecs_os_free(filter->terms); - } - } - - ecs_os_free(filter->name); - - filter->terms = NULL; - filter->name = NULL; - - if (filter->owned) { - ecs_os_free(filter); - } -} - static void filter_str_add_id( const ecs_world_t *world, @@ -41763,7 +45079,13 @@ void term_str_w_strbuf( if (!subj_set) { filter_str_add_id(world, buf, &term->first, false, def_first_mask); - ecs_strbuf_appendlit(buf, "()"); + if (!obj_set) { + ecs_strbuf_appendlit(buf, "()"); + } else { + ecs_strbuf_appendlit(buf, "(0,"); + filter_str_add_id(world, buf, &term->second, false, def_second_mask); + ecs_strbuf_appendlit(buf, ")"); + } } else if (ecs_term_match_this(term) && (src->flags & EcsTraverseFlags) == def_src_mask) { @@ -42052,8 +45374,8 @@ bool flecs_term_match_table( ecs_table_record_t *tr = 0; bool is_any = is_any_pair(id); - column = ecs_search_relation(world, match_table, - column, id, src->trav, src->flags, &source, id_out, &tr); + column = flecs_search_relation_w_idr(world, match_table, + column, id, src->trav, src->flags, &source, id_out, &tr, term->idr); if (tr && match_index_out) { if (!is_any) { @@ -42267,7 +45589,10 @@ void term_iter_init( iter->term = *term; if (src->flags & EcsSelf) { - iter->self_index = flecs_query_id_record_get(world, term->id); + iter->self_index = term->idr; + if (!iter->self_index) { + iter->self_index = flecs_query_id_record_get(world, term->id); + } } if (src->flags & EcsUp) { @@ -42393,8 +45718,8 @@ bool flecs_term_iter_find_superset( ecs_term_id_t *src = &term->src; /* Test if following the relationship finds the id */ - int32_t index = ecs_search_relation(world, table, 0, - term->id, src->trav, src->flags, source, id, 0); + int32_t index = flecs_search_relation_w_idr(world, table, 0, + term->id, src->trav, src->flags, source, id, 0, term->idr); if (index == -1) { *source = 0; @@ -42585,7 +45910,6 @@ bool ecs_term_next( do { if (!next(chain_it)) { - ecs_iter_fini(it); goto done; } @@ -42615,6 +45939,7 @@ yield: ECS_BIT_SET(it->flags, EcsIterIsValid); return true; done: + ecs_iter_fini(it); error: return false; } @@ -42687,7 +46012,9 @@ ecs_iter_t flecs_filter_iter_w_flags( ecs_check(filter != NULL, ECS_INVALID_PARAMETER, NULL); const ecs_world_t *world = ecs_get_world(stage); - flecs_process_pending_tables(world); + if (!(flags & EcsIterMatchVar)) { + flecs_process_pending_tables(world); + } ecs_iter_t it = { .real_world = (ecs_world_t*)world, @@ -42738,7 +46065,7 @@ ecs_iter_t flecs_filter_iter_w_flags( } ECS_BIT_COND(it.flags, EcsIterIsFilter, - ECS_BIT_IS_SET(filter->flags, EcsFilterIsFilter)); + ECS_BIT_IS_SET(filter->flags, EcsFilterNoData)); if (ECS_BIT_IS_SET(filter->flags, EcsFilterMatchThis)) { /* Make space for one variable if the filter has terms for This var */ @@ -43021,10 +46348,20 @@ yield: return true; } +/** + * @file search.c + * @brief Search functions to find (component) ids in table types. + * + * Search functions are used to find the column index of a (component) id in a + * table. Additionally, search functions implement the logic for finding a + * component id by following a relationship upwards. + */ + static -int32_t type_search( +int32_t flecs_type_search( const ecs_table_t *table, + ecs_id_t search_id, ecs_id_record_t *idr, ecs_id_t *ids, ecs_id_t *id_out, @@ -43035,7 +46372,11 @@ int32_t type_search( int32_t r = tr->column; if (tr_out) tr_out[0] = tr; if (id_out) { - id_out[0] = flecs_to_public_id(ids[r]); + if (ECS_PAIR_FIRST(search_id) == EcsUnion) { + id_out[0] = ids[r]; + } else { + id_out[0] = flecs_to_public_id(ids[r]); + } } return r; } @@ -43044,7 +46385,7 @@ int32_t type_search( } static -int32_t type_offset_search( +int32_t flecs_type_offset_search( int32_t offset, ecs_id_t id, ecs_id_t *ids, @@ -43070,7 +46411,7 @@ int32_t type_offset_search( } static -bool type_can_inherit_id( +bool flecs_type_can_inherit_id( const ecs_world_t *world, const ecs_table_t *table, const ecs_id_record_t *idr, @@ -43093,7 +46434,7 @@ bool type_can_inherit_id( } static -int32_t type_search_relation( +int32_t flecs_type_search_relation( const ecs_world_t *world, const ecs_table_t *table, int32_t offset, @@ -43112,12 +46453,12 @@ int32_t type_search_relation( if (self) { if (offset) { - int32_t r = type_offset_search(offset, id, ids, count, id_out); + int32_t r = flecs_type_offset_search(offset, id, ids, count, id_out); if (r != -1) { return r; } } else { - int32_t r = type_search(table, idr, ids, id_out, tr_out); + int32_t r = flecs_type_search(table, id, idr, ids, id_out, tr_out); if (r != -1) { return r; } @@ -43131,12 +46472,13 @@ int32_t type_search_relation( if (!(flags & EcsTableHasIsA)) { return -1; } - if (!type_can_inherit_id(world, table, idr, id)) { - return -1; - } idr_r = world->idr_isa_wildcard; } + if (!flecs_type_can_inherit_id(world, table, idr, id)) { + return -1; + } + if (!idr_r) { idr_r = flecs_id_record_get(world, rel); if (!idr_r) { @@ -43147,9 +46489,9 @@ int32_t type_search_relation( ecs_id_t id_r; int32_t r, r_column; if (offset) { - r_column = type_offset_search(offset, rel, ids, count, &id_r); + r_column = flecs_type_offset_search(offset, rel, ids, count, &id_r); } else { - r_column = type_search(table, idr_r, ids, &id_r, 0); + r_column = flecs_type_search(table, id, idr_r, ids, &id_r, 0); } while (r_column != -1) { ecs_entity_t obj = ECS_PAIR_SECOND(id_r); @@ -43162,7 +46504,7 @@ int32_t type_search_relation( if (obj_table) { ecs_assert(obj_table != table, ECS_CYCLE_DETECTED, NULL); - r = type_search_relation(world, obj_table, 0, id, idr, + r = flecs_type_search_relation(world, obj_table, 0, id, idr, rel, idr_r, true, subject_out, id_out, tr_out); if (r != -1) { if (subject_out && !subject_out[0]) { @@ -43172,7 +46514,7 @@ int32_t type_search_relation( } if (!is_a) { - r = type_search_relation(world, obj_table, 0, id, idr, + r = flecs_type_search_relation(world, obj_table, 0, id, idr, ecs_pair(EcsIsA, EcsWildcard), world->idr_isa_wildcard, true, subject_out, id_out, tr_out); if (r != -1) { @@ -43184,13 +46526,57 @@ int32_t type_search_relation( } } - r_column = type_offset_search(r_column + 1, rel, ids, count, &id_r); + r_column = flecs_type_offset_search( + r_column + 1, rel, ids, count, &id_r); } } return -1; } +int32_t flecs_search_relation_w_idr( + const ecs_world_t *world, + const ecs_table_t *table, + int32_t offset, + ecs_id_t id, + ecs_entity_t rel, + ecs_flags32_t flags, + ecs_entity_t *subject_out, + ecs_id_t *id_out, + struct ecs_table_record_t **tr_out, + ecs_id_record_t *idr) +{ + if (!table) return -1; + + ecs_poly_assert(world, ecs_world_t); + ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL); + + flags = flags ? flags : (EcsSelf|EcsUp); + + if (!idr && !offset) { + idr = flecs_query_id_record_get(world, id); + if (!idr) { + return -1; + } + } + + if (subject_out) subject_out[0] = 0; + if (!(flags & EcsUp)) { + if (offset) { + return ecs_search_offset(world, table, offset, id, id_out); + } else { + return flecs_type_search( + table, id, idr, table->type.array, id_out, tr_out); + } + } + + int32_t result = flecs_type_search_relation(world, table, offset, id, idr, + ecs_pair(rel, EcsWildcard), NULL, flags & EcsSelf, subject_out, + id_out, tr_out); + + return result; +} + int32_t ecs_search_relation( const ecs_world_t *world, const ecs_table_t *table, @@ -43219,7 +46605,7 @@ int32_t ecs_search_relation( return -1; } - int32_t result = type_search_relation(world, table, offset, id, idr, + int32_t result = flecs_type_search_relation(world, table, offset, id, idr, ecs_pair(rel, EcsWildcard), NULL, flags & EcsSelf, subject_out, id_out, tr_out); @@ -43244,7 +46630,7 @@ int32_t ecs_search( ecs_type_t type = table->type; ecs_id_t *ids = type.array; - return type_search(table, idr, ids, id_out, NULL); + return flecs_type_search(table, id, idr, ids, id_out, 0); } int32_t ecs_search_offset( @@ -43265,15 +46651,15 @@ int32_t ecs_search_offset( ecs_type_t type = table->type; ecs_id_t *ids = type.array; int32_t count = type.count; - return type_offset_search(offset, id, ids, count, id_out); + return flecs_type_offset_search(offset, id, ids, count, id_out); } static int32_t flecs_relation_depth_walk( const ecs_world_t *world, - ecs_id_record_t *idr, - ecs_table_t *first, - ecs_table_t *table) + const ecs_id_record_t *idr, + const ecs_table_t *first, + const ecs_table_t *table) { int32_t result = 0; @@ -43305,7 +46691,7 @@ int32_t flecs_relation_depth_walk( int32_t flecs_relation_depth( const ecs_world_t *world, ecs_entity_t r, - ecs_table_t *table) + const ecs_table_t *table) { ecs_id_record_t *idr = flecs_id_record_get(world, ecs_pair(r, EcsWildcard)); if (!idr) { @@ -43314,22 +46700,394 @@ int32_t flecs_relation_depth( return flecs_relation_depth_walk(world, idr, table, table); } +/** + * @file observer.h + * @brief Observer implementation. + * + * The observer implementation contains functions for creating, deleting and + * invoking observers. The code is split up into single-term observers and + * multi-term observers. Multi-term observers are created from multiple single- + * term observers. + */ + #include static -void flecs_observer_invoke(ecs_iter_t *it) { - ecs_assert(it->callback != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_table_lock(it->world, it->table); - if (ecs_should_log_3()) { - char *path = ecs_get_fullpath(it->world, it->system); - ecs_dbg_3("observer %s", path); - ecs_os_free(path); - ecs_log_push_3(); +ecs_entity_t flecs_get_observer_event( + ecs_term_t *term, + ecs_entity_t event) +{ + /* If operator is Not, reverse the event */ + if (term->oper == EcsNot) { + if (event == EcsOnAdd) { + event = EcsOnRemove; + } else if (event == EcsOnRemove) { + event = EcsOnAdd; + } } - it->real_world->info.observers_ran_frame ++; - it->callback(it); - ecs_table_unlock(it->world, it->table); + + return event; +} + +static +ecs_flags32_t flecs_id_flag_for_event( + ecs_entity_t e) +{ + if (e == EcsOnAdd) { + return EcsIdHasOnAdd; + } + if (e == EcsOnRemove) { + return EcsIdHasOnRemove; + } + if (e == EcsOnSet) { + return EcsIdHasOnSet; + } + if (e == EcsUnSet) { + return EcsIdHasUnSet; + } + return 0; +} + +static +void flecs_inc_observer_count( + ecs_world_t *world, + ecs_entity_t event, + ecs_event_record_t *evt, + ecs_id_t id, + int32_t value) +{ + ecs_event_id_record_t *idt = flecs_event_id_record_ensure(world, evt, id); + ecs_assert(idt != NULL, ECS_INTERNAL_ERROR, NULL); + + int32_t result = idt->observer_count += value; + if (result == 1) { + /* Notify framework that there are observers for the event/id. This + * allows parts of the code to skip event evaluation early */ + flecs_notify_tables(world, id, &(ecs_table_event_t){ + .kind = EcsTableTriggersForId, + .event = event + }); + + ecs_flags32_t flags = flecs_id_flag_for_event(event); + if (flags) { + ecs_id_record_t *idr = flecs_id_record_get(world, id); + if (idr) { + idr->flags |= flags; + } + } + } else if (result == 0) { + /* Ditto, but the reverse */ + flecs_notify_tables(world, id, &(ecs_table_event_t){ + .kind = EcsTableNoTriggersForId, + .event = event + }); + + ecs_flags32_t flags = flecs_id_flag_for_event(event); + if (flags) { + ecs_id_record_t *idr = flecs_id_record_get(world, id); + if (idr) { + idr->flags &= ~flags; + } + } + + flecs_event_id_record_remove(evt, id); + ecs_os_free(idt); + } +} + +static +void flecs_register_observer_for_id( + ecs_world_t *world, + ecs_observable_t *observable, + ecs_observer_t *observer, + size_t offset) +{ + ecs_id_t term_id = observer->register_id; + ecs_term_t *term = &observer->filter.terms[0]; + ecs_entity_t trav = term->src.trav; + + int i; + for (i = 0; i < observer->event_count; i ++) { + ecs_entity_t event = flecs_get_observer_event( + term, observer->events[i]); + + /* Get observers for event */ + ecs_event_record_t *er = flecs_event_record_ensure(observable, event); + ecs_assert(er != NULL, ECS_INTERNAL_ERROR, NULL); + + /* Get observers for (component) id for event */ + ecs_event_id_record_t *idt = flecs_event_id_record_ensure( + world, er, term_id); + ecs_assert(idt != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_map_t *observers = ECS_OFFSET(idt, offset); + ecs_map_init_w_params_if(observers, &world->allocators.ptr); + + ecs_map_ensure(observers, ecs_observer_t*, + observer->filter.entity)[0] = observer; + + flecs_inc_observer_count(world, event, er, term_id, 1); + if (trav) { + flecs_inc_observer_count(world, event, er, + ecs_pair(trav, EcsWildcard), 1); + } + } +} + +static +void flecs_uni_observer_register( + ecs_world_t *world, + ecs_observable_t *observable, + ecs_observer_t *observer) +{ + ecs_term_t *term = &observer->filter.terms[0]; + ecs_flags32_t flags = term->src.flags; + + if ((flags & (EcsSelf|EcsUp)) == (EcsSelf|EcsUp)) { + flecs_register_observer_for_id(world, observable, observer, + offsetof(ecs_event_id_record_t, self_up)); + } else if (flags & EcsSelf) { + flecs_register_observer_for_id(world, observable, observer, + offsetof(ecs_event_id_record_t, self)); + } else if (flags & EcsUp) { + ecs_assert(term->src.trav != 0, ECS_INTERNAL_ERROR, NULL); + flecs_register_observer_for_id(world, observable, observer, + offsetof(ecs_event_id_record_t, up)); + } +} + +static +void flecs_unregister_observer_for_id( + ecs_world_t *world, + ecs_observable_t *observable, + ecs_observer_t *observer, + size_t offset) +{ + ecs_id_t term_id = observer->register_id; + ecs_term_t *term = &observer->filter.terms[0]; + ecs_entity_t trav = term->src.trav; + + int i; + for (i = 0; i < observer->event_count; i ++) { + ecs_entity_t event = flecs_get_observer_event( + term, observer->events[i]); + + /* Get observers for event */ + ecs_event_record_t *er = flecs_event_record_get(observable, event); + ecs_assert(er != NULL, ECS_INTERNAL_ERROR, NULL); + + /* Get observers for (component) id */ + ecs_event_id_record_t *idt = flecs_event_id_record_get(er, term_id); + ecs_assert(idt != NULL, ECS_INTERNAL_ERROR, NULL); + + ecs_map_t *id_observers = ECS_OFFSET(idt, offset); + + if (ecs_map_remove(id_observers, observer->filter.entity) == 0) { + ecs_map_fini(id_observers); + } + + flecs_inc_observer_count(world, event, er, term_id, -1); + if (trav) { + flecs_inc_observer_count(world, event, er, + ecs_pair(trav, EcsWildcard), -1); + } + } +} + +static +void flecs_unregister_observer( + ecs_world_t *world, + ecs_observable_t *observable, + ecs_observer_t *observer) +{ + ecs_assert(observer != NULL, ECS_INTERNAL_ERROR, NULL); + if (!observer->filter.terms) { + ecs_assert(observer->filter.term_count == 0, ECS_INTERNAL_ERROR, NULL); + return; + } + + ecs_term_t *term = &observer->filter.terms[0]; + ecs_flags32_t flags = term->src.flags; + + if ((flags & (EcsSelf|EcsUp)) == (EcsSelf|EcsUp)) { + flecs_unregister_observer_for_id(world, observable, observer, + offsetof(ecs_event_id_record_t, self_up)); + } else if (flags & EcsSelf) { + flecs_unregister_observer_for_id(world, observable, observer, + offsetof(ecs_event_id_record_t, self)); + } else if (flags & EcsUp) { + flecs_unregister_observer_for_id(world, observable, observer, + offsetof(ecs_event_id_record_t, up)); + } +} + + +static +bool flecs_ignore_observer( + ecs_world_t *world, + ecs_observer_t *observer, + ecs_table_t *table) +{ + int32_t *last_event_id = observer->last_event_id; + if (last_event_id && last_event_id[0] == world->event_id) { + return true; + } + + if (!table) { + return false; + } + + ecs_filter_t *filter = &observer->filter; + if (!(filter->flags & EcsFilterMatchPrefab) && + (table->flags & EcsTableIsPrefab)) + { + return true; + } + if (!(filter->flags & EcsFilterMatchDisabled) && + (table->flags & EcsTableIsDisabled)) + { + return true; + } + + return false; +} + +static +void flecs_observer_invoke( + ecs_world_t *world, + ecs_iter_t *it, + ecs_observer_t *observer, + ecs_iter_action_t callback, + ecs_table_t *table, + int32_t term_index) +{ + ecs_assert(it->callback != NULL, ECS_INVALID_PARAMETER, NULL); + + ecs_table_lock(it->world, table); + if (ecs_should_log_3()) { + char *path = ecs_get_fullpath(world, it->system); + ecs_dbg_3("observer: invoke %s", path); + ecs_os_free(path); + } + + ecs_log_push_3(); + + world->info.observers_ran_frame ++; + + ecs_filter_t *filter = &observer->filter; + ecs_assert(term_index < filter->term_count, ECS_INTERNAL_ERROR, NULL); + ecs_term_t *term = &filter->terms[term_index]; + ecs_entity_t observer_src = term->src.id; + if (observer_src && !(term->src.flags & EcsIsEntity)) { + observer_src = 0; + } + + if (term->oper != EcsNot) { + ecs_assert((it->offset + it->count) <= ecs_table_count(table), + ECS_INTERNAL_ERROR, NULL); + } + + int32_t i, count = it->count; + ecs_size_t size = it->sizes[0]; + ecs_entity_t src = it->sources[0]; + bool instanced = filter->flags & EcsFilterIsInstanced; + + if (!observer_src && ((count == 1) || (size == 0) || (src == 0) || instanced)) { + ECS_BIT_COND(it->flags, EcsIterIsInstanced, instanced); + callback(it); + } else { + ECS_BIT_CLEAR(it->flags, EcsIterIsInstanced); + ecs_entity_t *entities = it->entities; + it->count = 1; + for (i = 0; i < count; i ++) { + ecs_entity_t e = entities[i]; + it->entities = &e; + if (!observer_src) { + callback(it); + } else if (observer_src == e) { + ecs_entity_t dummy = 0; + it->entities = &dummy; + if (!src) { + it->sources[0] = e; + } + callback(it); + it->sources[0] = src; + break; + } + } + it->entities = entities; + it->count = count; + } + ecs_log_pop_3(); + ecs_table_unlock(it->world, table); +} + +static +void flecs_uni_observer_invoke( + ecs_world_t *world, + ecs_observer_t *observer, + ecs_iter_t *it, + ecs_table_t *table, + ecs_entity_t trav) +{ + ecs_filter_t *filter = &observer->filter; + ecs_term_t *term = &filter->terms[0]; + if (flecs_ignore_observer(world, observer, table)) { + return; + } + + ecs_assert(trav == 0 || it->sources[0] != 0, ECS_INTERNAL_ERROR, NULL); + + if (trav && term->src.trav != trav) { + return; + } + + bool is_filter = term->inout == EcsInOutNone; + ECS_BIT_COND(it->flags, EcsIterIsFilter, is_filter); + it->system = observer->filter.entity; + it->ctx = observer->ctx; + it->binding_ctx = observer->binding_ctx; + it->term_index = observer->term_index; + it->terms = term; + + ecs_entity_t event = it->event; + it->event = flecs_get_observer_event(term, event); + void *ptrs = it->ptrs; + if (is_filter) { + it->ptrs = NULL; + } + + if (observer->run) { + it->next = flecs_default_observer_next_callback; + it->callback = flecs_default_uni_observer_run_callback; + it->ctx = observer; + observer->run(it); + } else { + ecs_iter_action_t callback = observer->callback; + it->callback = callback; + flecs_observer_invoke(world, it, observer, callback, table, 0); + } + + it->event = event; + it->ptrs = ptrs; +} + +void flecs_observers_invoke( + ecs_world_t *world, + ecs_map_t *observers, + ecs_iter_t *it, + ecs_table_t *table, + ecs_entity_t trav) +{ + if (ecs_map_is_initialized(observers)) { + ecs_map_iter_t oit = ecs_map_iter(observers); + ecs_observer_t *o; + while ((o = ecs_map_next_ptr(&oit, ecs_observer_t*, NULL))) { + ecs_assert(it->table == table, ECS_INTERNAL_ERROR, NULL); + flecs_uni_observer_invoke(world, o, it, table, trav); + } + } } static @@ -43349,7 +47107,7 @@ bool flecs_multi_observer_invoke(ecs_iter_t *it) { user_it.terms = o->filter.terms; user_it.flags = 0; ECS_BIT_COND(user_it.flags, EcsIterIsFilter, - ECS_BIT_IS_SET(o->filter.flags, EcsFilterIsFilter)); + ECS_BIT_IS_SET(o->filter.flags, EcsFilterNoData)); user_it.ids = NULL; user_it.columns = NULL; user_it.sources = NULL; @@ -43410,7 +47168,7 @@ bool flecs_multi_observer_invoke(ecs_iter_t *it) { it->count, user_it.ptrs, user_it.sizes); user_it.ids[pivot_term] = it->event_id; - user_it.system = o->entity; + user_it.system = o->filter.entity; user_it.term_index = pivot_term; user_it.ctx = o->ctx; user_it.binding_ctx = o->binding_ctx; @@ -43418,10 +47176,8 @@ bool flecs_multi_observer_invoke(ecs_iter_t *it) { user_it.callback = o->callback; flecs_iter_validate(&user_it); - flecs_observer_invoke(&user_it); + flecs_observer_invoke(world, &user_it, o, o->callback, table, pivot_term); ecs_iter_fini(&user_it); - - ecs_log_pop_3(); return true; } @@ -43430,414 +47186,40 @@ done: return false; } -static -ecs_entity_t flecs_observer_get_actual_event( - ecs_observer_t *observer, - ecs_entity_t event) -{ - /* If operator is Not, reverse the event */ - if (observer->filter.terms[0].oper == EcsNot) { - if (event == EcsOnAdd) { - event = EcsOnRemove; - } else if (event == EcsOnRemove) { - event = EcsOnAdd; - } - } - - return event; -} - -static -void flecs_unregister_event_observer( - ecs_event_record_t *evt, - ecs_id_t id) -{ - if (ecs_map_remove(&evt->event_ids, id) == 0) { - ecs_map_fini(&evt->event_ids); - } -} - -static -ecs_event_id_record_t* flecs_ensure_event_id_record( - ecs_map_t *map, - ecs_id_t id) -{ - ecs_event_id_record_t **idt = ecs_map_ensure( - map, ecs_event_id_record_t*, id); - if (!idt[0]) { - idt[0] = ecs_os_calloc_t(ecs_event_id_record_t); - } - - return idt[0]; -} - -static -ecs_flags32_t flecs_id_flag_for_event( - ecs_entity_t e) -{ - if (e == EcsOnAdd) { - return EcsIdHasOnAdd; - } - if (e == EcsOnRemove) { - return EcsIdHasOnRemove; - } - if (e == EcsOnSet) { - return EcsIdHasOnSet; - } - if (e == EcsUnSet) { - return EcsIdHasUnSet; - } - return 0; -} - -static -void flecs_inc_observer_count( - ecs_world_t *world, - ecs_entity_t event, - ecs_event_record_t *evt, - ecs_id_t id, - int32_t value) -{ - ecs_event_id_record_t *idt = flecs_ensure_event_id_record(&evt->event_ids, id); - ecs_assert(idt != NULL, ECS_INTERNAL_ERROR, NULL); - - int32_t result = idt->observer_count += value; - if (result == 1) { - /* Notify framework that there are observers for the event/id. This - * allows parts of the code to skip event evaluation early */ - flecs_notify_tables(world, id, &(ecs_table_event_t){ - .kind = EcsTableTriggersForId, - .event = event - }); - - ecs_flags32_t flags = flecs_id_flag_for_event(event); - if (flags) { - ecs_id_record_t *idr = flecs_id_record_get(world, id); - if (idr) { - idr->flags |= flags; - } - } - } else if (result == 0) { - /* Ditto, but the reverse */ - flecs_notify_tables(world, id, &(ecs_table_event_t){ - .kind = EcsTableNoTriggersForId, - .event = event - }); - - ecs_flags32_t flags = flecs_id_flag_for_event(event); - if (flags) { - ecs_id_record_t *idr = flecs_id_record_get(world, id); - if (idr) { - idr->flags &= ~flags; - } - } - - /* Remove admin for id for event */ - if (!ecs_map_is_initialized(&idt->observers) && - !ecs_map_is_initialized(&idt->set_observers)) - { - flecs_unregister_event_observer(evt, id); - ecs_os_free(idt); - } - } -} - -static -void flecs_register_observer_for_id( - ecs_world_t *world, - ecs_observable_t *observable, - ecs_observer_t *observer, - ecs_id_t id, - size_t offset) -{ - ecs_sparse_t *events = observable->events; - ecs_assert(events != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_id_t term_id = observer->register_id; - - int i; - for (i = 0; i < observer->event_count; i ++) { - ecs_entity_t event = flecs_observer_get_actual_event( - observer, observer->events[i]); - - /* Get observers for event */ - ecs_event_record_t *evt = flecs_sparse_ensure( - events, ecs_event_record_t, event); - ecs_assert(evt != NULL, ECS_INTERNAL_ERROR, NULL); - - ecs_map_init_w_params_if(&evt->event_ids, &world->allocators.ptr); - - /* Get observers for (component) id for event */ - ecs_event_id_record_t *idt = flecs_ensure_event_id_record( - &evt->event_ids, id); - ecs_assert(idt != NULL, ECS_INTERNAL_ERROR, NULL); - - ecs_map_t *observers = ECS_OFFSET(idt, offset); - ecs_map_init_w_params_if(observers, &world->allocators.ptr); - - ecs_map_ensure(observers, ecs_observer_t*, - observer->entity)[0] = observer; - - flecs_inc_observer_count(world, event, evt, term_id, 1); - if (term_id != id) { - flecs_inc_observer_count(world, event, evt, id, 1); - } - } -} - -static -void flecs_uni_observer_register( - ecs_world_t *world, - ecs_observable_t *observable, - ecs_observer_t *observer) -{ - ecs_term_t *term = &observer->filter.terms[0]; - ecs_id_t id = observer->register_id; - - if (term->src.flags & EcsSelf) { - if (ecs_term_match_this(term)) { - flecs_register_observer_for_id(world, observable, observer, id, - offsetof(ecs_event_id_record_t, observers)); - } else { - flecs_register_observer_for_id(world, observable, observer, id, - offsetof(ecs_event_id_record_t, entity_observers)); - } - } - - if (observer->filter.terms[0].src.flags & EcsUp) { - ecs_id_t pair = ecs_pair(term->src.trav, EcsWildcard); - flecs_register_observer_for_id(world, observable, observer, pair, - offsetof(ecs_event_id_record_t, set_observers)); - } -} - -static -void flecs_unregister_observer_for_id( - ecs_world_t *world, - ecs_observable_t *observable, - ecs_observer_t *observer, - ecs_id_t id, - size_t offset) -{ - ecs_sparse_t *events = observable->events; - ecs_assert(events != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_id_t term_id = observer->register_id; - - int i; - for (i = 0; i < observer->event_count; i ++) { - ecs_entity_t event = flecs_observer_get_actual_event( - observer, observer->events[i]); - - /* Get observers for event */ - ecs_event_record_t *evt = flecs_sparse_get( - events, ecs_event_record_t, event); - ecs_assert(evt != NULL, ECS_INTERNAL_ERROR, NULL); - - /* Get observers for (component) id */ - ecs_event_id_record_t *idt = ecs_map_get_ptr( - &evt->event_ids, ecs_event_id_record_t*, id); - ecs_assert(idt != NULL, ECS_INTERNAL_ERROR, NULL); - - ecs_map_t *id_observers = ECS_OFFSET(idt, offset); - - if (ecs_map_remove(id_observers, observer->entity) == 0) { - ecs_map_fini(id_observers); - } - - flecs_inc_observer_count(world, event, evt, term_id, -1); - - if (id != term_id) { - /* Id is different from term_id in case of a set observer. If they're - * the same, flecs_inc_observer_count could already have done cleanup */ - if (!ecs_map_is_initialized(&idt->observers) && - !ecs_map_is_initialized(&idt->set_observers) && - !idt->observer_count) - { - flecs_unregister_event_observer(evt, id); - } - - flecs_inc_observer_count(world, event, evt, id, -1); - } - } -} - -static -void flecs_unregister_observer( - ecs_world_t *world, - ecs_observable_t *observable, - ecs_observer_t *observer) -{ - ecs_assert(observer != NULL, ECS_INTERNAL_ERROR, NULL); - if (!observer->filter.terms) { - ecs_assert(observer->filter.term_count == 0, ECS_INTERNAL_ERROR, NULL); - return; - } - - ecs_term_t *term = &observer->filter.terms[0]; - ecs_id_t id = observer->register_id; - - if (term->src.flags & EcsSelf) { - if (ecs_term_match_this(term)) { - flecs_unregister_observer_for_id(world, observable, observer, id, - offsetof(ecs_event_id_record_t, observers)); - } else { - flecs_unregister_observer_for_id(world, observable, observer, id, - offsetof(ecs_event_id_record_t, entity_observers)); - } - } - - if (term->src.flags & EcsUp) { - ecs_id_t pair = ecs_pair(term->src.trav, EcsWildcard); - flecs_unregister_observer_for_id(world, observable, observer, pair, - offsetof(ecs_event_id_record_t, set_observers)); - } -} - -static -ecs_map_t* flecs_get_observers_for_event( - const ecs_observable_t *observable, - ecs_entity_t event) -{ - ecs_check(observable != NULL, ECS_INVALID_PARAMETER, NULL); - ecs_assert(event != 0, ECS_INTERNAL_ERROR, NULL); - - ecs_sparse_t *events = observable->events; - ecs_assert(events != NULL, ECS_INTERNAL_ERROR, NULL); - - const ecs_event_record_t *evt = flecs_sparse_get( - events, ecs_event_record_t, event); - - if (evt) { - return (ecs_map_t*)&evt->event_ids; - } - -error: - return NULL; -} - -static -ecs_event_id_record_t* flecs_get_observers_for_id( - const ecs_map_t *evt, - ecs_id_t id) -{ - return ecs_map_get_ptr(evt, ecs_event_id_record_t*, id); -} - -static -void flecs_init_observer_iter( - ecs_iter_t *it, - bool *iter_set) -{ - ecs_assert(it != NULL, ECS_INTERNAL_ERROR, NULL); - - if (*iter_set) { - return; - } - - it->ids[0] = it->event_id; - - if (ECS_BIT_IS_SET(it->flags, EcsIterTableOnly)) { - return; - } - - it->field_count = 1; - ecs_world_t *world = it->world; - flecs_iter_init(world, it, flecs_iter_cache_all); - flecs_iter_validate(it); - - *iter_set = true; - - ecs_assert(it->table != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(!it->count || it->offset < ecs_table_count(it->table), - ECS_INTERNAL_ERROR, NULL); - ecs_assert((it->offset + it->count) <= ecs_table_count(it->table), - ECS_INTERNAL_ERROR, NULL); - - int32_t index = ecs_search_relation(it->real_world, it->table, 0, - it->event_id, EcsIsA, 0, it->sources, 0, 0); - - if (index == -1) { - it->columns[0] = 0; - } else if (it->sources[0]) { - it->columns[0] = -index - 1; - } else { - it->columns[0] = index + 1; - } - - ecs_term_t term = { - .id = it->event_id - }; - - it->terms = &term; - flecs_iter_populate_data(it->real_world, it, it->table, it->offset, - it->count, it->ptrs, it->sizes); -} - -static -bool flecs_ignore_observer( - ecs_world_t *world, - ecs_observer_t *observer, - ecs_table_t *table) -{ - int32_t *last_event_id = observer->last_event_id; - if (last_event_id && last_event_id[0] == world->event_id) { - return true; - } - - if (!table) { - return false; - } - - ecs_filter_t *filter = &observer->filter; - if (!(filter->flags & EcsFilterMatchPrefab) && - (table->flags & EcsTableIsPrefab)) - { - return true; - } - if (!(filter->flags & EcsFilterMatchDisabled) && - (table->flags & EcsTableIsDisabled)) - { - return true; - } - - return false; -} - bool ecs_observer_default_run_action(ecs_iter_t *it) { - ecs_observer_t *observer = it->ctx; - if (observer->is_multi) { + ecs_observer_t *o = it->ctx; + if (o->is_multi) { return flecs_multi_observer_invoke(it); } else { - flecs_observer_invoke(it); + it->ctx = o->ctx; + flecs_observer_invoke(it->real_world, it, o, o->callback, it->table, 0); return true; } } -static +static void flecs_default_multi_observer_run_callback(ecs_iter_t *it) { flecs_multi_observer_invoke(it); } -static void flecs_default_uni_observer_run_callback(ecs_iter_t *it) { - ecs_observer_t *observer = it->ctx; - it->ctx = observer->ctx; - it->callback = observer->callback; + ecs_observer_t *o = it->ctx; + it->ctx = o->ctx; + it->callback = o->callback; if (ecs_should_log_3()) { char *path = ecs_get_fullpath(it->world, it->system); ecs_dbg_3("observer %s", path); ecs_os_free(path); - ecs_log_push_3(); } - flecs_observer_invoke(it); - + ecs_log_push_3(); + flecs_observer_invoke(it->real_world, it, o, o->callback, it->table, 0); ecs_log_pop_3(); } /* For convenience, so applications can (in theory) use a single run callback * that uses ecs_iter_next to iterate results */ -static bool flecs_default_observer_next_callback(ecs_iter_t *it) { if (it->interrupted_by) { return false; @@ -43864,301 +47246,6 @@ void flecs_multi_observer_builtin_run(ecs_iter_t *it) { } } -/* Run action for uni observer */ -static -void flecs_uni_observer_builtin_run( - ecs_observer_t *observer, - ecs_iter_t *it) -{ - ecs_flags32_t flags = it->flags; - ECS_BIT_COND(it->flags, EcsIterIsFilter, - observer->filter.terms[0].inout == EcsInOutNone); - - it->system = observer->entity; - it->ctx = observer->ctx; - it->binding_ctx = observer->binding_ctx; - it->term_index = observer->term_index; - it->terms = observer->filter.terms; - - void *ptrs = it->ptrs; - if (it->flags & EcsIterIsFilter) { - it->ptrs = NULL; - } - - ecs_entity_t event = it->event; - it->event = flecs_observer_get_actual_event(observer, event); - - if (observer->run) { - it->next = flecs_default_observer_next_callback; - it->callback = flecs_default_uni_observer_run_callback; - it->ctx = observer; - observer->run(it); - } else { - it->callback = observer->callback; - flecs_observer_invoke(it); - } - - it->event = event; - it->ptrs = ptrs; - it->flags = flags; -} - -static -void flecs_notify_self_observers( - ecs_world_t *world, - ecs_iter_t *it, - const ecs_map_t *observers) -{ - ecs_assert(observers != NULL, ECS_INTERNAL_ERROR, NULL); - - ecs_map_iter_t mit = ecs_map_iter(observers); - ecs_observer_t *observer; - while ((observer = ecs_map_next_ptr(&mit, ecs_observer_t*, NULL))) { - if (flecs_ignore_observer(world, observer, it->table)) { - continue; - } - - flecs_uni_observer_builtin_run(observer, it); - } -} - -static -void flecs_notify_entity_observers( - ecs_world_t *world, - ecs_iter_t *it, - const ecs_map_t *observers) -{ - ecs_assert(observers != NULL, ECS_INTERNAL_ERROR, NULL); - - if (ECS_BIT_IS_SET(it->flags, EcsIterTableOnly)) { - return; - } - - ecs_map_iter_t mit = ecs_map_iter(observers); - ecs_observer_t *observer; - int32_t offset = it->offset, count = it->count; - ecs_entity_t *entities = it->entities; - - ecs_entity_t dummy = 0; - it->entities = &dummy; - - while ((observer = ecs_map_next_ptr(&mit, ecs_observer_t*, NULL))) { - if (flecs_ignore_observer(world, observer, it->table)) { - continue; - } - - int32_t i, entity_count = it->count; - for (i = 0; i < entity_count; i ++) { - if (entities[i] != observer->filter.terms[0].src.id) { - continue; - } - - it->offset = i; - it->count = 1; - it->sources[0] = entities[i]; - flecs_uni_observer_builtin_run(observer, it); - } - } - - it->offset = offset; - it->count = count; - it->entities = entities; - it->sources[0] = 0; -} - -static -void flecs_notify_set_base_observers( - ecs_world_t *world, - ecs_iter_t *it, - const ecs_map_t *observers) -{ - ecs_assert(observers != NULL, ECS_INTERNAL_ERROR, NULL); - - ecs_entity_t event_id = it->event_id; - ecs_entity_t rel = ECS_PAIR_FIRST(event_id); - ecs_entity_t obj = ecs_pair_second(world, event_id); - if (!obj) { - /* Don't notify for deleted (or in progress of being deleted) object */ - return; - } - - ecs_record_t *obj_record = flecs_entities_get(world, obj); - if (!obj_record) { - return; - } - - ecs_table_t *obj_table = obj_record->table; - if (!obj_table) { - return; - } - - ecs_map_iter_t mit = ecs_map_iter(observers); - ecs_observer_t *observer; - while ((observer = ecs_map_next_ptr(&mit, ecs_observer_t*, NULL))) { - if (flecs_ignore_observer(world, observer, it->table)) { - continue; - } - - ecs_term_t *term = &observer->filter.terms[0]; - ecs_id_t id = term->id; - int32_t column = ecs_search_relation(world, obj_table, 0, id, rel, - 0, it->sources, it->ids, 0); - - bool result = column != -1; - if (!result) { - continue; - } - - if ((term->src.flags & EcsSelf) && flecs_table_record_get( - world, it->table, id) != NULL) - { - continue; - } - - if (!ECS_BIT_IS_SET(it->flags, EcsIterTableOnly)) { - if (!it->sources[0]) { - it->sources[0] = obj; - } - - /* Populate pointer from object */ - int32_t s_column = ecs_table_type_to_storage_index( - obj_table, column); - if (s_column != -1) { - ecs_vec_t *c = &obj_table->data.columns[s_column]; - int32_t row = ECS_RECORD_TO_ROW(obj_record->row); - ecs_type_info_t *ti = obj_table->type_info[s_column]; - void *ptr = ecs_vec_get(c, ti->size, row); - it->ptrs[0] = ptr; - it->sizes[0] = ti->size; - } - } - - it->event_id = observer->filter.terms[0].id; - flecs_uni_observer_builtin_run(observer, it); - } -} - -static -void flecs_notify_set_observers( - ecs_world_t *world, - ecs_iter_t *it, - const ecs_map_t *observers) -{ - ecs_assert(observers != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(it->count != 0, ECS_INTERNAL_ERROR, NULL); - - if (ECS_BIT_IS_SET(it->flags, EcsIterTableOnly)) { - return; - } - - ecs_map_iter_t mit = ecs_map_iter(observers); - ecs_observer_t *observer; - while ((observer = ecs_map_next_ptr(&mit, ecs_observer_t*, NULL))) { - if (!ecs_id_match(it->event_id, observer->filter.terms[0].id)) { - continue; - } - - if (flecs_ignore_observer(world, observer, it->table)) { - continue; - } - - ecs_entity_t src = it->entities[0]; - int32_t i, count = it->count; - ecs_filter_t *filter = &observer->filter; - ecs_term_t *term = &filter->terms[0]; - ecs_entity_t src_id = term->src.id; - - /* If observer is for a specific entity, make sure it is in the table - * being triggered for */ - if (!(ecs_term_match_this(term))) { - for (i = 0; i < count; i ++) { - if (it->entities[i] == src_id) { - break; - } - } - - if (i == count) { - continue; - } - - /* If the entity matches, observer for no other entities */ - it->entities[0] = 0; - it->count = 1; - } - - if (flecs_term_match_table(world, term, it->table, it->ids, - it->columns, it->sources, NULL, true, it->flags)) - { - if (!it->sources[0]) { - /* Do not match owned components */ - continue; - } - - /* Triggers for supersets can be instanced */ - bool instanced = filter->flags & EcsFilterIsInstanced; - bool is_filter = ECS_BIT_IS_SET(it->flags, EcsIterIsFilter); - if (it->count == 1 || instanced || is_filter || !it->sizes[0]) { - ECS_BIT_COND(it->flags, EcsIterIsInstanced, instanced); - flecs_uni_observer_builtin_run(observer, it); - ECS_BIT_CLEAR(it->flags, EcsIterIsInstanced); - } else { - ecs_entity_t *entities = it->entities; - it->count = 1; - for (i = 0; i < count; i ++) { - it->entities = &entities[i]; - flecs_uni_observer_builtin_run(observer, it); - } - it->entities = entities; - } - } - - it->entities[0] = src; - it->count = count; - } -} - -static -void flecs_notify_observers_for_id( - ecs_world_t *world, - const ecs_map_t *evt, - ecs_id_t event_id, - ecs_iter_t *it, - bool *iter_set) -{ - const ecs_event_id_record_t *idt = flecs_get_observers_for_id(evt, event_id); - if (!idt) { - return; - } - - if (ecs_map_is_initialized(&idt->observers)) { - flecs_init_observer_iter(it, iter_set); - flecs_notify_self_observers(world, it, &idt->observers); - } - if (ecs_map_is_initialized(&idt->entity_observers)) { - flecs_init_observer_iter(it, iter_set); - flecs_notify_entity_observers(world, it, &idt->entity_observers); - } - if (ecs_map_is_initialized(&idt->set_observers)) { - flecs_init_observer_iter(it, iter_set); - flecs_notify_set_base_observers(world, it, &idt->set_observers); - } -} - -static -void flecs_notify_set_observers_for_id( - ecs_world_t *world, - const ecs_map_t *evt, - ecs_iter_t *it, - bool *iter_set, - ecs_id_t set_id) -{ - const ecs_event_id_record_t *idt = flecs_get_observers_for_id(evt, set_id); - if (idt && ecs_map_is_initialized(&idt->set_observers)) { - flecs_init_observer_iter(it, iter_set); - flecs_notify_set_observers(world, it, &idt->set_observers); - } -} - static void flecs_uni_observer_trigger_existing( ecs_world_t *world, @@ -44178,7 +47265,7 @@ void flecs_uni_observer_trigger_existing( ecs_iter_t it; iterable->init(world, world, &it, &observer->filter.terms[0]); - it.system = observer->entity; + it.system = observer->filter.entity; it.ctx = observer->ctx; it.binding_ctx = observer->binding_ctx; it.event = evt; @@ -44226,7 +47313,7 @@ void flecs_multi_observer_yield_existing( it.terms = observer->filter.terms; it.field_count = 1; it.term_index = pivot_term; - it.system = observer->entity; + it.system = observer->filter.entity; it.ctx = observer; it.binding_ctx = observer->binding_ctx; it.event = evt; @@ -44241,120 +47328,6 @@ void flecs_multi_observer_yield_existing( } } -bool flecs_check_observers_for_event( - const ecs_poly_t *object, - ecs_id_t id, - ecs_entity_t event) -{ - ecs_observable_t *observable = ecs_get_observable(object); - const ecs_map_t *evt = flecs_get_observers_for_event(observable, event); - if (!evt) { - return false; - } - - ecs_event_id_record_t *edr = flecs_get_observers_for_id(evt, id); - if (edr) { - return edr->observer_count != 0; - } else { - return false; - } -} - -void flecs_observers_notify( - ecs_iter_t *it, - ecs_observable_t *observable, - const ecs_type_t *ids, - ecs_entity_t event) -{ - ecs_assert(ids != NULL && ids->count != 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(ids->array != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_entity_t events[2] = {event, EcsWildcard}; - int32_t e, i, ids_count = ids->count; - ecs_id_t *ids_array = ids->array; - ecs_world_t *world = it->real_world; - - for (e = 0; e < 2; e ++) { - event = events[e]; - const ecs_map_t *evt = flecs_get_observers_for_event(observable, event); - if (!evt) { - continue; - } - - it->event = event; - - for (i = 0; i < ids_count; i ++) { - ecs_id_t id = ids_array[i]; - ecs_entity_t role = id & ECS_ID_FLAGS_MASK; - bool iter_set = false; - - it->event_id = id; - - flecs_notify_observers_for_id(world, evt, id, it, &iter_set); - - if (ECS_HAS_ID_FLAG(role, PAIR)) { - ecs_entity_t r = ECS_PAIR_FIRST(id); - ecs_entity_t o = ECS_PAIR_SECOND(id); - - ecs_id_t tid = ecs_pair(r, EcsWildcard); - flecs_notify_observers_for_id(world, evt, tid, it, &iter_set); - - tid = ecs_pair(EcsWildcard, o); - flecs_notify_observers_for_id(world, evt, tid, it, &iter_set); - - tid = ecs_pair(EcsWildcard, EcsWildcard); - flecs_notify_observers_for_id(world, evt, tid, it, &iter_set); - } else { - flecs_notify_observers_for_id( - world, evt, EcsWildcard, it, &iter_set); - } - - flecs_notify_observers_for_id(world, evt, EcsAny, it, &iter_set); - - if (iter_set) { - ecs_iter_fini(it); - } - } - } -} - -void flecs_set_observers_notify( - ecs_iter_t *it, - ecs_observable_t *observable, - const ecs_type_t *ids, - ecs_entity_t event, - ecs_id_t set_id) -{ - ecs_assert(ids != NULL && ids->count != 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(ids->array != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_entity_t events[2] = {event, EcsWildcard}; - int32_t e, i, ids_count = ids->count; - ecs_id_t *ids_array = ids->array; - ecs_world_t *world = it->real_world; - - for (e = 0; e < 2; e ++) { - event = events[e]; - const ecs_map_t *evt = flecs_get_observers_for_event(observable, event); - if (!evt) { - continue; - } - - it->event = event; - - for (i = 0; i < ids_count; i ++) { - ecs_id_t id = ids_array[i]; - bool iter_set = false; - - it->event_id = id; - - flecs_notify_set_observers_for_id(world, evt, it, &iter_set, set_id); - - if (iter_set) { - ecs_iter_fini(it); - } - } - } -} - static int flecs_uni_observer_init( ecs_world_t *world, @@ -44438,7 +47411,7 @@ int flecs_multi_observer_init( } /* Create observers as children of observer */ - ecs_entity_t old_scope = ecs_set_scope(world, observer->entity); + ecs_entity_t old_scope = ecs_set_scope(world, observer->filter.entity); for (i = 0; i < term_count; i ++) { if (filter->terms[i].src.flags & EcsFilter) { @@ -44529,16 +47502,13 @@ ecs_entity_t ecs_observer_init( ecs_observer_t *observer = ecs_poly_new(ecs_observer_t); ecs_assert(observer != NULL, ECS_INTERNAL_ERROR, NULL); - - observer->world = world; observer->dtor = (ecs_poly_dtor_t)flecs_observer_fini; - observer->entity = entity; /* Make writeable copy of filter desc so that we can set name. This will * make debugging easier, as any error messages related to creating the * filter will have the name of the observer. */ ecs_filter_desc_t filter_desc = desc->filter; - filter_desc.name = ecs_get_name(world, entity); + filter_desc.entity = entity; ecs_filter_t *filter = filter_desc.storage = &observer->filter; *filter = ECS_FILTER_INIT; @@ -44678,7 +47648,7 @@ void flecs_observer_fini( } else { if (observer->filter.term_count) { flecs_unregister_observer( - observer->world, observer->observable, observer); + observer->filter.world, observer->observable, observer); } else { /* Observer creation failed while creating filter */ } @@ -44699,6 +47669,22 @@ void flecs_observer_fini( ecs_poly_free(observer, ecs_observer_t); } +/** + * @file table_cache.c + * @brief Data structure for fast table iteration/lookups. + * + * A table cache is a data structure that provides constant time operations for + * insertion and removal of tables, and to testing whether a table is registered + * with the cache. A table cache also provides functions to iterate the tables + * in a cache. + * + * The world stores a table cache per (component) id inside the id record + * administration. Cached queries store a table cache with matched tables. + * + * A table cache has separate lists for non-empty tables and empty tables. This + * improves performance as applications don't waste time iterating empty tables. + */ + static void flecs_table_cache_list_remove( @@ -44975,6 +47961,15 @@ ecs_table_cache_hdr_t* _flecs_table_cache_next( return next; } +/** + * @file os_api.h + * @brief Operating system abstraction API. + * + * The OS API implements an overridable interface for implementing functions + * that are operating system specific, in addition to a number of hooks which + * allow for customization by the user, like logging. + */ + #include #include @@ -45042,6 +48037,8 @@ void ecs_os_fini(void) { #define HAVE_EXECINFO 0 #elif !defined(ECS_TARGET_WINDOWS) && !defined(ECS_TARGET_EM) && !defined(ECS_TARGET_ANDROID) #define HAVE_EXECINFO 1 +#else +#define HAVE_EXECINFO 0 #endif #if HAVE_EXECINFO @@ -45062,7 +48059,7 @@ void flecs_dump_backtrace( return; } - for (int j = 3; j < nptrs; j++) { + for (int j = 1; j < nptrs; j++) { fprintf(stream, "%s\n", strings[j]); } @@ -45132,7 +48129,10 @@ void flecs_log_msg( fputs(" ", stream); } - if (level >= 0) { + if (level >= 4) { + if (use_colors) fputs(ECS_NORMAL, stream); + fputs("jrnl", stream); + } else if (level >= 0) { if (level == 0) { if (use_colors) fputs(ECS_MAGENTA, stream); } else { @@ -45439,7 +48439,8 @@ bool ecs_os_has_threading(void) { (ecs_os_api.cond_signal_ != NULL) && (ecs_os_api.cond_broadcast_ != NULL) && (ecs_os_api.thread_new_ != NULL) && - (ecs_os_api.thread_join_ != NULL); + (ecs_os_api.thread_join_ != NULL) && + (ecs_os_api.thread_self_ != NULL); } bool ecs_os_has_time(void) { @@ -45479,6 +48480,21 @@ const char* ecs_os_strerror(int err) { # endif } +/** + * @file query.c + * @brief Cached query implementation. + * + * Cached queries store a list of matched tables. The inputs for a cached query + * are a filter and an observer. The filter is used to initially populate the + * cache, and an observer is used to keep the cacne up to date. + * + * Cached queries additionally support features like sorting and grouping. + * With sorting, an application can iterate over entities that can be sorted by + * a component. Grouping allows an application to group matched tables, which is + * used internally to implement the cascade feature, and can additionally be + * used to implement things like world cells. + */ + static uint64_t flecs_query_get_group_id( @@ -45486,7 +48502,7 @@ uint64_t flecs_query_get_group_id( ecs_table_t *table) { if (query->group_by) { - return query->group_by(query->world, table, + return query->group_by(query->filter.world, table, query->group_by_id, query->group_by_ctx); } else { return 0; @@ -45532,7 +48548,7 @@ ecs_query_table_list_t* flecs_query_ensure_group( &query->groups, ecs_query_table_list_t, group_id); if (created) { group->info.ctx = query->on_group_create( - query->world, group_id, query->group_by_ctx); + query->filter.world, group_id, query->group_by_ctx); } return group; @@ -45548,7 +48564,7 @@ void flecs_query_remove_group( &query->groups, ecs_query_table_list_t, group_id); if (group) { query->on_group_delete( - query->world, group_id, group->info.ctx, query->group_by_ctx); + query->filter.world, group_id, group->info.ctx, query->group_by_ctx); } } @@ -45787,8 +48803,8 @@ void flecs_query_insert_table_node( ECS_INTERNAL_ERROR, NULL); /* If this is the first match, activate system */ - if (!query->list.first && query->entity) { - ecs_remove_id(query->world, query->entity, EcsEmpty); + if (!query->list.first && query->filter.entity) { + ecs_remove_id(query->filter.world, query->filter.entity, EcsEmpty); } flecs_query_compute_group_id(query, node->match); @@ -45885,7 +48901,7 @@ void flecs_query_get_dirty_state( int32_t term, table_dirty_state_t *out) { - ecs_world_t *world = query->world; + ecs_world_t *world = query->filter.world; ecs_entity_t subject = match->sources[term]; ecs_table_t *table; int32_t column = -1; @@ -45987,7 +49003,7 @@ void flecs_query_sync_match_monitor( int32_t *monitor = match->monitor; ecs_table_t *table = match->node.table; - int32_t *dirty_state = flecs_table_get_dirty_state(query->world, table); + int32_t *dirty_state = flecs_table_get_dirty_state(query->filter.world, table); ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); table_dirty_state_t cur; @@ -46021,7 +49037,7 @@ bool flecs_query_check_match_monitor_term( int32_t *monitor = match->monitor; ecs_table_t *table = match->node.table; - int32_t *dirty_state = flecs_table_get_dirty_state(query->world, table); + int32_t *dirty_state = flecs_table_get_dirty_state(query->filter.world, table); ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); table_dirty_state_t cur; @@ -46054,14 +49070,14 @@ bool flecs_query_check_match_monitor( int32_t *monitor = match->monitor; ecs_table_t *table = match->node.table; - int32_t *dirty_state = flecs_table_get_dirty_state(query->world, table); + int32_t *dirty_state = flecs_table_get_dirty_state(query->filter.world, table); ecs_assert(dirty_state != NULL, ECS_INTERNAL_ERROR, NULL); if (monitor[0] != dirty_state[0]) { return true; } - ecs_world_t *world = query->world; + ecs_world_t *world = query->filter.world; int32_t i, field_count = query->filter.field_count; int32_t *storage_columns = match->storage_columns; int32_t *columns = match->columns; @@ -46187,22 +49203,13 @@ void flecs_query_add_ref( ecs_world_t *world, ecs_query_t *query, ecs_query_table_match_t *qm, - ecs_term_t *term, ecs_entity_t component, ecs_entity_t entity, ecs_size_t size) { ecs_assert(entity != 0, ECS_INTERNAL_ERROR, NULL); - - if (!qm->refs.array) { - ecs_vec_init_t(&world->allocator, &qm->refs, ecs_ref_t, 1); - } ecs_ref_t *ref = ecs_vec_append_t(&world->allocator, &qm->refs, ecs_ref_t); - ecs_term_id_t *src = &term->src; - - if (!(src->flags & EcsCascade)) { - ecs_assert(entity != 0, ECS_INTERNAL_ERROR, NULL); - } + ecs_assert(entity != 0, ECS_INTERNAL_ERROR, NULL); if (size) { *ref = ecs_ref_init_id(world, entity, component); @@ -46224,7 +49231,7 @@ ecs_query_table_match_t* flecs_query_add_table_match( { /* Add match for table. One table can have more than one match, if * the query contains wildcards. */ - ecs_query_table_match_t *qm = flecs_query_cache_add(query->world, qt); + ecs_query_table_match_t *qm = flecs_query_cache_add(query->filter.world, qt); qm->node.table = table; qm->columns = flecs_balloc(&query->allocators.columns); @@ -46248,25 +49255,15 @@ void flecs_query_set_table_match( ecs_query_table_match_t *qm, ecs_table_t *table, ecs_iter_t *it) -{ +{ + ecs_allocator_t *a = &world->allocator; ecs_filter_t *filter = &query->filter; int32_t i, term_count = filter->term_count; int32_t field_count = filter->field_count; ecs_term_t *terms = filter->terms; /* Reset resources in case this is an existing record */ - if (qm->sparse_columns) { - ecs_vector_free(qm->sparse_columns); - qm->sparse_columns = NULL; - } - if (qm->bitset_columns) { - ecs_vector_free(qm->bitset_columns); - qm->bitset_columns = NULL; - } - if (qm->refs.array) { - ecs_vec_fini_t(&world->allocator, &qm->refs, ecs_ref_t); - } - + ecs_vec_reset_t(a, &qm->refs, ecs_ref_t); ecs_os_memcpy_n(qm->columns, it->columns, int32_t, field_count); ecs_os_memcpy_n(qm->ids, it->ids, ecs_id_t, field_count); ecs_os_memcpy_n(qm->sources, it->sources, ecs_entity_t, field_count); @@ -46285,58 +49282,8 @@ void flecs_query_set_table_match( } } - /* 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 = it->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 *sc = ecs_vector_add( - &qm->sparse_columns, flecs_switch_term_t); - sc->signature_column_index = field; - sc->sw_case = ECS_PAIR_SECOND(id); - sc->sw_column = NULL; - qm->ids[field] = id; - } - } - - /* 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 = it->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_vector_add( - &qm->bitset_columns, flecs_bitset_term_t); - bc->column_index = bs_index; - bc->bs_column = NULL; - } - } - } + flecs_entity_filter_init(world, &qm->entity_filter, filter, + table, qm->ids, qm->columns); } /* Add references for substituted terms */ @@ -46358,7 +49305,7 @@ void flecs_query_set_table_match( ecs_assert(ecs_is_valid(world, src), ECS_INTERNAL_ERROR, NULL); if (id) { - flecs_query_add_ref(world, query, qm, term, id, src, size); + flecs_query_add_ref(world, query, qm, id, src, size); /* Use column index to bind term and ref */ if (qm->columns[field] != 0) { @@ -46407,16 +49354,15 @@ bool flecs_query_match_table( } ecs_query_table_t *qt = NULL; - int var_id = ecs_filter_find_this_var(&query->filter); + ecs_filter_t *filter = &query->filter; + int var_id = ecs_filter_find_this_var(filter); if (var_id == -1) { /* If query doesn't match with This term, it can't match with tables */ return false; } - ecs_iter_t it = ecs_filter_iter(world, &query->filter); - ECS_BIT_SET(it.flags, EcsIterIsInstanced); - ECS_BIT_SET(it.flags, EcsIterIsFilter); - ECS_BIT_SET(it.flags, EcsIterEntityOptional); + ecs_iter_t it = flecs_filter_iter_w_flags(world, filter, EcsIterMatchVar| + EcsIterIsInstanced|EcsIterIsFilter|EcsIterEntityOptional); ecs_iter_set_var_as_table(&it, var_id, table); while (ecs_filter_next(&it)) { @@ -46514,7 +49460,7 @@ void flecs_query_build_sorted_table_range( ecs_query_t *query, ecs_query_table_list_t *list) { - ecs_world_t *world = query->world; + ecs_world_t *world = query->filter.world; ecs_entity_t id = query->order_by_component; ecs_order_by_action_t compare = query->order_by; int32_t table_count = list->info.table_count; @@ -46733,7 +49679,7 @@ void flecs_query_sort_tables( ecs_table_t *storage_table = table->storage_table; if (storage_table) { column = ecs_search(world, storage_table, - order_by_component, NULL); + order_by_component, 0); } if (column == -1) { @@ -46958,6 +49904,7 @@ void flecs_query_table_match_free( ecs_query_table_match_t *first) { ecs_query_table_match_t *cur, *next; + ecs_world_t *world = query->filter.world; for (cur = first; cur != NULL; cur = next) { flecs_bfree(&query->allocators.columns, cur->columns); @@ -46965,24 +49912,20 @@ void flecs_query_table_match_free( flecs_bfree(&query->allocators.ids, cur->ids); flecs_bfree(&query->allocators.sources, cur->sources); flecs_bfree(&query->allocators.sizes, cur->sizes); + if (cur->monitor) { flecs_bfree(&query->allocators.monitors, cur->monitor); } - if (cur->refs.array) { - ecs_allocator_t *a = &query->world->allocator; - ecs_vec_fini_t(a, &cur->refs, ecs_ref_t); - } - - ecs_os_free(cur->sparse_columns); - ecs_os_free(cur->bitset_columns); - if (!elem->hdr.empty) { flecs_query_remove_table_node(query, &cur->node); } + + ecs_vec_fini_t(&world->allocator, &cur->refs, ecs_ref_t); + flecs_entity_filter_fini(world, &cur->entity_filter); next = cur->next_match; - flecs_bfree(&query->world->allocators.query_table_match, cur); + flecs_bfree(&world->allocators.query_table_match, cur); } } @@ -46992,7 +49935,7 @@ void flecs_query_table_free( ecs_query_table_t *elem) { flecs_query_table_match_free(query, elem, elem->first); - flecs_bfree(&query->world->allocators.query_table, elem); + flecs_bfree(&query->filter.world->allocators.query_table, elem); } static @@ -47019,6 +49962,12 @@ void flecs_query_rematch_tables( ecs_query_table_t *qt = NULL; ecs_query_table_match_t *qm = NULL; + if (query->monitor_generation == world->monitor_generation) { + return; + } + + query->monitor_generation = world->monitor_generation; + if (parent_query) { parent_it = ecs_query_iter(world, parent_query); it = ecs_filter_chain_iter(&parent_it, &query->filter); @@ -47038,7 +49987,7 @@ void flecs_query_rematch_tables( ecs_time_measure(&t); } - while (ecs_iter_next(&it)) { + while (ecs_filter_next(&it)) { if ((table != it.table) || (!it.table && !qt)) { if (qm && qm->next_match) { flecs_query_table_match_free(query, qt, qm->next_match); @@ -47203,7 +50152,7 @@ void flecs_query_group_by( query->group_by = group_by; ecs_map_init_w_params(&query->groups, - &query->world->allocators.query_table_list); + &query->filter.world->allocators.query_table_list); error: return; } @@ -47313,7 +50262,7 @@ static void flecs_query_fini( ecs_query_t *query) { - ecs_world_t *world = query->world; + ecs_world_t *world = query->filter.world; ecs_group_delete_action_t on_delete = query->on_group_delete; if (on_delete) { @@ -47357,7 +50306,6 @@ void flecs_query_fini( ecs_poly_free(query, ecs_query_t); } - /* -- Public API -- */ ecs_query_t* ecs_query_init( @@ -47371,7 +50319,7 @@ ecs_query_t* ecs_query_init( ecs_query_t *result = ecs_poly_new(ecs_query_t); ecs_observer_desc_t observer_desc = { .filter = desc->filter }; - ecs_entity_t entity = desc->entity; + ecs_entity_t entity = desc->filter.entity; observer_desc.filter.flags = EcsFilterMatchEmptyTables; observer_desc.filter.storage = &result->filter; @@ -47389,7 +50337,8 @@ ecs_query_t* ecs_query_init( observer_desc.ctx = result; observer_desc.events[0] = EcsOnTableEmpty; observer_desc.events[1] = EcsOnTableFill; - observer_desc.filter.flags |= EcsFilterIsFilter; + observer_desc.filter.flags |= EcsFilterNoData; + observer_desc.filter.instanced = true; /* ecs_filter_init could have moved away resources from the terms array * in the descriptor, so use the terms array from the filter. */ @@ -47403,7 +50352,6 @@ ecs_query_t* ecs_query_init( } } - result->world = world; result->iterable.init = flecs_query_iter_init; result->dtor = (ecs_poly_dtor_t)flecs_query_fini; result->prev_match_count = -1; @@ -47464,7 +50412,7 @@ ecs_query_t* ecs_query_init( EcsPoly *poly = ecs_poly_bind(world, entity, ecs_query_t); poly->poly = result; - result->entity = entity; + result->filter.entity = entity; /* Ensure that while initially populating the query with tables, they are * in the right empty/non-empty list. This ensures the query won't miss @@ -47508,7 +50456,7 @@ void ecs_query_fini( ecs_query_t *query) { ecs_poly_assert(query, ecs_query_t); - ecs_delete(query->world, query->entity); + ecs_delete(query->filter.world, query->filter.entity); } const ecs_filter_t* ecs_query_get_filter( @@ -47526,7 +50474,7 @@ ecs_iter_t ecs_query_iter( ecs_check(!(query->flags & EcsQueryIsOrphaned), ECS_INVALID_PARAMETER, NULL); - ecs_world_t *world = query->world; + ecs_world_t *world = query->filter.world; ecs_poly_assert(world, ecs_world_t); /* Process table events to ensure that the list of iterated tables doesn't @@ -47564,7 +50512,7 @@ ecs_iter_t ecs_query_iter( ecs_flags32_t flags = 0; ECS_BIT_COND(flags, EcsIterIsFilter, ECS_BIT_IS_SET(query->filter.flags, - EcsFilterIsFilter)); + EcsFilterNoData)); ECS_BIT_COND(flags, EcsIterIsInstanced, ECS_BIT_IS_SET(query->filter.flags, EcsFilterIsInstanced)); @@ -47676,330 +50624,6 @@ void* ecs_query_get_group_ctx( } } -static -int find_smallest_column( - ecs_table_t *table, - ecs_query_table_match_t *table_data, - ecs_vector_t *sparse_columns) -{ - flecs_switch_term_t *sparse_column_array = - ecs_vector_first(sparse_columns, flecs_switch_term_t); - int32_t i, count = ecs_vector_count(sparse_columns); - 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 = &sparse_column_array[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 = table_data->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; -} - -typedef struct { - int32_t first; - int32_t count; -} query_iter_cursor_t; - -static -int sparse_column_next( - ecs_table_t *table, - ecs_query_table_match_t *matched_table, - ecs_vector_t *sparse_columns, - ecs_query_iter_t *iter, - query_iter_cursor_t *cur, - bool filter) -{ - bool first_iteration = false; - int32_t sparse_smallest; - - if (!(sparse_smallest = iter->sparse_smallest)) { - sparse_smallest = iter->sparse_smallest = find_smallest_column( - table, matched_table, sparse_columns); - first_iteration = true; - } - - sparse_smallest -= 1; - - flecs_switch_term_t *columns = ecs_vector_first( - sparse_columns, flecs_switch_term_t); - flecs_switch_term_t *column = &columns[sparse_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->sparse_first; - - 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 = cur->first, cur_count = cur->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_vector_count(sparse_columns); - do { - for (i = 0; i < count; i ++) { - if (i == sparse_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); - - cur->first = iter->sparse_first = first; - cur->count = 1; - - return 0; -done: - /* Iterated all elements in the sparse list, we should move to the - * next matched table. */ - iter->sparse_smallest = 0; - iter->sparse_first = 0; - - return -1; -} - -#define BS_MAX ((uint64_t)0xFFFFFFFFFFFFFFFF) - -static -int bitset_column_next( - ecs_table_t *table, - ecs_vector_t *bitset_columns, - ecs_query_iter_t *iter, - query_iter_cursor_t *cur) -{ - /* 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_vector_count(bitset_columns); - flecs_bitset_term_t *columns = ecs_vector_first( - bitset_columns, flecs_bitset_term_t); - int32_t bs_offset = table->bs_offset; - - int32_t first = iter->bitset_first; - int32_t last = 0; - - for (i = 0; i < count; i ++) { - flecs_bitset_term_t *column = &columns[i]; - ecs_bitset_t *bs = columns[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]; - columns[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->bitset_first = 0; - goto done; - } - } - - cur->first = first; - cur->count = elem_count; - iter->bitset_first = first; - } - - /* Keep track of last processed element for iteration */ - iter->bitset_first = last; - - return 0; -done: - iter->sparse_smallest = 0; - iter->sparse_first = 0; - return -1; -} - static void flecs_query_mark_columns_dirty( ecs_query_t *query, @@ -48083,7 +50707,7 @@ void ecs_query_populate( ecs_table_t *table = node->table; ecs_query_table_match_t *match = node->match; ecs_query_t *query = iter->query; - ecs_world_t *world = query->world; + ecs_world_t *world = query->filter.world; const ecs_filter_t *filter = &query->filter; bool only_this = filter->flags & EcsFilterMatchOnlyThis; @@ -48146,15 +50770,13 @@ bool ecs_query_next_instanced( ecs_query_iter_t *iter = &it->priv.iter.query; ecs_query_t *query = iter->query; - ecs_world_t *world = query->world; + ecs_world_t *world = query->filter.world; ecs_flags32_t flags = query->flags; const ecs_filter_t *filter = &query->filter; bool only_this = filter->flags & EcsFilterMatchOnlyThis; - ecs_poly_assert(world, ecs_world_t); - (void)world; - query_iter_cursor_t cur; + ecs_entity_filter_iter_t *ent_it = it->priv.entity_iter; ecs_query_table_node_t *node, *next, *prev, *last; if ((prev = iter->prev)) { /* Match has been iterated, update monitor for change tracking */ @@ -48166,79 +50788,41 @@ bool ecs_query_next_instanced( } } - iter->skip_count = 0; - flecs_iter_validate(it); - + iter->skip_count = 0; last = iter->last; + for (node = iter->node; node != last; node = next) { + ecs_assert(ent_it != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_table_range_t *range = &ent_it->range; ecs_query_table_match_t *match = node->match; ecs_table_t *table = match->node.table; next = node->next; if (table) { - cur.first = node->offset; - cur.count = node->count; - if (!cur.count) { - cur.count = ecs_table_count(table); - - /* List should never contain empty tables */ - ecs_assert(cur.count != 0, ECS_INTERNAL_ERROR, NULL); + range->offset = node->offset; + range->count = node->count; + if (!range->count) { + range->count = ecs_table_count(table); + ecs_assert(range->count != 0, ECS_INTERNAL_ERROR, NULL); } - ecs_vector_t *bitset_columns = match->bitset_columns; - ecs_vector_t *sparse_columns = match->sparse_columns; - if (bitset_columns || sparse_columns) { - bool found = false; - - do { - found = false; - - if (bitset_columns) { - if (bitset_column_next(table, bitset_columns, iter, - &cur) == -1) - { - /* No more enabled components for table */ - iter->bitset_first = 0; - break; - } else { - found = true; - next = node; - } - } - - if (sparse_columns) { - if (sparse_column_next(table, match, - sparse_columns, iter, &cur, found) == -1) - { - /* No more elements in sparse column */ - if (found) { - /* Try again */ - next = node->next; - found = false; - } else { - /* Nothing found */ - iter->bitset_first = 0; - break; - } - } else { - found = true; - next = node; - iter->bitset_first = cur.first + cur.count; - } - } - } while (!found); - - if (!found) { - continue; + if (match->entity_filter.has_filter) { + ent_it->entity_filter = &match->entity_filter; + ent_it->columns = match->columns; + ent_it->range.table = table; + switch(flecs_entity_filter_next(ent_it)) { + case 1: continue; + case -1: next = node; + default: break; } } it->group_id = match->node.group_id; } else { - cur.count = 0; - cur.first = 0; + range->offset = 0; + range->count = 0; } if (only_this) { @@ -48266,7 +50850,7 @@ bool ecs_query_next_instanced( it->references = ecs_vec_first(&match->refs); it->instance_count = 0; - flecs_iter_populate_data(world, it, table, cur.first, cur.count, + flecs_iter_populate_data(world, it, table, range->offset, range->count, it->ptrs, NULL); iter->node = next; @@ -48312,7 +50896,7 @@ bool ecs_query_changed( ecs_check(!(query->flags & EcsQueryIsOrphaned), ECS_INVALID_PARAMETER, NULL); - flecs_process_pending_tables(query->world); + flecs_process_pending_tables(query->filter.world); if (!(query->flags & EcsQueryHasMonitor)) { query->flags |= EcsQueryHasMonitor; @@ -48357,27 +50941,27 @@ bool ecs_query_orphaned( char* ecs_query_str( const ecs_query_t *query) { - return ecs_filter_str(query->world, &query->filter); + return ecs_filter_str(query->filter.world, &query->filter); } int32_t ecs_query_table_count( const ecs_query_t *query) { - ecs_run_aperiodic(query->world, EcsAperiodicEmptyTables); + ecs_run_aperiodic(query->filter.world, EcsAperiodicEmptyTables); return query->cache.tables.count; } int32_t ecs_query_empty_table_count( const ecs_query_t *query) { - ecs_run_aperiodic(query->world, EcsAperiodicEmptyTables); + ecs_run_aperiodic(query->filter.world, EcsAperiodicEmptyTables); return query->cache.empty_tables.count; } int32_t ecs_query_entity_count( const ecs_query_t *query) { - ecs_run_aperiodic(query->world, EcsAperiodicEmptyTables); + ecs_run_aperiodic(query->filter.world, EcsAperiodicEmptyTables); int32_t result = 0; ecs_table_cache_hdr_t *cur, *last = query->cache.tables.last; @@ -48392,11 +50976,15 @@ int32_t ecs_query_entity_count( return result; } -ecs_entity_t ecs_query_entity( - const ecs_query_t *query) -{ - return query->entity; -} +/** + * @file table_graph.c + * @brief Data structure to speed up table transitions. + * + * The table graph is used to speed up finding tables in add/remove operations. + * For example, if component C is added to an entity in table [A, B], the entity + * must be moved to table [A, B, C]. The graph speeds this process up with an + * edge for component C that connects [A, B] to [A, B, C]. + */ /* Marker object used to differentiate a component vs. a tag edge */ @@ -48686,8 +51274,6 @@ void flecs_table_diff_builder_init( ecs_allocator_t *a = &world->allocator; ecs_vec_init_t(a, &builder->added, ecs_id_t, 256); ecs_vec_init_t(a, &builder->removed, ecs_id_t, 256); - ecs_vec_init_t(a, &builder->on_set, ecs_id_t, 256); - ecs_vec_init_t(a, &builder->un_set, ecs_id_t, 256); } void flecs_table_diff_builder_fini( @@ -48697,8 +51283,6 @@ void flecs_table_diff_builder_fini( ecs_allocator_t *a = &world->allocator; ecs_vec_fini_t(a, &builder->added, ecs_id_t); ecs_vec_fini_t(a, &builder->removed, ecs_id_t); - ecs_vec_fini_t(a, &builder->on_set, ecs_id_t); - ecs_vec_fini_t(a, &builder->un_set, ecs_id_t); } void flecs_table_diff_builder_clear( @@ -48706,8 +51290,6 @@ void flecs_table_diff_builder_clear( { ecs_vec_clear(&builder->added); ecs_vec_clear(&builder->removed); - ecs_vec_clear(&builder->on_set); - ecs_vec_clear(&builder->un_set); } static @@ -48732,18 +51314,12 @@ void flecs_table_diff_build( ecs_table_diff_builder_t *builder, ecs_table_diff_t *diff, int32_t added_offset, - int32_t removed_offset, - int32_t on_set_offset, - int32_t un_set_offset) + int32_t removed_offset) { flecs_table_diff_build_type(world, &builder->added, &diff->added, added_offset); flecs_table_diff_build_type(world, &builder->removed, &diff->removed, removed_offset); - flecs_table_diff_build_type(world, &builder->on_set, &diff->on_set, - on_set_offset); - flecs_table_diff_build_type(world, &builder->un_set, &diff->un_set, - un_set_offset); } void flecs_table_diff_build_noalloc( @@ -48754,10 +51330,6 @@ void flecs_table_diff_build_noalloc( .array = builder->added.array, .count = builder->added.count }; diff->removed = (ecs_type_t){ .array = builder->removed.array, .count = builder->removed.count }; - diff->on_set = (ecs_type_t){ - .array = builder->on_set.array, .count = builder->on_set.count }; - diff->un_set = (ecs_type_t){ - .array = builder->un_set.array, .count = builder->un_set.count }; } static @@ -48783,8 +51355,6 @@ void flecs_table_diff_build_append_table( { flecs_table_diff_build_add_type_to_vec(world, &dst->added, &src->added); flecs_table_diff_build_add_type_to_vec(world, &dst->removed, &src->removed); - flecs_table_diff_build_add_type_to_vec(world, &dst->on_set, &src->on_set); - flecs_table_diff_build_add_type_to_vec(world, &dst->un_set, &src->un_set); } static @@ -48794,8 +51364,6 @@ void flecs_table_diff_free( { flecs_wfree_n(world, ecs_id_t, diff->added.count, diff->added.array); flecs_wfree_n(world, ecs_id_t, diff->removed.count, diff->removed.array); - flecs_wfree_n(world, ecs_id_t, diff->on_set.count, diff->on_set.array); - flecs_wfree_n(world, ecs_id_t, diff->un_set.count, diff->un_set.array); flecs_bfree(&world->allocators.table_diff, diff); } @@ -49033,143 +51601,23 @@ ecs_table_t* flecs_table_ensure( return flecs_create_table(world, ©, elem, prev); } -static -void flecs_diff_insert_isa( - ecs_world_t *world, - ecs_table_t *table, - ecs_table_diff_t *base_diff, - ecs_vec_t *append_to, - ecs_type_t *append_from, - ecs_id_t add) -{ - ecs_entity_t base = ecs_pair_second(world, add); - ecs_table_t *base_table = ecs_get_table(world, base); - if (!base_table) { - return; - } - - ecs_type_t base_type = base_table->type; - ecs_table_t *table_wo_base = base_table; - - /* If the table does not have a component from the base, it should - * emit an OnSet event */ - ecs_allocator_t *a = &world->allocator; - ecs_id_t *ids = base_type.array; - int32_t j, i, count = base_type.count; - for (i = 0; i < count; i ++) { - ecs_id_t id = ids[i]; - - if (ECS_HAS_RELATION(id, EcsIsA)) { - /* The base has an IsA relationship. Find table without the base, which - * gives us the list of ids the current base inherits and doesn't - * override. This saves us from having to recursively check for each - * base in the hierarchy whether the component is overridden. */ - table_wo_base = flecs_table_traverse_remove( - world, table_wo_base, &id, base_diff); - - /* Because we removed, the ids are stored in un_set vs. on_set */ - for (j = 0; j < append_from->count; j ++) { - ecs_id_t base_id = append_from->array[j]; - /* We still have to make sure the id isn't overridden by the - * current table */ - if (ecs_search(world, table, base_id, NULL) == -1) { - ecs_vec_append_t(a, append_to, ecs_id_t)[0] = base_id; - } - } - - continue; - } - - /* Identifiers are not inherited */ - if (ECS_HAS_RELATION(id, ecs_id(EcsIdentifier))) { - continue; - } - - if (!ecs_get_typeid(world, id)) { - continue; - } - - if (ecs_search(world, table, id, NULL) == -1) { - ecs_vec_append_t(a, append_to, ecs_id_t)[0] = id; - } - } -} - -static -void flecs_diff_insert_added_isa( - ecs_world_t *world, - ecs_table_t *table, - ecs_table_diff_builder_t *diff, - ecs_id_t id) -{ - ecs_table_diff_t base_diff; - flecs_diff_insert_isa(world, table, &base_diff, &diff->on_set, - &base_diff.un_set, id); -} - -static -void flecs_diff_insert_removed_isa( - ecs_world_t *world, - ecs_table_t *table, - ecs_table_diff_builder_t *diff, - ecs_id_t id) -{ - ecs_table_diff_t base_diff; - flecs_diff_insert_isa(world, table, &base_diff, &diff->un_set, - &base_diff.un_set, id); -} - static void flecs_diff_insert_added( ecs_world_t *world, - ecs_table_t *table, ecs_table_diff_builder_t *diff, ecs_id_t id) { ecs_vec_append_t(&world->allocator, &diff->added, ecs_id_t)[0] = id; - - if (ECS_HAS_RELATION(id, EcsIsA)) { - flecs_diff_insert_added_isa(world, table, diff, id); - } } static void flecs_diff_insert_removed( ecs_world_t *world, - ecs_table_t *table, ecs_table_diff_builder_t *diff, ecs_id_t id) { ecs_allocator_t *a = &world->allocator; ecs_vec_append_t(a, &diff->removed, ecs_id_t)[0] = id; - - if (ECS_HAS_RELATION(id, EcsIsA)) { - /* Removing an IsA relationship also "removes" all components from the - * instance. Any id from base that's not overridden should be UnSet. */ - flecs_diff_insert_removed_isa(world, table, diff, id); - return; - } - - if (table->flags & EcsTableHasIsA) { - if (!ecs_get_typeid(world, id)) { - /* Do nothing if id is not a component */ - return; - } - - /* If next table has a base and component is removed, check if - * the removed component was an override. Removed overrides reexpose the - * base component, thus "changing" the value which requires an OnSet. */ - if (ecs_search_relation(world, table, 0, id, EcsIsA, - EcsUp, 0, 0, 0) != -1) - { - ecs_vec_append_t(a, &diff->on_set, ecs_id_t)[0] = id; - return; - } - } - - if (ecs_get_typeid(world, id) != 0) { - ecs_vec_append_t(a, &diff->un_set, ecs_id_t)[0] = id; - } } static @@ -49190,7 +51638,6 @@ void flecs_compute_table_diff( ecs_table_diff_t *diff = flecs_bcalloc( &world->allocators.table_diff); diff->added.count = 1; - diff->added.array = flecs_wdup_n(world, ecs_id_t, 1, &id); edge->diff = diff; return; @@ -49234,15 +51681,6 @@ void flecs_compute_table_diff( trivial_edge &= (added_count + removed_count) <= 1 && !ecs_id_is_wildcard(id); - if (trivial_edge && removed_count && (node->flags & EcsTableHasIsA)) { - /* If a single component was removed from a table with an IsA, - * relationship it could reexpose an inherited component. If this is - * the case, don't treat it as a trivial edge. */ - if (ecs_search_relation(world, next, 0, id, EcsIsA, EcsUp, 0, 0, 0) != -1) { - trivial_edge = false; - } - } - if (trivial_edge) { /* If edge is trivial there's no need to create a diff element for it. * Store whether the id is a tag or not, so that we can still tell @@ -49256,17 +51694,15 @@ void flecs_compute_table_diff( ecs_table_diff_builder_t *builder = &world->allocators.diff_builder; int32_t added_offset = builder->added.count; int32_t removed_offset = builder->removed.count; - int32_t on_set_offset = builder->on_set.count; - int32_t un_set_offset = builder->un_set.count; for (i_node = 0, i_next = 0; i_node < node_count && i_next < next_count; ) { ecs_id_t id_node = ids_node[i_node]; ecs_id_t id_next = ids_next[i_next]; if (id_next < id_node) { - flecs_diff_insert_added(world, node, builder, id_next); + flecs_diff_insert_added(world, builder, id_next); } else if (id_node < id_next) { - flecs_diff_insert_removed(world, next, builder, id_node); + flecs_diff_insert_removed(world, builder, id_node); } i_node += id_node <= id_next; @@ -49274,16 +51710,15 @@ void flecs_compute_table_diff( } for (; i_next < next_count; i_next ++) { - flecs_diff_insert_added(world, node, builder, ids_next[i_next]); + flecs_diff_insert_added(world, builder, ids_next[i_next]); } for (; i_node < node_count; i_node ++) { - flecs_diff_insert_removed(world, next, builder, ids_node[i_node]); + flecs_diff_insert_removed(world, builder, ids_node[i_node]); } ecs_table_diff_t *diff = flecs_bcalloc(&world->allocators.table_diff); edge->diff = diff; - flecs_table_diff_build(world, builder, diff, - added_offset, removed_offset, on_set_offset, un_set_offset); + flecs_table_diff_build(world, builder, diff, added_offset, removed_offset); ecs_assert(diff->added.count == added_count, ECS_INTERNAL_ERROR, NULL); ecs_assert(diff->removed.count == removed_count, ECS_INTERNAL_ERROR, NULL); @@ -49568,8 +52003,6 @@ void flecs_table_populate_diff( if (diff && diff != &ecs_table_edge_is_component) { *out = *diff; } else { - out->on_set.count = 0; - if (add_ptr) { out->added.array = add_ptr; out->added.count = 1; @@ -49580,15 +52013,8 @@ void flecs_table_populate_diff( if (remove_ptr) { out->removed.array = remove_ptr; out->removed.count = 1; - if (diff == &ecs_table_edge_is_component) { - out->un_set.array = remove_ptr; - out->un_set.count = 1; - } else { - out->un_set.count = 0; - } } else { out->removed.count = 0; - out->un_set.count = 0; } } } @@ -49768,6 +52194,16 @@ ecs_table_t* ecs_table_remove_id( return flecs_table_traverse_remove(world, table, &id, NULL); } +/** + * @file iter.c + * @brief Iterator API. + * + * The iterator API contains functions that apply to all iterators, such as + * resource management, or fetching resources for a matched table. The API also + * contains functions for generic iterators, which make it possible to iterate + * an iterator without needing to know what created the iterator. + */ + #include /* Utility macros to enforce consistency when initializing iterator fields */ @@ -49800,6 +52236,8 @@ void flecs_iter_init( it->priv.cache.used = 0; it->priv.cache.allocated = 0; it->priv.cache.stack_cursor = flecs_stack_get_cursor(stack); + it->priv.entity_iter = flecs_stack_calloc_t( + stack, ecs_entity_filter_iter_t); INIT_CACHE(it, stack, fields, ids, ecs_id_t, it->field_count); INIT_CACHE(it, stack, fields, sources, ecs_entity_t, it->field_count); @@ -49842,6 +52280,7 @@ void ecs_iter_fini( FINI_CACHE(it, variables, ecs_var_t, it->variable_count); FINI_CACHE(it, sizes, ecs_size_t, it->field_count); FINI_CACHE(it, ptrs, void*, it->field_count); + flecs_stack_free_t(it->priv.entity_iter, ecs_entity_filter_iter_t); ecs_stage_t *stage = flecs_stage_from_world(&world); flecs_stack_restore_cursor(&stage->allocators.iter_stack, @@ -50211,17 +52650,6 @@ error: return false; } -int32_t ecs_iter_find_column( - const ecs_iter_t *it, - ecs_entity_t component) -{ - ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, NULL); - ecs_check(it->table != NULL, ECS_INVALID_PARAMETER, NULL); - return ecs_search(it->real_world, it->table, component, 0); -error: - return -1; -} - bool ecs_field_is_set( const ecs_iter_t *it, int32_t index) @@ -50291,51 +52719,6 @@ size_t ecs_field_size( } } -void* ecs_iter_column_w_size( - const ecs_iter_t *it, - size_t size, - int32_t index) -{ - ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, NULL); - ecs_check(it->table != NULL, ECS_INVALID_PARAMETER, NULL); - (void)size; - - ecs_table_t *table = it->table; - int32_t storage_index = ecs_table_type_to_storage_index(table, index); - if (storage_index == -1) { - return NULL; - } - - ecs_type_info_t *ti = table->type_info[storage_index]; - ecs_check(!size || (ecs_size_t)size == ti->size, - ECS_INVALID_PARAMETER, NULL); - (void)ti; - - ecs_vec_t *column = &table->data.columns[storage_index]; - return ecs_vec_get(column, flecs_uto(int32_t, size), it->offset); -error: - return NULL; -} - -size_t ecs_iter_column_size( - const ecs_iter_t *it, - int32_t index) -{ - ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, NULL); - ecs_check(it->table != NULL, ECS_INVALID_PARAMETER, NULL); - - ecs_table_t *table = it->table; - int32_t storage_index = ecs_table_type_to_storage_index(table, index); - if (storage_index == -1) { - return 0; - } - - ecs_type_info_t *ti = table->type_info[storage_index]; - return flecs_ito(size_t, ti->size); -error: - return 0; -} - char* ecs_iter_str( const ecs_iter_t *it) { @@ -50445,6 +52828,25 @@ error: return 0; } +ecs_entity_t ecs_iter_first( + ecs_iter_t *it) +{ + ecs_check(it != NULL, ECS_INVALID_PARAMETER, NULL); + + ECS_BIT_SET(it->flags, EcsIterIsFilter); + ECS_BIT_SET(it->flags, EcsIterIsInstanced); + + ecs_entity_t result = 0; + if (ecs_iter_next(it)) { + result = it->entities[0]; + ecs_iter_fini(it); + } + + return result; +error: + return 0; +} + bool ecs_iter_is_true( ecs_iter_t *it) { @@ -50678,24 +53080,27 @@ error: } static -void offset_iter( +void flecs_offset_iter( ecs_iter_t *it, int32_t offset) { it->entities = &it->entities[offset]; int32_t t, field_count = it->field_count; - for (t = 0; t < field_count; t ++) { - void *ptrs = it->ptrs[t]; - if (!ptrs) { - continue; - } + void **it_ptrs = it->ptrs; + if (it_ptrs) { + for (t = 0; t < field_count; t ++) { + void *ptrs = it_ptrs[t]; + if (!ptrs) { + continue; + } - if (it->sources[t]) { - continue; - } + if (it->sources[t]) { + continue; + } - it->ptrs[t] = ECS_OFFSET(ptrs, offset * it->sizes[t]); + it->ptrs[t] = ECS_OFFSET(ptrs, offset * it->sizes[t]); + } } } @@ -50750,7 +53155,7 @@ bool ecs_page_next_instanced( it->offset += offset; count = it->count -= offset; iter->offset = 0; - offset_iter(it, offset); + flecs_offset_iter(it, offset); } } @@ -50810,18 +53215,15 @@ ecs_iter_t ecs_worker_iter( ecs_check(index >= 0, ECS_INVALID_PARAMETER, NULL); ecs_check(index < count, ECS_INVALID_PARAMETER, NULL); - return (ecs_iter_t){ - .real_world = it->real_world, - .world = it->world, - .priv.iter.worker = { - .index = index, - .count = count - }, - .next = ecs_worker_next, - .chain_it = (ecs_iter_t*)it, - .flags = it->flags & EcsIterIsInstanced + ecs_iter_t result = *it; + result.priv.iter.worker = (ecs_worker_iter_t){ + .index = index, + .count = count }; + result.next = ecs_worker_next; + result.chain_it = (ecs_iter_t*)it; + return result; error: return (ecs_iter_t){ 0 }; } @@ -50880,7 +53282,7 @@ bool ecs_worker_next_instanced( it->instance_count = instances_per_worker; it->frame_offset += first; - offset_iter(it, it->offset + first); + flecs_offset_iter(it, it->offset + first); it->count = per_worker; if (ECS_BIT_IS_SET(it->flags, EcsIterIsInstanced)) { @@ -50912,6 +53314,11 @@ error: return false; } +/** + * @file misc.c + * @brief Miscallaneous functions. + */ + #include #ifndef FLECS_NDEBUG @@ -51112,6 +53519,11 @@ char* ecs_asprintf( distribution. */ +/** + * @file value.c + * @brief Utility functions to work with non-trivial pointers of user types. + */ + int ecs_value_init_w_type_info( const ecs_world_t *world, @@ -51326,6 +53738,24 @@ error: return -1; } +/** + * @file bootstrap.c + * @brief Bootstrap entities in the flecs.core namespace. + * + * Before the ECS storage can be used, core entities such first need to be + * initialized. For example, components in Flecs are stored as entities in the + * ECS storage itself with an EcsComponent component, but before this component + * can be stored, the component itself needs to be initialized. + * + * The bootstrap code uses lower-level APIs to initialize the data structures. + * After bootstrap is completed, regular ECS operations can be used to create + * entities and components. + * + * The bootstrap file also includes several lifecycle hooks and observers for + * builtin features, such as relationship properties and hooks for keeping the + * entity name administration in sync with the (Identifier, Name) component. + */ + /* -- Identifier Component -- */ static ECS_DTOR(EcsIdentifier, ptr, { @@ -51791,7 +54221,8 @@ void flecs_on_parent_change(ecs_iter_t *it) { } /* Get the table column with names */ - EcsIdentifier *names = ecs_iter_column(it, EcsIdentifier, col); + EcsIdentifier *names = ecs_table_get_pair(it->real_world, + table, EcsIdentifier, EcsName, it->offset); ecs_hashmap_t *from_index = 0; if (from_has_name) { @@ -51976,10 +54407,12 @@ void flecs_bootstrap( ecs_ensure(world, EcsSymbol); ecs_ensure(world, EcsAlias); ecs_ensure(world, EcsChildOf); + ecs_ensure(world, EcsFlecs); ecs_ensure(world, EcsFlecsCore); ecs_ensure(world, EcsOnDelete); ecs_ensure(world, EcsPanic); ecs_ensure(world, EcsFlag); + ecs_ensure(world, EcsIsA); ecs_ensure(world, EcsWildcard); ecs_ensure(world, EcsAny); ecs_ensure(world, EcsTag); @@ -52009,11 +54442,8 @@ void flecs_bootstrap( flecs_type_info_init(world, EcsIterable, { 0 }); - /* Cache often used id records on world */ - world->idr_wildcard = flecs_id_record_ensure(world, EcsWildcard); - world->idr_wildcard_wildcard = flecs_id_record_ensure(world, - ecs_pair(EcsWildcard, EcsWildcard)); - world->idr_any = flecs_id_record_ensure(world, EcsAny); + /* Cache often used id records */ + flecs_init_id_records(world); /* Create table for initial components */ ecs_table_t *table = flecs_bootstrap_component_table(world); @@ -52071,6 +54501,13 @@ void flecs_bootstrap( ecs_set_name(world, EcsFlecsInternals, "internals"); ecs_add_id(world, EcsFlecsInternals, EcsModule); + /* Self check */ + ecs_record_t *r = flecs_entities_get(world, EcsFlecs); + ecs_assert(r != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(r->table != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(r->row & EcsEntityObservedAcyclic, ECS_INTERNAL_ERROR, NULL); + (void)r; + /* Initialize builtin entities */ flecs_bootstrap_entity(world, EcsWorld, "World", EcsFlecsCore); flecs_bootstrap_entity(world, EcsWildcard, "*", EcsFlecsCore); @@ -52134,10 +54571,6 @@ void flecs_bootstrap( ecs_add_id(world, EcsChildOf, EcsDontInherit); ecs_add_id(world, ecs_id(EcsIdentifier), EcsDontInherit); - /* The (IsA, *) id record is used often in searches, so cache it */ - world->idr_isa_wildcard = flecs_id_record_ensure(world, - ecs_pair(EcsIsA, EcsWildcard)); - /* Create triggers in internals scope */ ecs_set_scope(world, EcsFlecsInternals); @@ -52204,6 +54637,7 @@ void flecs_bootstrap( }); ecs_observer_init(world, &(ecs_observer_desc_t){ + .entity = ecs_entity(world, { .name = "RegisterDontInherit" }), .filter.terms = {{ .id = EcsDontInherit, .src.flags = EcsSelf }, match_prefab }, .events = {EcsOnAdd}, .callback = flecs_register_dont_inherit @@ -52252,7 +54686,6 @@ void flecs_bootstrap( ecs_add_id(world, EcsWith, EcsAcyclic); /* DontInherit components */ - ecs_add_id(world, EcsDisabled, EcsDontInherit); ecs_add_id(world, EcsPrefab, EcsDontInherit); /* Transitive relationships are always Acyclic */ @@ -52276,6 +54709,11 @@ void flecs_bootstrap( ecs_log_pop(); } +/** + * @file hierarchy.c + * @brief API for entity paths and name lookups. + */ + #include #define ECS_NAME_BUFFER_LENGTH (64) @@ -52360,13 +54798,17 @@ ecs_entity_t flecs_name_to_id( const ecs_world_t *world, const char *name) { - long int result = atol(name); + int64_t result = atoll(name); ecs_assert(result >= 0, ECS_INTERNAL_ERROR, NULL); ecs_entity_t alive = ecs_get_alive(world, (ecs_entity_t)result); if (alive) { return alive; } else { - return (ecs_entity_t)result; + if ((uint32_t)result == (uint64_t)result) { + return (ecs_entity_t)result; + } else { + return 0; + } } } @@ -52564,7 +55006,13 @@ ecs_entity_t ecs_lookup_child( world = ecs_get_world(world); if (flecs_is_string_number(name)) { - return flecs_name_to_id(world, name); + ecs_entity_t result = flecs_name_to_id(world, name); + if (result) { + if (parent && !ecs_has_pair(world, result, EcsChildOf, parent)) { + return 0; + } + return result; + } } ecs_id_t pair = ecs_childof(parent); @@ -52645,10 +55093,6 @@ ecs_entity_t ecs_lookup_path_w_sep( return 0; } - if (!sep) { - sep = "."; - } - ecs_check(world != NULL, ECS_INTERNAL_ERROR, NULL); const ecs_world_t *stage = world; world = ecs_get_world(world); @@ -52682,6 +55126,10 @@ ecs_entity_t ecs_lookup_path_w_sep( parent = flecs_get_parent_from_path(stage, parent, &path, prefix, true); + if (!sep[0]) { + return ecs_lookup_child(world, parent, path); + } + retry: cur = parent; ptr_start = ptr = path; @@ -52836,69 +55284,76 @@ ecs_entity_t ecs_add_path_w_sep( char *name = NULL; - while ((ptr = flecs_path_elem(ptr, sep, &len))) { - if (len < size) { - ecs_os_memcpy(elem, ptr_start, len); - } else { - if (size == ECS_NAME_BUFFER_LENGTH) { - elem = NULL; - } - - elem = ecs_os_realloc(elem, len + 1); - ecs_os_memcpy(elem, ptr_start, len); - size = len + 1; - } - - elem[len] = '\0'; - ptr_start = ptr; - - ecs_entity_t e = ecs_lookup_child(world, cur, elem); - if (!e) { - if (name) { - ecs_os_free(name); - } - - name = ecs_os_strdup(elem); - - /* If this is the last entity in the path, use the provided id */ - bool last_elem = false; - if (!flecs_path_elem(ptr, sep, NULL)) { - e = entity; - last_elem = true; - } - - if (!e) { - if (last_elem) { - ecs_entity_t prev = ecs_set_scope(world, 0); - e = ecs_new(world, 0); - ecs_set_scope(world, prev); - } else { - e = ecs_new_id(world); + if (sep[0]) { + while ((ptr = flecs_path_elem(ptr, sep, &len))) { + if (len < size) { + ecs_os_memcpy(elem, ptr_start, len); + } else { + if (size == ECS_NAME_BUFFER_LENGTH) { + elem = NULL; } + + elem = ecs_os_realloc(elem, len + 1); + ecs_os_memcpy(elem, ptr_start, len); + size = len + 1; } - if (cur) { - ecs_add_pair(world, e, EcsChildOf, cur); - } else if (last_elem && root_path) { - ecs_remove_pair(world, e, EcsChildOf, EcsWildcard); + elem[len] = '\0'; + ptr_start = ptr; + + ecs_entity_t e = ecs_lookup_child(world, cur, elem); + if (!e) { + if (name) { + ecs_os_free(name); + } + + name = ecs_os_strdup(elem); + + /* If this is the last entity in the path, use the provided id */ + bool last_elem = false; + if (!flecs_path_elem(ptr, sep, NULL)) { + e = entity; + last_elem = true; + } + + if (!e) { + if (last_elem) { + ecs_entity_t prev = ecs_set_scope(world, 0); + e = ecs_new(world, 0); + ecs_set_scope(world, prev); + } else { + e = ecs_new_id(world); + } + } + + if (cur) { + ecs_add_pair(world, e, EcsChildOf, cur); + } else if (last_elem && root_path) { + ecs_remove_pair(world, e, EcsChildOf, EcsWildcard); + } + + ecs_set_name(world, e, name); } - ecs_set_name(world, e, name); + cur = e; } - cur = e; - } + if (entity && (cur != entity)) { + ecs_throw(ECS_ALREADY_DEFINED, name); + } - if (entity && (cur != entity)) { - ecs_throw(ECS_ALREADY_DEFINED, name); - } + if (name) { + ecs_os_free(name); + } - if (name) { - ecs_os_free(name); - } - - if (elem != buff) { - ecs_os_free(elem); + if (elem != buff) { + ecs_os_free(elem); + } + } else { + if (parent) { + ecs_add_pair(world, entity, EcsChildOf, parent); + } + ecs_set_name(world, entity, path); } return cur; @@ -52920,6 +55375,24 @@ ecs_entity_t ecs_new_from_path_w_sep( return ecs_add_path_w_sep(world, 0, parent, path, sep, prefix); } +/** + * @file id_record.c + * @brief Index for looking up tables by (component) id. + * + * An id record stores the administration for an in use (component) id, that is + * an id that has been used in tables. + * + * An id record contains a table cache, which stores the list of tables that + * have the id. Each entry in the cache (a table record) stores the first + * occurrence of the id in the table and the number of occurrences of the id in + * the table (in the case of wildcard ids). + * + * Id records are used in lots of scenarios, like uncached queries, or for + * getting a component array/component for an entity. + */ + + +#define ECS_HI_ID_RECORD_ID (4096 * 65536) static ecs_id_record_elem_t* flecs_id_record_elem( @@ -53015,7 +55488,7 @@ void flecs_remove_id_elem( } static -ecs_id_t flecs_id_record_id( +ecs_id_t flecs_id_record_hash( ecs_id_t id) { id = ecs_strip_generation(id); @@ -53038,33 +55511,55 @@ ecs_id_record_t* flecs_id_record_new( ecs_world_t *world, ecs_id_t id) { - ecs_id_record_t *idr = flecs_bcalloc(&world->allocators.id_record); + ecs_id_record_t *idr, **idr_ptr, *idr_t = NULL; + ecs_id_t hash = flecs_id_record_hash(id); + if (hash >= ECS_HI_ID_RECORD_ID) { + idr = flecs_bcalloc(&world->allocators.id_record); + idr_ptr = ecs_map_ensure(&world->id_index_hi, ecs_id_record_t*, hash); + ecs_assert(idr_ptr[0] == NULL, ECS_INTERNAL_ERROR, NULL); + idr_ptr[0] = idr; + } else { + idr = flecs_sparse_ensure_fast(&world->id_index_lo, + ecs_id_record_t, hash); + ecs_os_zeromem(idr); + } + ecs_table_cache_init(world, &idr->cache); idr->id = id; idr->refcount = 1; + idr->reachable.current = -1; bool is_wildcard = ecs_id_is_wildcard(id); - ecs_entity_t rel = 0, obj = 0, role = id & ECS_ID_FLAGS_MASK; + ecs_entity_t rel = 0, tgt = 0, role = id & ECS_ID_FLAGS_MASK; if (ECS_HAS_ID_FLAG(id, PAIR)) { rel = ecs_pair_first(world, id); ecs_assert(rel != 0, ECS_INTERNAL_ERROR, NULL); - /* Relationship object can be 0, as tables without a ChildOf relationship are - * added to the (ChildOf, 0) id record */ - obj = ECS_PAIR_SECOND(id); - if (obj) { - obj = ecs_get_alive(world, obj); - ecs_assert(obj != 0, ECS_INTERNAL_ERROR, NULL); + /* Relationship object can be 0, as tables without a ChildOf + * relationship are added to the (ChildOf, 0) id record */ + tgt = ECS_PAIR_SECOND(id); + if (tgt) { + tgt = ecs_get_alive(world, tgt); + ecs_assert(tgt != 0, ECS_INTERNAL_ERROR, NULL); } /* Check constraints */ - if (obj && !ecs_id_is_wildcard(obj)) { + if (tgt && !ecs_id_is_wildcard(tgt)) { + /* Check if target of relationship satisfies OneOf property */ ecs_entity_t oneof = flecs_get_oneof(world, rel); - ecs_check( !oneof || ecs_has_pair(world, obj, EcsChildOf, oneof), + ecs_check( !oneof || ecs_has_pair(world, tgt, EcsChildOf, oneof), ECS_CONSTRAINT_VIOLATED, NULL); (void)oneof; + + /* Check if we're not trying to inherit from a final target */ + if (rel == EcsIsA) { + bool is_final = ecs_has_id(world, tgt, EcsFinal); + ecs_check(!is_final, ECS_CONSTRAINT_VIOLATED, + "cannot inherit from final entity"); + (void)is_final; + } } if (!is_wildcard && ECS_IS_PAIR(id) && (rel != EcsFlag)) { @@ -53075,10 +55570,12 @@ ecs_id_record_t* flecs_id_record_new( idr->flags = idr_r->flags; /* If pair is not a wildcard, append it to wildcard lists. These - * allow for quickly enumerating all relationships for an object, or all - * objecs for a relationship. */ + * allow for quickly enumerating all relationships for an object, + * or all objecs for a relationship. */ flecs_insert_id_elem(world, idr, ecs_pair(rel, EcsWildcard), idr_r); - flecs_insert_id_elem(world, idr, ecs_pair(EcsWildcard, obj), NULL); + + idr_t = flecs_id_record_ensure(world, ecs_pair(EcsWildcard, tgt)); + flecs_insert_id_elem(world, idr, ecs_pair(EcsWildcard, tgt), idr_t); if (rel == EcsUnion) { idr->flags |= EcsIdUnion; @@ -53094,8 +55591,8 @@ ecs_id_record_t* flecs_id_record_new( if (!is_wildcard && (!role || ECS_IS_PAIR(id))) { if (!(idr->flags & EcsIdTag)) { const ecs_type_info_t *ti = flecs_type_info_get(world, rel); - if (!ti && obj) { - ti = flecs_type_info_get(world, obj); + if (!ti && tgt) { + ti = flecs_type_info_get(world, tgt); } idr->type_info = ti; } @@ -53107,13 +55604,17 @@ ecs_id_record_t* flecs_id_record_new( /* Flag for OnDelete policies */ flecs_add_flag(world, rel, EcsEntityObservedId); - if (obj) { + if (tgt) { /* Flag for OnDeleteTarget policies */ - flecs_add_flag(world, obj, EcsEntityObservedTarget); - if (ecs_has_id(world, rel, EcsAcyclic)) { + flecs_add_flag(world, tgt, EcsEntityObservedTarget); + if (idr->flags & EcsIdAcyclic) { /* Flag used to determine if object should be traversed when * propagating events or with super/subset queries */ - flecs_add_flag(world, obj, EcsEntityObservedAcyclic); + ecs_record_t *r = flecs_add_flag( + world, tgt, EcsEntityObservedAcyclic); + + /* Add reference to (*, tgt) id record to entity record */ + r->idr = idr_t; } } @@ -53182,6 +55683,8 @@ void flecs_id_record_free( ecs_id_t id = idr->id; flecs_id_record_assert_empty(idr); + ecs_assert((world->flags & EcsWorldQuit) || (idr->keep_alive == 0), + ECS_ID_IN_USE, "cannot delete id that is in use"); if (ECS_IS_PAIR(id)) { ecs_entity_t rel = ecs_pair_first(world, id); @@ -53240,13 +55743,18 @@ void flecs_id_record_free( world->info.wildcard_id_count --; } - /* Unregister the id record from the world */ - ecs_map_remove(&world->id_index, flecs_id_record_id(id)); - - /* Free resources */ + /* Unregister the id record from the world & free resources */ ecs_table_cache_fini(&idr->cache); flecs_name_index_free(idr->name_index); - flecs_bfree(&world->allocators.id_record, idr); + ecs_vec_fini_t(&world->allocator, &idr->reachable.ids, ecs_reachable_elem_t); + + ecs_id_t hash = flecs_id_record_hash(id); + if (hash >= ECS_HI_ID_RECORD_ID) { + ecs_map_remove(&world->id_index_hi, hash); + flecs_bfree(&world->allocators.id_record, idr); + } else { + idr->id = 0; /* Tombstone */ + } if (ecs_should_log_1()) { char *id_str = ecs_id_str(world, id); @@ -53259,19 +55767,10 @@ ecs_id_record_t* flecs_id_record_ensure( ecs_world_t *world, ecs_id_t id) { - ecs_poly_assert(world, ecs_world_t); - - ecs_id_record_t **idr_ptr = ecs_map_ensure(&world->id_index, - ecs_id_record_t*, ecs_strip_generation(id)); - ecs_id_record_t *idr = idr_ptr[0]; + ecs_id_record_t *idr = flecs_id_record_get(world, id); if (!idr) { idr = flecs_id_record_new(world, id); - idr_ptr = ecs_map_get(&world->id_index, - ecs_id_record_t*, flecs_id_record_id(id)); - ecs_assert(idr_ptr != NULL, ECS_INTERNAL_ERROR, NULL); - idr_ptr[0] = idr; } - return idr; } @@ -53280,8 +55779,27 @@ ecs_id_record_t* flecs_id_record_get( ecs_id_t id) { ecs_poly_assert(world, ecs_world_t); - return ecs_map_get_ptr(&world->id_index, ecs_id_record_t*, - flecs_id_record_id(id)); + if (id == ecs_pair(EcsIsA, EcsWildcard)) { + return world->idr_isa_wildcard; + } + + ecs_id_t hash = flecs_id_record_hash(id); + ecs_id_record_t *idr = NULL; + if (hash >= ECS_HI_ID_RECORD_ID) { + ecs_id_record_t **idr_ptr = ecs_map_get(&world->id_index_hi, + ecs_id_record_t*, hash); + ecs_assert(!idr_ptr || idr_ptr[0] != NULL, ECS_INTERNAL_ERROR, NULL); + if (idr_ptr) { + idr = idr_ptr[0]; + } + } else { + idr = flecs_sparse_get_any(&world->id_index_lo, ecs_id_record_t, hash); + if (idr && !idr->id) { + idr = NULL; + } + } + + return idr; } ecs_id_record_t* flecs_query_id_record_get( @@ -53290,9 +55808,9 @@ ecs_id_record_t* flecs_query_id_record_get( { ecs_id_record_t *idr = flecs_id_record_get(world, id); if (!idr) { - if (ECS_IS_PAIR(id)) { - idr = flecs_id_record_get(world, - ecs_pair(EcsUnion, ECS_PAIR_FIRST(id))); + ecs_entity_t first = ECS_PAIR_FIRST(id); + if (ECS_IS_PAIR(id) && (first != EcsWildcard)) { + idr = flecs_id_record_get(world, ecs_pair(EcsUnion, first)); } return idr; } @@ -53440,24 +55958,45 @@ const ecs_table_record_t* flecs_id_record_get_table( const ecs_id_record_t *idr, const ecs_table_t *table) { - if (!idr) { - return NULL; - } + ecs_assert(idr != NULL, ECS_INTERNAL_ERROR, NULL); return (ecs_table_record_t*)ecs_table_cache_get(&idr->cache, table); } +void flecs_init_id_records( + ecs_world_t *world) +{ + /* Cache often used id records on world */ + world->idr_wildcard = flecs_id_record_ensure(world, EcsWildcard); + world->idr_wildcard_wildcard = flecs_id_record_ensure(world, + ecs_pair(EcsWildcard, EcsWildcard)); + world->idr_any = flecs_id_record_ensure(world, EcsAny); + world->idr_isa_wildcard = flecs_id_record_ensure(world, + ecs_pair(EcsIsA, EcsWildcard)); +} + void flecs_fini_id_records( ecs_world_t *world) { - ecs_map_iter_t it = ecs_map_iter(&world->id_index); + ecs_map_iter_t it = ecs_map_iter(&world->id_index_hi); ecs_id_record_t *idr; while ((idr = ecs_map_next_ptr(&it, ecs_id_record_t*, NULL))) { flecs_id_record_release(world, idr); } - ecs_assert(ecs_map_count(&world->id_index) == 0, ECS_INTERNAL_ERROR, NULL); + int32_t i, count = flecs_sparse_count(&world->id_index_lo); + for (i = count - 1; i >= 0; i --) { + idr = flecs_sparse_get_dense(&world->id_index_lo, + ecs_id_record_t, i); + if (idr->id) { + flecs_id_record_release(world, idr); + } + } - ecs_map_fini(&world->id_index); + ecs_assert(ecs_map_count(&world->id_index_hi) == 0, + ECS_INTERNAL_ERROR, NULL); + + ecs_map_fini(&world->id_index_hi); + flecs_sparse_fini(&world->id_index_lo); flecs_sparse_free(world->pending_tables); flecs_sparse_free(world->pending_buffer); } diff --git a/code/vendors/flecs/flecs.h b/code/vendors/flecs/flecs.h index 403ed4d..b7de507 100644 --- a/code/vendors/flecs/flecs.h +++ b/code/vendors/flecs/flecs.h @@ -11,31 +11,50 @@ #define FLECS_H /** - * @defgroup options API toggles & constants + * @defgroup c C API + * + * @{ + * @} + */ + +/** + * @defgroup core Core + * @brief Core ECS functionality (entities, storage, queries). + * + * \ingroup c * @{ */ -/* Customizable precision for floating point operations (such as time ops) */ +/** + * @defgroup options API defines + * @brief Defines for customizing compile time features. + * @{ + */ + +/** \def ecs_float_t + * Customizable precision for floating point operations */ #ifndef ecs_float_t #define ecs_float_t float #endif -/* Customizable precision for time scalar values */ +/** \def ecs_ftime_t + * Customizable precision for scalar time values. Change to double precision for + * processes that can run for a long time (e.g. longer than a day). */ #ifndef ecs_ftime_t #define ecs_ftime_t ecs_float_t #endif -/** FLECS_LEGACY +/** \def FLECS_LEGACY * Define when building for C89 */ // #define FLECS_LEGACY -/** FLECS_NO_DEPRECATED_WARNINGS +/** \def FLECS_NO_DEPRECATED_WARNINGS * disables deprecated warnings */ #define FLECS_NO_DEPRECATED_WARNINGS -/** FLECS_ACCURATE_COUNTERS +/** \def FLECS_ACCURATE_COUNTERS * Define to ensure that global counters used for statistics (such as the * allocation counters in the OS API) are accurate in multithreaded * applications, at the cost of increased overhead. @@ -50,7 +69,7 @@ #error "invalid configuration: cannot both define FLECS_DEBUG and NDEBUG" #endif -/** Flecs debugging enables asserts. +/** \def FLECS_DEBUG * Used for input parameter checking and cheap sanity checks. There are lots of * asserts in every part of the code, so this will slow down applications. */ @@ -62,11 +81,10 @@ #endif #endif -/** FLECS_SANITIZE +/** \def FLECS_SANITIZE * Enables expensive checks that can detect issues early. Recommended for * running tests or when debugging issues. This will severely slow down code. */ -// #define FLECS_SANITIZE #ifdef FLECS_SANITIZE #define FLECS_DEBUG /* If sanitized mode is enabled, so is debug mode */ #endif @@ -75,7 +93,7 @@ * test with the FLECS_DEBUG or FLECS_SANITIZE flags enabled. There's a good * chance that this gives you more information about the issue! */ -/** FLECS_SOFT_ASSERT +/** \def FLECS_SOFT_ASSERT * Define to not abort for recoverable errors, like invalid parameters. An error * is still thrown to the console. This is recommended for when running inside a * third party runtime, such as the Unreal editor. @@ -90,15 +108,15 @@ */ // #define FLECS_SOFT_ASSERT -/** FLECS_KEEP_ASSERT +/** \def FLECS_KEEP_ASSERT * By default asserts are disabled in release mode, when either FLECS_NDEBUG or * NDEBUG is defined. Defining FLECS_KEEP_ASSERT ensures that asserts are not * disabled. This define can be combined with FLECS_SOFT_ASSERT. */ // #define FLECS_KEEP_ASSERT -/** Custom builds. - * The following macros let you customize with which addons Flecs is built. +/** \def FLECS_CUSTOM_BUILD + * This macro lets you customize which addons to build flecs with. * Without any addons Flecs is just a minimal ECS storage, but addons add * features such as systems, scheduling and reflection. If an addon is disabled, * it is excluded from the build, so that it consumes no resources. By default @@ -123,32 +141,51 @@ // #define FLECS_CUSTOM_BUILD #ifndef FLECS_CUSTOM_BUILD -// #define FLECS_C /* C API convenience macros, always enabled */ -#define FLECS_CPP /* C++ API */ -#define FLECS_MODULE /* Module support */ -#define FLECS_PARSER /* String parser for queries */ -#define FLECS_PLECS /* ECS data definition format */ -#define FLECS_RULES /* Constraint solver for advanced queries */ -#define FLECS_SNAPSHOT /* Snapshot & restore ECS data */ -#define FLECS_STATS /* Access runtime statistics */ -#define FLECS_MONITOR /* Track runtime statistics periodically */ -#define FLECS_SYSTEM /* System support */ -#define FLECS_PIPELINE /* Pipeline support */ -#define FLECS_TIMER /* Timer support */ -#define FLECS_META /* Reflection support */ -#define FLECS_META_C /* Utilities for populating reflection data */ -#define FLECS_UNITS /* Builtin standard units */ -#define FLECS_EXPR /* Parsing strings to/from component values */ -#define FLECS_JSON /* Parsing JSON to/from component values */ -#define FLECS_DOC /* Document entities & components */ -#define FLECS_COREDOC /* Documentation for core entities & components */ -#define FLECS_LOG /* When enabled ECS provides more detailed logs */ -#define FLECS_APP /* Application addon */ -#define FLECS_OS_API_IMPL /* Default implementation for OS API */ -#define FLECS_HTTP /* Tiny HTTP server for connecting to remote UI */ -#define FLECS_REST /* REST API for querying application data */ +// #define FLECS_C /**< C API convenience macros, always enabled */ +#define FLECS_CPP /**< C++ API */ +#define FLECS_MODULE /**< Module support */ +#define FLECS_PARSER /**< String parser for queries */ +#define FLECS_PLECS /**< ECS data definition format */ +#define FLECS_RULES /**< Constraint solver for advanced queries */ +#define FLECS_SNAPSHOT /**< Snapshot & restore ECS data */ +#define FLECS_STATS /**< Access runtime statistics */ +#define FLECS_MONITOR /**< Track runtime statistics periodically */ +#define FLECS_SYSTEM /**< System support */ +#define FLECS_PIPELINE /**< Pipeline support */ +#define FLECS_TIMER /**< Timer support */ +#define FLECS_META /**< Reflection support */ +#define FLECS_META_C /**< Utilities for populating reflection data */ +#define FLECS_UNITS /**< Builtin standard units */ +#define FLECS_EXPR /**< Parsing strings to/from component values */ +#define FLECS_JSON /**< Parsing JSON to/from component values */ +#define FLECS_DOC /**< Document entities & components */ +#define FLECS_COREDOC /**< Documentation for core entities & components */ +#define FLECS_LOG /**< When enabled ECS provides more detailed logs */ +#define FLECS_APP /**< Application addon */ +#define FLECS_OS_API_IMPL /**< Default implementation for OS API */ +#define FLECS_HTTP /**< Tiny HTTP server for connecting to remote UI */ +#define FLECS_REST /**< REST API for querying application data */ +// #define FLECS_JOURNAL /**< Journaling addon (disabled by default) */ #endif // ifndef FLECS_CUSTOM_BUILD +/** \def ECS_ID_CACHE_SIZE + * Maximum number of components to add/remove in a single operation */ +#ifndef ECS_ID_CACHE_SIZE +#define ECS_ID_CACHE_SIZE (32) +#endif + +/** \def ECS_TERM_DESC_CACHE_SIZE + * Maximum number of terms in desc (larger, as these are temp objects) */ +#define ECS_TERM_DESC_CACHE_SIZE (16) + +/** \def ECS_OBSERVER_DESC_EVENT_COUNT_MAX + * Maximum number of events to set in static array of observer descriptor */ +#define ECS_OBSERVER_DESC_EVENT_COUNT_MAX (8) + +/** \def ECS_VARIABLE_COUNT_MAX + * Maximum number of query variables per query */ +#define ECS_VARIABLE_COUNT_MAX (64) + /** @} */ /** @@ -263,7 +300,14 @@ extern "C" { #define EcsIterEntityOptional (1u << 5u) /* Treat terms with entity subject as optional */ #define EcsIterNoResults (1u << 6u) /* Iterator has no results */ #define EcsIterIgnoreThis (1u << 7u) /* Only evaluate non-this terms */ +#define EcsIterMatchVar (1u << 8u) +//////////////////////////////////////////////////////////////////////////////// +//// Filter flags (used by ecs_filter_t::flags) +//////////////////////////////////////////////////////////////////////////////// + +#define EcsEventTableOnly (1u << 8u) /* Table event (no data, same as iter flags) */ +#define EcsEventNoOnSet (1u << 16u) /* Don't emit OnSet/UnSet for inherited ids */ //////////////////////////////////////////////////////////////////////////////// //// Filter flags (used by ecs_filter_t::flags) @@ -275,7 +319,7 @@ extern "C" { #define EcsFilterMatchDisabled (1u << 4u) /* Does filter match disabled entities */ #define EcsFilterMatchEmptyTables (1u << 5u) /* Does filter return empty tables */ #define EcsFilterMatchAnything (1u << 6u) /* False if filter has no/only Not terms */ -#define EcsFilterIsFilter (1u << 7u) /* When true, data fields won't be populated */ +#define EcsFilterNoData (1u << 7u) /* When true, data fields won't be populated */ #define EcsFilterIsInstanced (1u << 8u) /* Is filter instanced (see ecs_filter_desc_t) */ #define EcsFilterPopulate (1u << 9u) /* Populate data, ignore non-matching fields */ @@ -296,7 +340,7 @@ extern "C" { #define EcsTableHasCopy (1u << 10u) #define EcsTableHasMove (1u << 11u) #define EcsTableHasUnion (1u << 12u) -#define EcsTableHasToggle (1u << 13u) +#define EcsTableHasToggle (1u << 13u) #define EcsTableHasOverrides (1u << 14u) #define EcsTableHasOnAdd (1u << 15u) /* Same values as id flags */ @@ -304,6 +348,8 @@ extern "C" { #define EcsTableHasOnSet (1u << 17u) #define EcsTableHasUnSet (1u << 18u) +#define EcsTableHasObserved (1u << 20u) + #define EcsTableMarkedForDelete (1u << 30u) /* Composite table flags */ @@ -368,10 +414,32 @@ extern "C" { #endif #endif +#if defined(__clang__) +#define ECS_TARGET_CLANG +#endif + #if defined(__GNUC__) #define ECS_TARGET_GNU #endif +/* Map between clang and apple clang versions, as version 13 has a difference in + * the format of __PRETTY_FUNCTION__ which enum reflection depends on. */ +#if defined(__clang__) + #if defined(__APPLE__) + #if __clang_major__ == 13 + #if __clang_minor__ < 1 + #define ECS_CLANG_VERSION 12 + #else + #define ECS_CLANG_VERSION 13 + #endif + #else + #define ECS_CLANG_VERSION __clang_major__ + #endif + #else + #define ECS_CLANG_VERSION __clang_major__ + #endif +#endif + /* Standard library dependencies */ #include #include @@ -675,33 +743,9 @@ typedef int32_t ecs_size_t; #endif - /** * @file vector.h * @brief Vector datastructure. - * - * This is an implementation of a simple vector type. The vector is allocated in - * a single block of memory, with the element count, and allocated number of - * elements encoded in the block. As this vector is used for user-types it has - * been designed to support alignments higher than 8 bytes. This makes the size - * of the vector header variable in size. To reduce the overhead associated with - * retrieving or computing this size, the functions are wrapped in macro calls - * that compute the header size at compile time. - * - * The API provides a number of _t macros, which accept a size and alignment. - * These macros are used when no compile-time type is available. - * - * The vector guarantees contiguous access to its elements. When an element is - * removed from the vector, the last element is copied to the removed element. - * - * The API requires passing in the type of the vector. This type is used to test - * whether the size of the provided type equals the size of the type with which - * the vector was created. In release mode this check is not performed. - * - * When elements are added to the vector, it will automatically resize to the - * next power of two. This can change the pointer of the vector, which is why - * operations that can increase the vector size, accept a double pointer to the - * vector. */ #ifndef FLECS_VECTOR_H @@ -1116,7 +1160,7 @@ public: } void add(T&& value) { - T* elem = static_cast(_ecs_vector_add(&m_vector, ECS_VECTOR_T(T))) + T* elem = static_cast(_ecs_vector_add(&m_vector, ECS_VECTOR_T(T))); *elem = value; } @@ -1163,10 +1207,317 @@ private: #endif +/** + * @file sparse.h + * @brief Sparse set data structure. + */ + +#ifndef FLECS_SPARSE_H +#define FLECS_SPARSE_H + + +#ifdef __cplusplus +extern "C" { +#endif + +/** The number of elements in a single chunk */ +#define FLECS_SPARSE_CHUNK_SIZE (4096) + +typedef struct ecs_sparse_t { + ecs_vector_t *dense; /* Dense array with indices to sparse array. The + * dense array stores both alive and not alive + * sparse indices. The 'count' member keeps + * track of which indices are alive. */ + + ecs_vector_t *chunks; /* Chunks with sparse arrays & data */ + ecs_size_t size; /* Element size */ + int32_t count; /* Number of alive entries */ + uint64_t max_id_local; /* Local max index (if no global is set) */ + uint64_t *max_id; /* Maximum issued sparse index */ + struct ecs_allocator_t *allocator; + struct ecs_block_allocator_t *chunk_allocator; +} ecs_sparse_t; + +/** Initialize sparse set */ +FLECS_DBG_API +void _flecs_sparse_init( + ecs_sparse_t *sparse, + struct ecs_allocator_t *allocator, + struct ecs_block_allocator_t *chunk_allocator, + ecs_size_t elem_size); + +#define flecs_sparse_init(sparse, allocator, chunk_allocator, T)\ + _flecs_sparse_init(sparse, allocator, chunk_allocator, ECS_SIZEOF(T)) + +/** Create new sparse set */ +FLECS_DBG_API +ecs_sparse_t* _flecs_sparse_new( + struct ecs_allocator_t *allocator, + struct ecs_block_allocator_t *chunk_allocator, + ecs_size_t elem_size); + +#define flecs_sparse_new(allocator, chunk_allocator, T)\ + _flecs_sparse_new(allocator, chunk_allocator, ECS_SIZEOF(T)) + +FLECS_DBG_API +void _flecs_sparse_fini( + ecs_sparse_t *sparse); + +#define flecs_sparse_fini(sparse)\ + _flecs_sparse_fini(sparse) + +/** Free sparse set */ +FLECS_DBG_API +void flecs_sparse_free( + ecs_sparse_t *sparse); + +/** Remove all elements from sparse set */ +FLECS_DBG_API +void flecs_sparse_clear( + ecs_sparse_t *sparse); + +/** Set id source. This allows the sparse set to use an external variable for + * issuing and increasing new ids. */ +FLECS_DBG_API +void flecs_sparse_set_id_source( + ecs_sparse_t *sparse, + uint64_t *id_source); + +/** Add element to sparse set, this generates or recycles an id */ +FLECS_DBG_API +void* _flecs_sparse_add( + ecs_sparse_t *sparse, + ecs_size_t elem_size); + +#define flecs_sparse_add(sparse, T)\ + ((T*)_flecs_sparse_add(sparse, ECS_SIZEOF(T))) + +/** Get last issued id. */ +FLECS_DBG_API +uint64_t flecs_sparse_last_id( + const ecs_sparse_t *sparse); + +/** Generate or recycle a new id. */ +FLECS_DBG_API +uint64_t flecs_sparse_new_id( + ecs_sparse_t *sparse); + +/** Generate or recycle new ids in bulk. The returned pointer points directly to + * the internal dense array vector with sparse ids. Operations on the sparse set + * can (and likely will) modify the contents of the buffer. */ +FLECS_DBG_API +const uint64_t* flecs_sparse_new_ids( + ecs_sparse_t *sparse, + int32_t count); + +/** Remove an element */ +FLECS_DBG_API +void flecs_sparse_remove( + ecs_sparse_t *sparse, + uint64_t id); + +/** Fast version of remove, no liveliness checking */ +FLECS_DBG_API +void* _flecs_sparse_remove_fast( + ecs_sparse_t *sparse, + ecs_size_t elem_size, + uint64_t id); + +/** Remove an element, return pointer to the value in the sparse array */ +FLECS_DBG_API +void* _flecs_sparse_remove_get( + ecs_sparse_t *sparse, + ecs_size_t elem_size, + uint64_t id); + +#define flecs_sparse_remove_get(sparse, T, index)\ + ((T*)_flecs_sparse_remove_get(sparse, ECS_SIZEOF(T), index)) + +/** Check whether an id has ever been issued. */ +FLECS_DBG_API +bool flecs_sparse_exists( + const ecs_sparse_t *sparse, + uint64_t id); + +/** Check whether an id has ever been issued and is currently alive. */ +FLECS_DBG_API +bool flecs_sparse_is_valid( + const ecs_sparse_t *sparse, + uint64_t index); + +/** Test if id is alive, which requires the generation count to match. */ +FLECS_DBG_API +bool flecs_sparse_is_alive( + const ecs_sparse_t *sparse, + uint64_t id); + +/** Return identifier with current generation set. */ +FLECS_DBG_API +uint64_t flecs_sparse_get_alive( + const ecs_sparse_t *sparse, + uint64_t id); + +/** Get value from sparse set by dense id. This function is useful in + * combination with flecs_sparse_count for iterating all values in the set. */ +FLECS_DBG_API +void* _flecs_sparse_get_dense( + const ecs_sparse_t *sparse, + ecs_size_t elem_size, + int32_t index); + +#define flecs_sparse_get_dense(sparse, T, index)\ + ((T*)_flecs_sparse_get_dense(sparse, ECS_SIZEOF(T), index)) + + +/** Get the number of alive elements in the sparse set. */ +FLECS_DBG_API +int32_t flecs_sparse_count( + const ecs_sparse_t *sparse); + +/** Get the number of not alive alive elements in the sparse set. */ +FLECS_DBG_API +int32_t flecs_sparse_not_alive_count( + const ecs_sparse_t *sparse); + +/** Return total number of allocated elements in the dense array */ +FLECS_DBG_API +int32_t flecs_sparse_size( + const ecs_sparse_t *sparse); + +/** Get element by (sparse) id. The returned pointer is stable for the duration + * of the sparse set, as it is stored in the sparse array. */ +FLECS_DBG_API +void* _flecs_sparse_get( + const ecs_sparse_t *sparse, + ecs_size_t elem_size, + uint64_t id); + +#define flecs_sparse_get(sparse, T, index)\ + ((T*)_flecs_sparse_get(sparse, ECS_SIZEOF(T), index)) + +/** Like get_sparse, but don't care whether element is alive or not. */ +FLECS_DBG_API +void* _flecs_sparse_get_any( + const ecs_sparse_t *sparse, + ecs_size_t elem_size, + uint64_t id); + +#define flecs_sparse_get_any(sparse, T, index)\ + ((T*)_flecs_sparse_get_any(sparse, ECS_SIZEOF(T), index)) + +/** Get or create element by (sparse) id. */ +FLECS_DBG_API +void* _flecs_sparse_ensure( + ecs_sparse_t *sparse, + ecs_size_t elem_size, + uint64_t id); + +#define flecs_sparse_ensure(sparse, T, index)\ + ((T*)_flecs_sparse_ensure(sparse, ECS_SIZEOF(T), index)) + +/** Fast version of ensure, no liveliness checking */ +FLECS_DBG_API +void* _flecs_sparse_ensure_fast( + ecs_sparse_t *sparse, + ecs_size_t elem_size, + uint64_t id); + +#define flecs_sparse_ensure_fast(sparse, T, index)\ + ((T*)_flecs_sparse_ensure_fast(sparse, ECS_SIZEOF(T), index)) + +/** Set value. */ +FLECS_DBG_API +void* _flecs_sparse_set( + ecs_sparse_t *sparse, + ecs_size_t elem_size, + uint64_t id, + void *value); + +#define flecs_sparse_set(sparse, T, index, value)\ + ((T*)_flecs_sparse_set(sparse, ECS_SIZEOF(T), index, value)) + +/** Get pointer to ids (alive and not alive). Use with count() or size(). */ +FLECS_DBG_API +const uint64_t* flecs_sparse_ids( + const ecs_sparse_t *sparse); + +/** Set size of the dense array. */ +FLECS_DBG_API +void flecs_sparse_set_size( + ecs_sparse_t *sparse, + int32_t elem_count); + +/** Copy sparse set into a new sparse set. */ +FLECS_DBG_API +ecs_sparse_t* flecs_sparse_copy( + const ecs_sparse_t *src); + +/** Restore sparse set into destination sparse set. */ +FLECS_DBG_API +void flecs_sparse_restore( + ecs_sparse_t *dst, + const ecs_sparse_t *src); + +/* Publicly exposed APIs + * The flecs_ functions aren't exposed directly as this can cause some + * optimizers to not consider them for link time optimization. */ + +FLECS_API +ecs_sparse_t* _ecs_sparse_new( + ecs_size_t elem_size); + +#define ecs_sparse_new(T)\ + _ecs_sparse_new(ECS_SIZEOF(T)) + +FLECS_API +void* _ecs_sparse_add( + ecs_sparse_t *sparse, + ecs_size_t elem_size); + +#define ecs_sparse_add(sparse, T)\ + ((T*)_ecs_sparse_add(sparse, ECS_SIZEOF(T))) + +FLECS_API +uint64_t ecs_sparse_last_id( + const ecs_sparse_t *sparse); + +FLECS_API +int32_t ecs_sparse_count( + const ecs_sparse_t *sparse); + +/** Override the generation count for a specific id */ +FLECS_API +void flecs_sparse_set_generation( + ecs_sparse_t *sparse, + uint64_t id); + +FLECS_API +void* _ecs_sparse_get_dense( + const ecs_sparse_t *sparse, + ecs_size_t elem_size, + int32_t index); + +#define ecs_sparse_get_dense(sparse, T, index)\ + ((T*)_ecs_sparse_get_dense(sparse, ECS_SIZEOF(T), index)) + +FLECS_API +void* _ecs_sparse_get( + const ecs_sparse_t *sparse, + ecs_size_t elem_size, + uint64_t id); + +#define ecs_sparse_get(sparse, T, index)\ + ((T*)_ecs_sparse_get(sparse, ECS_SIZEOF(T), index)) + +#ifdef __cplusplus +} +#endif + +#endif + /** * @file block_allocator.h - * @brief Allocator that returns memory objects of the same (chunk) size. - * Multiple elements are stored in a single block. + * @brief Block allocator. */ #ifndef FLECS_BLOCK_ALLOCATOR_H @@ -1193,7 +1544,7 @@ typedef struct ecs_block_allocator_t { int32_t alloc_count; } ecs_block_allocator_t; -FLECS_DBG_API +FLECS_API void flecs_ballocator_init( ecs_block_allocator_t *ba, ecs_size_t size); @@ -1203,38 +1554,38 @@ void flecs_ballocator_init( #define flecs_ballocator_init_n(ba, T, count)\ flecs_ballocator_init(ba, ECS_SIZEOF(T) * count) -FLECS_DBG_API +FLECS_API ecs_block_allocator_t* flecs_ballocator_new( ecs_size_t size); -FLECS_DBG_API +FLECS_API void flecs_ballocator_fini( ecs_block_allocator_t *ba); -FLECS_DBG_API +FLECS_API void flecs_ballocator_free( ecs_block_allocator_t *ba); -FLECS_DBG_API +FLECS_API void* flecs_balloc( ecs_block_allocator_t *allocator); -FLECS_DBG_API +FLECS_API void* flecs_bcalloc( ecs_block_allocator_t *allocator); -FLECS_DBG_API +FLECS_API void flecs_bfree( ecs_block_allocator_t *allocator, void *memory); -FLECS_DBG_API +FLECS_API void* flecs_brealloc( ecs_block_allocator_t *dst, ecs_block_allocator_t *src, void *memory); -FLECS_DBG_API +FLECS_API void* flecs_bdup( ecs_block_allocator_t *ba, void *memory); @@ -1243,28 +1594,7 @@ void* flecs_bdup( /** * @file map.h - * @brief Map datastructure. - * - * Key-value datastructure. The map allows for fast retrieval of a payload for - * a 64-bit key. While it is not as fast as the sparse set, it is better at - * handling randomly distributed values. - * - * Payload is stored in bucket arrays. A bucket is computed from an id by - * using the (bucket_count - 1) as an AND-mask. The number of buckets is always - * a power of 2. Multiple keys will be stored in the same bucket. As a result - * the worst case retrieval performance of the map is O(n), though this is rare. - * On average lookup performance should equal O(1). - * - * The datastructure will automatically grow the number of buckets when the - * ratio between elements and buckets exceeds a certain threshold (LOAD_FACTOR). - * - * Note that while the implementation is a hashmap, it can only compute hashes - * for the provided 64 bit keys. This means that the provided keys must always - * be unique. If the provided keys are hashes themselves, it is the - * responsibility of the user to ensure that collisions are handled. - * - * In debug mode the map verifies that the type provided to the map functions - * matches the one used at creation time. + * @brief Map data structure. */ #ifndef FLECS_MAP_H @@ -1297,7 +1627,7 @@ typedef struct ecs_map_t { int32_t bucket_count; int32_t count; struct ecs_allocator_t *allocator; - ecs_block_allocator_t *entry_allocator; + struct ecs_block_allocator_t *entry_allocator; } ecs_map_t; typedef struct ecs_map_iter_t { @@ -1309,7 +1639,7 @@ typedef struct ecs_map_iter_t { typedef struct ecs_map_params_t { ecs_size_t size; struct ecs_allocator_t *allocator; - ecs_block_allocator_t entry_allocator; + struct ecs_block_allocator_t entry_allocator; int32_t initial_count; } ecs_map_params_t; @@ -1542,27 +1872,39 @@ FLECS_DBG_API extern int64_t ecs_stack_allocator_alloc_count; FLECS_DBG_API extern int64_t ecs_stack_allocator_free_count; typedef struct ecs_allocator_t { - struct ecs_map_t sizes; /* */ + ecs_block_allocator_t chunks; + struct ecs_sparse_t sizes; /* */ } ecs_allocator_t; +FLECS_API void flecs_allocator_init( ecs_allocator_t *a); +FLECS_API void flecs_allocator_fini( ecs_allocator_t *a); +FLECS_API ecs_block_allocator_t* flecs_allocator_get( ecs_allocator_t *a, ecs_size_t size); +FLECS_API char* flecs_strdup( ecs_allocator_t *a, const char* str); +FLECS_API void flecs_strfree( ecs_allocator_t *a, char* str); +FLECS_API +void* flecs_dup( + ecs_allocator_t *a, + ecs_size_t size, + const void *src); + #define flecs_allocator(obj) (&obj->allocators.dyn) #define flecs_alloc(a, size) flecs_balloc(flecs_allocator_get(a, size)) @@ -1584,7 +1926,6 @@ void flecs_strfree( #define flecs_realloc_n(a, T, count_dst, count_src, ptr)\ flecs_realloc(a, ECS_SIZEOF(T) * (count_dst), ECS_SIZEOF(T) * (count_src), ptr) -#define flecs_dup(a, size, ptr) flecs_bdup(flecs_allocator_get(a, size), ptr) #define flecs_dup_n(a, T, count, ptr) flecs_dup(a, ECS_SIZEOF(T) * (count), ptr) #endif @@ -1592,17 +1933,6 @@ void flecs_strfree( /** * @file strbuf.h * @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. */ #ifndef FLECS_STRBUF_H_ @@ -1729,6 +2059,14 @@ bool ecs_strbuf_appendstr_zerocpy( ecs_strbuf_t *buffer, char *str); +/* Append string to buffer, transfer ownership to buffer. + * Returns false when max is reached, true when there is still space */ +FLECS_API +bool ecs_strbuf_appendstr_zerocpyn( + ecs_strbuf_t *buffer, + char *str, + int32_t n); + /* Append string to buffer, do not free/modify string. * Returns false when max is reached, true when there is still space */ FLECS_API @@ -1736,6 +2074,14 @@ bool ecs_strbuf_appendstr_zerocpy_const( ecs_strbuf_t *buffer, const char *str); +/* Append string to buffer, transfer ownership to buffer. + * Returns false when max is reached, true when there is still space */ +FLECS_API +bool ecs_strbuf_appendstr_zerocpyn_const( + ecs_strbuf_t *buffer, + const char *str, + int32_t n); + /* Append n characters to buffer. * Returns false when max is reached, true when there is still space */ FLECS_API @@ -1834,6 +2180,14 @@ int32_t ecs_strbuf_written( #ifndef FLECS_OS_API_H #define FLECS_OS_API_H +/** + * @defgroup c_os_api OS API + * @brief Interface for providing OS specific functionality. + * + * \ingroup c + * @{ + */ + #include #include @@ -1867,6 +2221,9 @@ typedef uintptr_t ecs_os_mutex_t; typedef uintptr_t ecs_os_dl_t; typedef uintptr_t ecs_os_sock_t; +/* 64 bit thread id */ +typedef uint64_t ecs_os_thread_id_t; + /* Generic function pointer type */ typedef void (*ecs_os_proc_t)(void); @@ -1914,6 +2271,9 @@ typedef void* (*ecs_os_api_thread_join_t)( ecs_os_thread_t thread); +typedef +ecs_os_thread_id_t (*ecs_os_api_thread_self_t)(void); + /* Atomic increment / decrement */ typedef int32_t (*ecs_os_api_ainc_t)( @@ -2029,6 +2389,7 @@ typedef struct ecs_os_api_t { /* Threads */ ecs_os_api_thread_new_t thread_new_; ecs_os_api_thread_join_t thread_join_; + ecs_os_api_thread_self_t thread_self_; /* Atomic incremenet / decrement */ ecs_os_api_ainc_t ainc_; @@ -2212,6 +2573,7 @@ void ecs_os_set_api_defaults(void); /* Threads */ #define ecs_os_thread_new(callback, param) ecs_os_api.thread_new_(callback, param) #define ecs_os_thread_join(thread) ecs_os_api.thread_join_(thread) +#define ecs_os_thread_self() ecs_os_api.thread_self_() /* Atomic increment / decrement */ #define ecs_os_ainc(value) ecs_os_api.ainc_(value) @@ -2334,6 +2696,8 @@ bool ecs_os_has_modules(void); } #endif +/** @} */ + #endif @@ -2342,33 +2706,16 @@ extern "C" { #endif /** - * @defgroup api_types Basic API types + * @defgroup api_types API types + * @brief Public API types. * @{ */ -/** A poly object. - * A poly (short for polymorph) object is an object that has a variable list of - * capabilities, determined by a mixin table. This is the current list of types - * in the flecs API that can be used as an ecs_poly_t: - * - * - ecs_world_t - * - ecs_stage_t - * - ecs_query_t - * - ecs_filter_t - * - ecs_rule_t - * - (more to come) - * - * Functions that accept an ecs_poly_t argument can accept objects of these - * types. If the object does not have the requested mixin the API will throw an - * assert. - * - * The poly/mixin framework enables partially overlapping features to be - * implemented once, and enables objects of different types to interact with - * each other depending on what mixins they have, rather than their type - * (in some ways it's like a mini-ECS). Additionally, each poly object has a - * header that enables the API to do sanity checking on the input arguments. +/** + * @defgroup core_types Core API Types + * @brief Types for core API objects. + * @{ */ -typedef void ecs_poly_t; /** An id. Ids are the things that can be added to an entity. An id can be an * entity or pair, and can have optional id flags. */ @@ -2419,36 +2766,51 @@ typedef struct ecs_type_hooks_t ecs_type_hooks_t; /** Type information */ typedef struct ecs_type_info_t ecs_type_info_t; -/* Mixins */ +/* Internal index that stores tables tables for a (component) id */ +typedef struct ecs_id_record_t ecs_id_record_t; + +/* Internal table storage record */ +typedef struct ecs_table_record_t ecs_table_record_t; + +/** A poly object. + * A poly (short for polymorph) object is an object that has a variable list of + * capabilities, determined by a mixin table. This is the current list of types + * in the flecs API that can be used as an ecs_poly_t: + * + * - ecs_world_t + * - ecs_stage_t + * - ecs_query_t + * - ecs_filter_t + * - ecs_rule_t + * - (more to come) + * + * Functions that accept an ecs_poly_t argument can accept objects of these + * types. If the object does not have the requested mixin the API will throw an + * assert. + * + * The poly/mixin framework enables partially overlapping features to be + * implemented once, and enables objects of different types to interact with + * each other depending on what mixins they have, rather than their type + * (in some ways it's like a mini-ECS). Additionally, each poly object has a + * header that enables the API to do sanity checking on the input arguments. + */ +typedef void ecs_poly_t; + +/** Type that stores poly mixins */ typedef struct ecs_mixins_t ecs_mixins_t; -/** @} */ - - -/** - * @defgroup constants API constants - * @{ - */ - -/* Maximum number of components to add/remove in a single operation */ -#ifndef ECS_ID_CACHE_SIZE -#define ECS_ID_CACHE_SIZE (32) -#endif - -/* Maximum number of terms in desc (larger, as these are temp objects) */ -#define ECS_TERM_DESC_CACHE_SIZE (16) - -/* Maximum number of events to set in static array of observer descriptor */ -#define ECS_OBSERVER_DESC_EVENT_COUNT_MAX (8) - -/* Maximum number of query variables per query */ -#define ECS_VARIABLE_COUNT_MAX (64) +/** Header for ecs_poly_t objects. */ +typedef struct ecs_header_t { + int32_t magic; /* Magic number verifying it's a flecs object */ + int32_t type; /* Magic number indicating which type of flecs object */ + ecs_mixins_t *mixins; /* Table with offsets to (optional) mixins */ +} ecs_header_t; /** @} */ - /** - * @defgroup function_types Function Prototypes + * @defgroup function_types Function types. + * @brief Function callback types. * @{ */ @@ -2595,104 +2957,100 @@ typedef void (*ecs_poly_dtor_t)( /** @} */ /** - * @defgroup mixin Public mixin types. + * @defgroup mixins Poly mixin types. + * @brief Mixin types for poly mechanism. * @{ */ -/** Header for ecs_poly_t objects. */ -typedef struct ecs_header_t { - int32_t magic; /* Magic number verifying it's a flecs object */ - int32_t type; /* Magic number indicating which type of flecs object */ - ecs_mixins_t *mixins; /* Table with offsets to (optional) mixins */ -} ecs_header_t; - /** Iterable mixin. * Allows its container to be iterated. */ typedef struct ecs_iterable_t { - ecs_iter_init_action_t init; /* Callback that creates iterator. */ + ecs_iter_init_action_t init; /**< Callback that creates iterator. */ } ecs_iterable_t; /** @} */ /** - * @defgroup query_types Types used to describe queries. + * @defgroup query_types Query descriptor types. + * @brief Types used to describe queries. * @{ */ /** Specify read/write access for term */ typedef enum ecs_inout_kind_t { - EcsInOutDefault, /* InOut for regular terms, In for shared terms */ - EcsInOutNone, /* Term is neither read nor written */ - EcsInOut, /* Term is both read and written */ - EcsIn, /* Term is only read */ - EcsOut, /* Term is only written */ + EcsInOutDefault, /**< InOut for regular terms, In for shared terms */ + EcsInOutNone, /**< Term is neither read nor written */ + EcsInOut, /**< Term is both read and written */ + EcsIn, /**< Term is only read */ + EcsOut, /**< Term is only written */ } ecs_inout_kind_t; /** Specify operator for term */ typedef enum ecs_oper_kind_t { - EcsAnd, /* The term must match */ - EcsOr, /* One of the terms in an or chain must match */ - EcsNot, /* The term must not match */ - EcsOptional, /* The term may match */ - EcsAndFrom, /* Term must match all components from term id */ - EcsOrFrom, /* Term must match at least one component from term id */ - EcsNotFrom /* Term must match none of the components from term id */ + EcsAnd, /**< The term must match */ + EcsOr, /**< One of the terms in an or chain must match */ + EcsNot, /**< The term must not match */ + EcsOptional, /**< The term may match */ + EcsAndFrom, /**< Term must match all components from term id */ + EcsOrFrom, /**< Term must match at least one component from term id */ + EcsNotFrom, /**< Term must match none of the components from term id */ } ecs_oper_kind_t; -/** Term flags */ -#define EcsSelf (1u << 1) /* Match on self */ -#define EcsUp (1u << 2) /* Match by traversing upwards */ -#define EcsDown (1u << 3) /* Match by traversing downwards (derived, cannot be set) */ -#define EcsCascade (1u << 4) /* Sort results breadth first */ -#define EcsParent (1u << 5) /* Short for up(ChildOf) */ -#define EcsIsVariable (1u << 6) /* Term id is a variable */ -#define EcsIsEntity (1u << 7) /* Term id is an entity */ -#define EcsFilter (1u << 8) /* Prevent observer from triggering on term */ +#define EcsSelf (1u << 1) /**< Match on self */ +#define EcsUp (1u << 2) /**< Match by traversing upwards */ +#define EcsDown (1u << 3) /**< Match by traversing downwards (derived, cannot be set) */ +#define EcsTraverseAll (1u << 4) /**< Match all entities encountered through traversal */ +#define EcsCascade (1u << 5) /**< Sort results breadth first */ +#define EcsParent (1u << 6) /**< Short for up(ChildOf) */ +#define EcsIsVariable (1u << 7) /**< Term id is a variable */ +#define EcsIsEntity (1u << 8) /**< Term id is an entity */ +#define EcsFilter (1u << 9) /**< Prevent observer from triggering on term */ -#define EcsTraverseFlags (EcsUp|EcsDown|EcsSelf|EcsCascade|EcsParent) +#define EcsTraverseFlags (EcsUp|EcsDown|EcsTraverseAll|EcsSelf|EcsCascade|EcsParent) /** Type that describes a single identifier in a term */ typedef struct ecs_term_id_t { - ecs_entity_t id; /* Entity id. If left to 0 and flags does not + ecs_entity_t id; /**< Entity id. If left to 0 and flags does not * specify whether id is an entity or a variable * the id will be initialized to EcsThis. * To explicitly set the id to 0, leave the id * member to 0 and set EcsIsEntity in flags. */ - char *name; /* Name. This can be either the variable name + char *name; /**< Name. This can be either the variable name * (when the EcsIsVariable flag is set) or an * entity name. Entity names are used to * initialize the id member during term * finalization and will be freed when term.move * is set to true. */ - ecs_entity_t trav; /* Relationship to traverse when looking for the + ecs_entity_t trav; /**< Relationship to traverse when looking for the * component. The relationship must have * the Acyclic property. Default is IsA. */ - ecs_flags32_t flags; /* Term flags */ + ecs_flags32_t flags; /**< Term flags */ } ecs_term_id_t; /** Type that describes a term (single element in a query) */ struct ecs_term_t { - ecs_id_t id; /* Component id to be matched by term. Can be + ecs_id_t id; /**< Component id to be matched by term. Can be * set directly, or will be populated from the * first/second members, which provide more * flexibility. */ - ecs_term_id_t src; /* Source of term */ - ecs_term_id_t first; /* Component or first element of pair */ - ecs_term_id_t second; /* Second element of pair */ + ecs_term_id_t src; /**< Source of term */ + ecs_term_id_t first; /**< Component or first element of pair */ + ecs_term_id_t second; /**< Second element of pair */ - ecs_inout_kind_t inout; /* Access to contents matched by term */ - ecs_oper_kind_t oper; /* Operator of term */ + ecs_inout_kind_t inout; /**< Access to contents matched by term */ + ecs_oper_kind_t oper; /**< Operator of term */ - ecs_id_t id_flags; /* Id flags of term id */ - char *name; /* Name of term */ + ecs_id_t id_flags; /**< Id flags of term id */ + char *name; /**< Name of term */ - int32_t field_index; /* Index of field for term in iterator */ + int32_t field_index; /**< Index of field for term in iterator */ + ecs_id_record_t *idr; /**< Cached pointer to internal index */ - bool move; /* Used by internals */ + bool move; /**< Used by internals */ }; /** Use this variable to initialize user-allocated filter object */ @@ -2702,117 +3060,123 @@ FLECS_API extern ecs_filter_t ECS_FILTER_INIT; struct ecs_filter_t { ecs_header_t hdr; - ecs_term_t *terms; /* Array containing terms for filter */ - int32_t term_count; /* Number of elements in terms array */ - int32_t field_count; /* Number of fields in iterator for filter */ + ecs_term_t *terms; /**< Array containing terms for filter */ + int32_t term_count; /**< Number of elements in terms array */ + int32_t field_count; /**< Number of fields in iterator for filter */ - bool owned; /* Is filter object owned by filter */ - bool terms_owned; /* Is terms array owned by filter */ + bool owned; /**< Is filter object owned by filter */ + bool terms_owned; /**< Is terms array owned by filter */ - ecs_flags32_t flags; /* Filter flags */ + ecs_flags32_t flags; /**< Filter flags */ - char *name; /* Name of filter (optional) */ - char *variable_names[1]; /* Array with variable names */ + char *variable_names[1]; /**< Array with variable names */ - ecs_iterable_t iterable; /* Iterable mixin */ + /* Mixins */ + ecs_entity_t entity; /**< Entity associated with filter (optional) */ + ecs_world_t *world; + ecs_iterable_t iterable; /**< Iterable mixin */ + ecs_poly_dtor_t dtor; /**< Dtor mixin */ }; /* An observer reacts to events matching a filter */ struct ecs_observer_t { ecs_header_t hdr; - ecs_filter_t filter; + ecs_filter_t filter; /**< Query for observer */ /* Observer events */ ecs_entity_t events[ECS_OBSERVER_DESC_EVENT_COUNT_MAX]; int32_t event_count; - ecs_iter_action_t callback; /* See ecs_observer_desc_t::callback */ - ecs_run_action_t run; /* See ecs_observer_desc_t::run */ + ecs_iter_action_t callback; /**< See ecs_observer_desc_t::callback */ + ecs_run_action_t run; /**< See ecs_observer_desc_t::run */ - void *ctx; /* Callback context */ - void *binding_ctx; /* Binding context (for language bindings) */ + void *ctx; /**< Callback context */ + void *binding_ctx; /**< Binding context (for language bindings) */ - ecs_ctx_free_t ctx_free; /* Callback to free ctx */ - ecs_ctx_free_t binding_ctx_free; /* Callback to free binding_ctx */ + ecs_ctx_free_t ctx_free; /**< Callback to free ctx */ + ecs_ctx_free_t binding_ctx_free; /**< Callback to free binding_ctx */ - ecs_observable_t *observable; /* Observable for observer */ + ecs_observable_t *observable; /**< Observable for observer */ - int32_t *last_event_id; /* Last handled event id */ + int32_t *last_event_id; /**< Last handled event id */ - ecs_id_t register_id; /* Id observer is registered with (single term observers only) */ - int32_t term_index; /* Index of the term in parent observer (single term observers only) */ + ecs_id_t register_id; /**< Id observer is registered with (single term observers only) */ + int32_t term_index; /**< Index of the term in parent observer (single term observers only) */ - bool is_monitor; /* If true, the observer only triggers when the + bool is_monitor; /**< If true, the observer only triggers when the * filter did not match with the entity before * the event happened. */ - bool is_multi; /* If true, the observer triggers on more than one term */ + bool is_multi; /**< If true, the observer triggers on more than one term */ /* Mixins */ - ecs_world_t *world; - ecs_entity_t entity; ecs_poly_dtor_t dtor; }; -/** Type that contains component lifecycle callbacks. */ -struct ecs_type_hooks_t { - ecs_xtor_t ctor; /* ctor */ - ecs_xtor_t dtor; /* dtor */ - ecs_copy_t copy; /* copy assignment */ - ecs_move_t move; /* move assignment */ +/** @} */ - /* Ctor + copy */ +/** Type that contains component lifecycle callbacks. + * + * \ingroup components + */ +struct ecs_type_hooks_t { + ecs_xtor_t ctor; /**< ctor */ + ecs_xtor_t dtor; /**< dtor */ + ecs_copy_t copy; /**< copy assignment */ + ecs_move_t move; /**< move assignment */ + + /** Ctor + copy */ ecs_copy_t copy_ctor; - /* Ctor + move */ + /** Ctor + move */ ecs_move_t move_ctor; - /* Ctor + move + dtor (or move_ctor + dtor). + /** Ctor + move + dtor (or move_ctor + dtor). * This combination is typically used when a component is moved from one * location to a new location, like when it is moved to a new table. If * not set explicitly it will be derived from other callbacks. */ ecs_move_t ctor_move_dtor; - /* Move + dtor. + /** Move + dtor. * This combination is typically used when a component is moved from one * location to an existing location, like what happens during a remove. If * not set explicitly it will be derived from other callbacks. */ ecs_move_t move_dtor; - /* Callback that is invoked when an instance of a component is added. This + /** Callback that is invoked when an instance of a component is added. This * callback is invoked before triggers are invoked. */ ecs_iter_action_t on_add; - /* Callback that is invoked when an instance of the component is set. This + /** Callback that is invoked when an instance of the component is set. This * callback is invoked before triggers are invoked, and enable the component * to respond to changes on itself before others can. */ ecs_iter_action_t on_set; - /* Callback that is invoked when an instance of the component is removed. + /** Callback that is invoked when an instance of the component is removed. * This callback is invoked after the triggers are invoked, and before the * destructor is invoked. */ ecs_iter_action_t on_remove; - void *ctx; /* User defined context */ - void *binding_ctx; /* Language binding context */ + void *ctx; /**< User defined context */ + void *binding_ctx; /**< Language binding context */ - ecs_ctx_free_t ctx_free; /* Callback to free ctx */ - ecs_ctx_free_t binding_ctx_free; /* Callback to free binding_ctx */ + ecs_ctx_free_t ctx_free; /**< Callback to free ctx */ + ecs_ctx_free_t binding_ctx_free; /**< Callback to free binding_ctx */ }; -/** Type that contains component information (passed to ctors/dtors/...) */ +/** Type that contains component information (passed to ctors/dtors/...) + * + * \ingroup components + */ struct ecs_type_info_t { - ecs_size_t size; /* Size of type */ - ecs_size_t alignment; /* Alignment of type */ - ecs_type_hooks_t hooks; /* Type hooks */ - ecs_entity_t component; /* Handle to component (do not set) */ - const char *name; /* Type name. */ + ecs_size_t size; /**< Size of type */ + ecs_size_t alignment; /**< Alignment of type */ + ecs_type_hooks_t hooks; /**< Type hooks */ + ecs_entity_t component; /**< Handle to component (do not set) */ + const char *name; /**< Type name. */ }; -/** @} */ - - /** * @file api_types.h * @brief Supporting types for the public API. @@ -2844,21 +3208,12 @@ typedef struct ecs_record_t ecs_record_t; /** Table data */ typedef struct ecs_data_t ecs_data_t; -/* Sparse set */ -typedef struct ecs_sparse_t ecs_sparse_t; - /* Switch list */ typedef struct ecs_switch_t ecs_switch_t; -/* Internal structure to lookup tables for a (component) id */ -typedef struct ecs_id_record_t ecs_id_record_t; - /* Cached query table data */ typedef struct ecs_query_table_node_t ecs_query_table_node_t; -/* Internal table storage record */ -struct ecs_table_record_t; - /* Allocator type */ struct ecs_allocator_t; @@ -2867,14 +3222,29 @@ struct ecs_allocator_t; //////////////////////////////////////////////////////////////////////////////// /** Mixin for emitting events to triggers/observers */ +/** All observers for a specific event */ +typedef struct ecs_event_record_t { + struct ecs_event_id_record_t *any; + struct ecs_event_id_record_t *wildcard; + struct ecs_event_id_record_t *wildcard_pair; + ecs_map_t event_ids; /* map */ + ecs_entity_t event; +} ecs_event_record_t; + struct ecs_observable_t { + ecs_event_record_t on_add; + ecs_event_record_t on_remove; + ecs_event_record_t on_set; + ecs_event_record_t un_set; + ecs_event_record_t on_wildcard; ecs_sparse_t *events; /* sparse */ }; /** Record for entity index */ struct ecs_record_t { - ecs_table_t *table; /* Identifies a type (and table) in world */ - uint32_t row; /* Table row of the entity */ + ecs_id_record_t *idr; /* Id record to (*, entity) for target entities */ + ecs_table_t *table; /* Identifies a type (and table) in world */ + uint32_t row; /* Table row of the entity */ }; /** Range in table */ @@ -2989,15 +3359,6 @@ typedef struct ecs_snapshot_iter_t { int32_t index; } ecs_snapshot_iter_t; -/** Type used for iterating ecs_sparse_t */ -typedef struct ecs_sparse_iter_t { - ecs_sparse_t *sparse; - const uint64_t *ids; - ecs_size_t size; - int32_t i; - int32_t count; -} ecs_sparse_iter_t; - /** Rule-iterator specific data */ typedef struct ecs_rule_iter_t { const ecs_rule_t *rule; @@ -3045,6 +3406,7 @@ typedef struct ecs_iter_private_t { ecs_worker_iter_t worker; } iter; /* Iterator specific data */ + void *entity_iter; /* Filter applied after matching a table */ ecs_iter_cache_t cache; /* Inline arrays to reduce allocations */ } ecs_iter_private_t; @@ -3187,7 +3549,7 @@ char* ecs_vasprintf( va_list args); /* Create allocated string from format */ -FLECS_DBG_API +FLECS_API char* ecs_asprintf( const char *fmt, ...); @@ -3239,6 +3601,7 @@ typedef struct ecs_vec_t { #endif } ecs_vec_t; +FLECS_API ecs_vec_t* ecs_vec_init( ecs_allocator_t *allocator, ecs_vec_t *vec, @@ -3248,6 +3611,7 @@ ecs_vec_t* ecs_vec_init( #define ecs_vec_init_t(allocator, vec, T, elem_count) \ ecs_vec_init(allocator, vec, ECS_SIZEOF(T), elem_count) +FLECS_API void ecs_vec_fini( ecs_allocator_t *allocator, ecs_vec_t *vec, @@ -3256,9 +3620,20 @@ void ecs_vec_fini( #define ecs_vec_fini_t(allocator, vec, T) \ ecs_vec_fini(allocator, vec, ECS_SIZEOF(T)) +FLECS_API +ecs_vec_t* ecs_vec_reset( + ecs_allocator_t *allocator, + ecs_vec_t *vec, + ecs_size_t size); + +#define ecs_vec_reset_t(allocator, vec, T) \ + ecs_vec_reset(allocator, vec, ECS_SIZEOF(T)) + +FLECS_API void ecs_vec_clear( ecs_vec_t *vec); +FLECS_API void* ecs_vec_append( ecs_allocator_t *allocator, ecs_vec_t *vec, @@ -3267,6 +3642,7 @@ void* ecs_vec_append( #define ecs_vec_append_t(allocator, vec, T) \ ECS_CAST(T*, ecs_vec_append(allocator, vec, ECS_SIZEOF(T))) +FLECS_API void ecs_vec_remove( ecs_vec_t *vec, ecs_size_t size, @@ -3275,9 +3651,11 @@ void ecs_vec_remove( #define ecs_vec_remove_t(vec, T, elem) \ ecs_vec_remove(vec, ECS_SIZEOF(T), elem) +FLECS_API void ecs_vec_remove_last( ecs_vec_t *vec); +FLECS_API ecs_vec_t ecs_vec_copy( ecs_allocator_t *allocator, ecs_vec_t *vec, @@ -3286,6 +3664,7 @@ ecs_vec_t ecs_vec_copy( #define ecs_vec_copy_t(allocator, vec, T) \ ecs_vec_copy(allocator, vec, ECS_SIZEOF(T)) +FLECS_API void ecs_vec_reclaim( ecs_allocator_t *allocator, ecs_vec_t *vec, @@ -3294,6 +3673,7 @@ void ecs_vec_reclaim( #define ecs_vec_reclaim_t(allocator, vec, T) \ ecs_vec_reclaim(allocator, vec, ECS_SIZEOF(T)) +FLECS_API void ecs_vec_set_size( ecs_allocator_t *allocator, ecs_vec_t *vec, @@ -3303,6 +3683,7 @@ void ecs_vec_set_size( #define ecs_vec_set_size_t(allocator, vec, T, elem_count) \ ecs_vec_set_size(allocator, vec, ECS_SIZEOF(T), elem_count) +FLECS_API void ecs_vec_set_count( ecs_allocator_t *allocator, ecs_vec_t *vec, @@ -3312,6 +3693,7 @@ void ecs_vec_set_count( #define ecs_vec_set_count_t(allocator, vec, T, elem_count) \ ecs_vec_set_count(allocator, vec, ECS_SIZEOF(T), elem_count) +FLECS_API void* ecs_vec_grow( ecs_allocator_t *allocator, ecs_vec_t *vec, @@ -3321,12 +3703,15 @@ void* ecs_vec_grow( #define ecs_vec_grow_t(allocator, vec, T, elem_count) \ ecs_vec_grow(allocator, vec, ECS_SIZEOF(T), elem_count) +FLECS_API int32_t ecs_vec_count( const ecs_vec_t *vec); +FLECS_API int32_t ecs_vec_size( const ecs_vec_t *vec); +FLECS_API void* ecs_vec_get( const ecs_vec_t *vec, ecs_size_t size, @@ -3335,12 +3720,14 @@ void* ecs_vec_get( #define ecs_vec_get_t(vec, T, index) \ ECS_CAST(T*, ecs_vec_get(vec, ECS_SIZEOF(T), index)) +FLECS_API void* ecs_vec_first( const ecs_vec_t *vec); #define ecs_vec_first_t(vec, T) \ ECS_CAST(T*, ecs_vec_first(vec)) +FLECS_API void* ecs_vec_last( const ecs_vec_t *vec, ecs_size_t size); @@ -3350,345 +3737,9 @@ void* ecs_vec_last( #endif -/** - * @file sparse.h - * @brief Sparse set datastructure. - * - * This is an implementation of a paged sparse set that stores the payload in - * the sparse array. - * - * A sparse set has a dense and a sparse array. The sparse array is directly - * indexed by a 64 bit identifier. The sparse element is linked with a dense - * element, which allows for liveliness checking. The liveliness check itself - * can be performed by doing (psuedo code): - * dense[sparse[sparse_id].dense] == sparse_id - * - * To ensure that the sparse array doesn't have to grow to a large size when - * using large sparse_id's, the sparse set uses paging. This cuts up the array - * into several pages of 4096 elements. When an element is set, the sparse set - * ensures that the corresponding page is created. The page associated with an - * id is determined by shifting a bit 12 bits to the right. - * - * The sparse set keeps track of a generation count per id, which is increased - * each time an id is deleted. The generation is encoded in the returned id. - * - * This sparse set implementation stores payload in the sparse array, which is - * not typical. The reason for this is to guarantee that (in combination with - * paging) the returned payload pointers are stable. This allows for various - * optimizations in the parts of the framework that uses the sparse set. - * - * The sparse set has been designed so that new ids can be generated in bulk, in - * an O(1) operation. The way this works is that once a dense-sparse pair is - * created, it is never unpaired. Instead it is moved to the end of the dense - * array, and the sparse set stores an additional count to keep track of the - * last alive id in the sparse set. To generate new ids in bulk, the sparse set - * only needs to increase this count by the number of requested ids. - */ - -#ifndef FLECS_SPARSE_H -#define FLECS_SPARSE_H - - -#ifdef __cplusplus -extern "C" { -#endif - -/** The number of elements in a single chunk */ -#define FLECS_SPARSE_CHUNK_SIZE (4096) - -struct ecs_sparse_t { - ecs_vector_t *dense; /* Dense array with indices to sparse array. The - * dense array stores both alive and not alive - * sparse indices. The 'count' member keeps - * track of which indices are alive. */ - - ecs_vector_t *chunks; /* Chunks with sparse arrays & data */ - ecs_size_t size; /* Element size */ - int32_t count; /* Number of alive entries */ - uint64_t max_id_local; /* Local max index (if no global is set) */ - uint64_t *max_id; /* Maximum issued sparse index */ - struct ecs_allocator_t *allocator; - ecs_block_allocator_t *chunk_allocator; -}; - -/** Initialize sparse set */ -FLECS_DBG_API -void _flecs_sparse_init( - ecs_sparse_t *sparse, - struct ecs_allocator_t *allocator, - ecs_block_allocator_t *chunk_allocator, - ecs_size_t elem_size); - -#define flecs_sparse_init(sparse, allocator, chunk_allocator, T)\ - _flecs_sparse_init(sparse, allocator, chunk_allocator, ECS_SIZEOF(T)) - -/** Create new sparse set */ -FLECS_DBG_API -ecs_sparse_t* _flecs_sparse_new( - struct ecs_allocator_t *allocator, - ecs_block_allocator_t *chunk_allocator, - ecs_size_t elem_size); - -#define flecs_sparse_new(allocator, chunk_allocator, T)\ - _flecs_sparse_new(allocator, chunk_allocator, ECS_SIZEOF(T)) - -FLECS_DBG_API -void _flecs_sparse_fini( - ecs_sparse_t *sparse); - -#define flecs_sparse_fini(sparse)\ - _flecs_sparse_fini(sparse) - -/** Free sparse set */ -FLECS_DBG_API -void flecs_sparse_free( - ecs_sparse_t *sparse); - -/** Remove all elements from sparse set */ -FLECS_DBG_API -void flecs_sparse_clear( - ecs_sparse_t *sparse); - -/** Set id source. This allows the sparse set to use an external variable for - * issuing and increasing new ids. */ -FLECS_DBG_API -void flecs_sparse_set_id_source( - ecs_sparse_t *sparse, - uint64_t *id_source); - -/** Add element to sparse set, this generates or recycles an id */ -FLECS_DBG_API -void* _flecs_sparse_add( - ecs_sparse_t *sparse, - ecs_size_t elem_size); - -#define flecs_sparse_add(sparse, T)\ - ((T*)_flecs_sparse_add(sparse, ECS_SIZEOF(T))) - -/** Get last issued id. */ -FLECS_DBG_API -uint64_t flecs_sparse_last_id( - const ecs_sparse_t *sparse); - -/** Generate or recycle a new id. */ -FLECS_DBG_API -uint64_t flecs_sparse_new_id( - ecs_sparse_t *sparse); - -/** Generate or recycle new ids in bulk. The returned pointer points directly to - * the internal dense array vector with sparse ids. Operations on the sparse set - * can (and likely will) modify the contents of the buffer. */ -FLECS_DBG_API -const uint64_t* flecs_sparse_new_ids( - ecs_sparse_t *sparse, - int32_t count); - -/** Remove an element */ -FLECS_DBG_API -void flecs_sparse_remove( - ecs_sparse_t *sparse, - uint64_t id); - -/** Remove an element, return pointer to the value in the sparse array */ -FLECS_DBG_API -void* _flecs_sparse_remove_get( - ecs_sparse_t *sparse, - ecs_size_t elem_size, - uint64_t id); - -#define flecs_sparse_remove_get(sparse, T, index)\ - ((T*)_flecs_sparse_remove_get(sparse, ECS_SIZEOF(T), index)) - -/** Check whether an id has ever been issued. */ -FLECS_DBG_API -bool flecs_sparse_exists( - const ecs_sparse_t *sparse, - uint64_t id); - -/** Test if id is alive, which requires the generation count to match. */ -FLECS_DBG_API -bool flecs_sparse_is_alive( - const ecs_sparse_t *sparse, - uint64_t id); - -/** Return identifier with current generation set. */ -FLECS_DBG_API -uint64_t flecs_sparse_get_alive( - const ecs_sparse_t *sparse, - uint64_t id); - -/** Get value from sparse set by dense id. This function is useful in - * combination with flecs_sparse_count for iterating all values in the set. */ -FLECS_DBG_API -void* _flecs_sparse_get_dense( - const ecs_sparse_t *sparse, - ecs_size_t elem_size, - int32_t index); - -#define flecs_sparse_get_dense(sparse, T, index)\ - ((T*)_flecs_sparse_get_dense(sparse, ECS_SIZEOF(T), index)) - -/** Get the number of alive elements in the sparse set. */ -FLECS_DBG_API -int32_t flecs_sparse_count( - const ecs_sparse_t *sparse); - -/** Get the number of not alive alive elements in the sparse set. */ -FLECS_DBG_API -int32_t flecs_sparse_not_alive_count( - const ecs_sparse_t *sparse); - -/** Return total number of allocated elements in the dense array */ -FLECS_DBG_API -int32_t flecs_sparse_size( - const ecs_sparse_t *sparse); - -/** Get element by (sparse) id. The returned pointer is stable for the duration - * of the sparse set, as it is stored in the sparse array. */ -FLECS_DBG_API -void* _flecs_sparse_get( - const ecs_sparse_t *sparse, - ecs_size_t elem_size, - uint64_t id); - -#define flecs_sparse_get(sparse, T, index)\ - ((T*)_flecs_sparse_get(sparse, ECS_SIZEOF(T), index)) - -/** Like get_sparse, but don't care whether element is alive or not. */ -FLECS_DBG_API -void* _flecs_sparse_get_any( - const ecs_sparse_t *sparse, - ecs_size_t elem_size, - uint64_t id); - -#define flecs_sparse_get_any(sparse, T, index)\ - ((T*)_flecs_sparse_get_any(sparse, ECS_SIZEOF(T), index)) - -/** Get or create element by (sparse) id. */ -FLECS_DBG_API -void* _flecs_sparse_ensure( - ecs_sparse_t *sparse, - ecs_size_t elem_size, - uint64_t id); - -#define flecs_sparse_ensure(sparse, T, index)\ - ((T*)_flecs_sparse_ensure(sparse, ECS_SIZEOF(T), index)) - -/** Set value. */ -FLECS_DBG_API -void* _flecs_sparse_set( - ecs_sparse_t *sparse, - ecs_size_t elem_size, - uint64_t id, - void *value); - -#define flecs_sparse_set(sparse, T, index, value)\ - ((T*)_flecs_sparse_set(sparse, ECS_SIZEOF(T), index, value)) - -/** Get pointer to ids (alive and not alive). Use with count() or size(). */ -FLECS_DBG_API -const uint64_t* flecs_sparse_ids( - const ecs_sparse_t *sparse); - -/** Set size of the dense array. */ -FLECS_DBG_API -void flecs_sparse_set_size( - ecs_sparse_t *sparse, - int32_t elem_count); - -/** Copy sparse set into a new sparse set. */ -FLECS_DBG_API -ecs_sparse_t* flecs_sparse_copy( - const ecs_sparse_t *src); - -/** Restore sparse set into destination sparse set. */ -FLECS_DBG_API -void flecs_sparse_restore( - ecs_sparse_t *dst, - const ecs_sparse_t *src); - -FLECS_DBG_API -ecs_sparse_iter_t _flecs_sparse_iter( - ecs_sparse_t *sparse, - ecs_size_t elem_size); - -#define flecs_sparse_iter(sparse, T)\ - _flecs_sparse_iter(sparse, ECS_SIZEOF(T)) - -#ifndef FLECS_LEGACY -#define flecs_sparse_each(sparse, T, var, ...)\ - {\ - int var##_i, var##_count = ecs_sparse_count(sparse);\ - for (var##_i = 0; var##_i < var##_count; var##_i ++) {\ - T* var = ecs_sparse_get_dense(sparse, T, var##_i);\ - __VA_ARGS__\ - }\ - } -#endif - -/* Publicly exposed APIs - * The flecs_ functions aren't exposed directly as this can cause some - * optimizers to not consider them for link time optimization. */ - -FLECS_API -ecs_sparse_t* _ecs_sparse_new( - ecs_size_t elem_size); - -#define ecs_sparse_new(T)\ - _ecs_sparse_new(ECS_SIZEOF(T)) - -FLECS_API -void* _ecs_sparse_add( - ecs_sparse_t *sparse, - ecs_size_t elem_size); - -#define ecs_sparse_add(sparse, T)\ - ((T*)_ecs_sparse_add(sparse, ECS_SIZEOF(T))) - -FLECS_API -uint64_t ecs_sparse_last_id( - const ecs_sparse_t *sparse); - -FLECS_API -int32_t ecs_sparse_count( - const ecs_sparse_t *sparse); - -/** Override the generation count for a specific id */ -FLECS_API -void flecs_sparse_set_generation( - ecs_sparse_t *sparse, - uint64_t id); - -FLECS_API -void* _ecs_sparse_get_dense( - const ecs_sparse_t *sparse, - ecs_size_t elem_size, - int32_t index); - -#define ecs_sparse_get_dense(sparse, T, index)\ - ((T*)_ecs_sparse_get_dense(sparse, ECS_SIZEOF(T), index)) - -FLECS_API -void* _ecs_sparse_get( - const ecs_sparse_t *sparse, - ecs_size_t elem_size, - uint64_t id); - -#define ecs_sparse_get(sparse, T, index)\ - ((T*)_ecs_sparse_get(sparse, ECS_SIZEOF(T), index)) - -#ifdef __cplusplus -} -#endif - -#endif - /** * @file hashmap.h - * @brief Hashmap datastructure. - * - * Datastructure that computes a hash to store & retrieve values. Similar to - * ecs_map_t, but allows for arbitrary keytypes. + * @brief Hashmap data structure. */ #ifndef FLECS_HASHMAP_H @@ -3834,197 +3885,210 @@ void* _flecs_hashmap_next( #endif -/** - * @defgroup desc_types Types used for creating API constructs - * @{ - */ - -/** Used with ecs_entity_init */ +/** Used with ecs_entity_init + * + * \ingroup entities + */ typedef struct ecs_entity_desc_t { int32_t _canary; - ecs_entity_t id; /* Set to modify existing entity (optional) */ + ecs_entity_t id; /**< Set to modify existing entity (optional) */ - const char *name; /* Name of the entity. If no entity is provided, an - * entity with this name will be looked up first. When - * an entity is provided, the name will be verified - * with the existing entity. */ + const char *name; /**< Name of the entity. If no entity is provided, an + * entity with this name will be looked up first. When + * an entity is provided, the name will be verified + * with the existing entity. */ - const char *sep; /* Optional custom separator for hierarchical names */ - const char *root_sep; /* Optional, used for identifiers relative to root */ + const char *sep; /**< Optional custom separator for hierarchical names. + * Leave to NULL for default ('.') separator. Set to + * an empty string to prevent tokenization of name. */ - const char *symbol; /* Optional entity symbol. A symbol is an unscoped - * identifier that can be used to lookup an entity. The - * primary use case for this is to associate the entity - * with a language identifier, such as a type or - * function name, where these identifiers differ from - * the name they are registered with in flecs. For - * example, C type "EcsPosition" might be registered - * as "flecs.components.transform.Position", with the - * symbol set to "EcsPosition". */ + const char *root_sep; /**< Optional, used for identifiers relative to root */ - bool use_low_id; /* When set to true, a low id (typically reserved for - * components) will be used to create the entity, if - * no id is specified. */ + const char *symbol; /**< Optional entity symbol. A symbol is an unscoped + * identifier that can be used to lookup an entity. The + * primary use case for this is to associate the entity + * with a language identifier, such as a type or + * function name, where these identifiers differ from + * the name they are registered with in flecs. For + * example, C type "EcsPosition" might be registered + * as "flecs.components.transform.Position", with the + * symbol set to "EcsPosition". */ - /* Array of ids to add to the new or existing entity. */ + bool use_low_id; /**< When set to true, a low id (typically reserved for + * components) will be used to create the entity, if + * no id is specified. */ + + /** Array of ids to add to the new or existing entity. */ ecs_id_t add[ECS_ID_CACHE_SIZE]; - /* String expression with components to add */ + /** String expression with components to add */ const char *add_expr; } ecs_entity_desc_t; -/** Used with ecs_bulk_init */ +/** Used with ecs_bulk_init + * + * \ingroup entities + */ typedef struct ecs_bulk_desc_t { int32_t _canary; - ecs_entity_t *entities; /* Entities to bulk insert. Entity ids provided by - * the application application must be empty (cannot - * have components). If no entity ids are provided, the - * operation will create 'count' new entities. */ + ecs_entity_t *entities; /**< Entities to bulk insert. Entity ids provided by + * the application application must be empty (cannot + * have components). If no entity ids are provided, the + * operation will create 'count' new entities. */ - int32_t count; /* Number of entities to create/populate */ + int32_t count; /**< Number of entities to create/populate */ - ecs_id_t ids[ECS_ID_CACHE_SIZE]; /* Ids to create the entities with */ + ecs_id_t ids[ECS_ID_CACHE_SIZE]; /**< Ids to create the entities with */ - void **data; /* Array with component data to insert. Each element in + void **data; /**< Array with component data to insert. Each element in * the array must correspond with an element in the ids * array. If an element in the ids array is a tag, the * data array must contain a NULL. An element may be * set to NULL for a component, in which case the * component will not be set by the operation. */ - ecs_table_t *table; /* Table to insert the entities into. Should not be set + ecs_table_t *table; /**< Table to insert the entities into. Should not be set * at the same time as ids. When 'table' is set at the * same time as 'data', the elements in the data array * must correspond with the ids in the table's type. */ } ecs_bulk_desc_t; - -/** Used with ecs_component_init. */ +/** Used with ecs_component_init. + * + * \ingroup components + */ typedef struct ecs_component_desc_t { int32_t _canary; - /* Existing entity to associate with observer (optional) */ + /** Existing entity to associate with observer (optional) */ ecs_entity_t entity; - ecs_type_info_t type; /* Parameters for type (size, hooks, ...) */ + /** Parameters for type (size, hooks, ...) */ + ecs_type_info_t type; } ecs_component_desc_t; -/** Used with ecs_filter_init. */ +/** Used with ecs_filter_init. + * + * \ingroup filters + */ typedef struct ecs_filter_desc_t { int32_t _canary; - /* Terms of the filter. If a filter has more terms than + /** Terms of the filter. If a filter has more terms than * ECS_TERM_DESC_CACHE_SIZE use terms_buffer */ ecs_term_t terms[ECS_TERM_DESC_CACHE_SIZE]; - /* For filters with lots of terms an outside array can be provided. */ + /** For filters with lots of terms an outside array can be provided. */ ecs_term_t *terms_buffer; + + /** Number of terms in array provided in terms_buffer. */ int32_t terms_buffer_count; - /* External storage to prevent allocation of the filter object */ + /** External storage to prevent allocation of the filter object */ ecs_filter_t *storage; - /* When true, terms returned by an iterator may either contain 1 or N + /** When true, terms returned by an iterator may either contain 1 or N * elements, where terms with N elements are owned, and terms with 1 element * are shared, for example from a parent or base entity. When false, the * iterator will at most return 1 element when the result contains both * owned and shared terms. */ bool instanced; - /* Flags for advanced usage */ + /** Flags for advanced usage */ ecs_flags32_t flags; - /* Filter expression. Should not be set at the same time as terms array */ + /** Filter expression. Should not be set at the same time as terms array */ const char *expr; - /* Optional name of filter, used for debugging. If a filter is created for - * a system, the provided name should match the system name. */ - const char *name; + /** Entity associated with query (optional) */ + ecs_entity_t entity; } ecs_filter_desc_t; - -/** Used with ecs_query_init. */ +/** Used with ecs_query_init. + * + * \ingroup queries + */ typedef struct ecs_query_desc_t { int32_t _canary; - /* Filter for the query */ + /** Filter for the query */ ecs_filter_desc_t filter; - /* Component to be used by order_by */ + /** Component to be used by order_by */ ecs_entity_t order_by_component; - /* Callback used for ordering query results. If order_by_id is 0, the + /** Callback used for ordering query results. If order_by_id is 0, the * pointer provided to the callback will be NULL. If the callback is not * set, results will not be ordered. */ ecs_order_by_action_t order_by; - /* Callback used for ordering query results. Same as order_by, + /** Callback used for ordering query results. Same as order_by, * but more efficient. */ ecs_sort_table_action_t sort_table; - /* Id to be used by group_by. This id is passed to the group_by function and + /** Id to be used by group_by. This id is passed to the group_by function and * can be used identify the part of an entity type that should be used for * grouping. */ ecs_id_t group_by_id; - /* Callback used for grouping results. If the callback is not set, results + /** Callback used for grouping results. If the callback is not set, results * will not be grouped. When set, this callback will be used to calculate a * "rank" for each entity (table) based on its components. This rank is then * used to sort entities (tables), so that entities (tables) of the same * rank are "grouped" together when iterated. */ ecs_group_by_action_t group_by; - /* Callback that is invoked when a new group is created. The return value of + /** Callback that is invoked when a new group is created. The return value of * the callback is stored as context for a group. */ ecs_group_create_action_t on_group_create; - /* Callback that is invoked when an existing group is deleted. The return + /** Callback that is invoked when an existing group is deleted. The return * value of the on_group_create callback is passed as context parameter. */ ecs_group_delete_action_t on_group_delete; - /* Context to pass to group_by */ + /** Context to pass to group_by */ void *group_by_ctx; - /* Function to free group_by_ctx */ + /** Function to free group_by_ctx */ ecs_ctx_free_t group_by_ctx_free; - /* If set, the query will be created as a subquery. A subquery matches at + /** If set, the query will be created as a subquery. A subquery matches at * most a subset of its parent query. Subqueries do not directly receive * (table) notifications from the world. Instead parent queries forward * results to subqueries. This can improve matching performance, as fewer * queries need to be matched with new tables. * Subqueries can be nested. */ ecs_query_t *parent; - - /* Entity associated with query (optional) */ - ecs_entity_t entity; } ecs_query_desc_t; -/** Used with ecs_observer_init. */ +/** Used with ecs_observer_init. + * + * \ingroup observers + */ typedef struct ecs_observer_desc_t { int32_t _canary; - /* Existing entity to associate with observer (optional) */ + /** Existing entity to associate with observer (optional) */ ecs_entity_t entity; - /* Filter for observer */ + /** Filter for observer */ ecs_filter_desc_t filter; - /* Events to observe (OnAdd, OnRemove, OnSet, UnSet) */ + /** Events to observe (OnAdd, OnRemove, OnSet, UnSet) */ ecs_entity_t events[ECS_OBSERVER_DESC_EVENT_COUNT_MAX]; - /* When observer is created, generate events from existing data. For example, + /** When observer is created, generate events from existing data. For example, * EcsOnAdd Position would match all existing instances of Position. * This is only supported for events that are iterable (see EcsIterable) */ bool yield_existing; - /* Callback to invoke on an event, invoked when the observer matches. */ + /** Callback to invoke on an event, invoked when the observer matches. */ ecs_iter_action_t callback; - /* Callback invoked on an event. When left to NULL the default runner + /** Callback invoked on an event. When left to NULL the default runner * is used which matches the event with the observer's filter, and calls * 'callback' when it matches. * A reason to override the run function is to improve performance, if there @@ -4032,65 +4096,32 @@ typedef struct ecs_observer_desc_t { * the general purpose query matcher. */ ecs_run_action_t run; - /* User context to pass to callback */ + /** User context to pass to callback */ void *ctx; - /* Context to be used for language bindings */ + /** Context to be used for language bindings */ void *binding_ctx; - /* Callback to free ctx */ + /** Callback to free ctx */ ecs_ctx_free_t ctx_free; - /* Callback to free binding_ctx */ + /** Callback to free binding_ctx */ ecs_ctx_free_t binding_ctx_free; - /* Observable with which to register the observer */ + /** Observable with which to register the observer */ ecs_poly_t *observable; - /* Optional shared last event id for multiple observers. Ensures only one + /** Optional shared last event id for multiple observers. Ensures only one * of the observers with the shared id gets triggered for an event */ int32_t *last_event_id; - /* Used for internal purposes */ + /** Used for internal purposes */ int32_t term_index; } ecs_observer_desc_t; -/** @} */ - - /** - * @defgroup builtin_components Builtin components - * @{ - */ - -/** A (string) identifier. Used as pair with EcsName and EcsSymbol tags */ -typedef struct EcsIdentifier { - char *value; - ecs_size_t length; - uint64_t hash; - uint64_t index_hash; /* Hash of existing record in current index */ - ecs_hashmap_t *index; /* Current index */ -} EcsIdentifier; - -/** Component information. */ -typedef struct EcsComponent { - ecs_size_t size; /* Component size */ - ecs_size_t alignment; /* Component alignment */ -} EcsComponent; - -/** Component for storing a poly object */ -typedef struct EcsPoly { - ecs_poly_t *poly; -} EcsPoly; - -/** Component for iterable entities */ -typedef ecs_iterable_t EcsIterable; - -/** @} */ - - -/** - * @defgroup misc_types Miscalleneous types + * @defgroup misc_types Miscellaneous types + * @brief Types used to create entities, observers, queries and more. * @{ */ @@ -4102,64 +4133,64 @@ typedef struct ecs_value_t { /** Type that contains information about the world. */ typedef struct ecs_world_info_t { - ecs_entity_t last_component_id; /* Last issued component entity id */ - ecs_entity_t last_id; /* Last issued entity id */ - ecs_entity_t min_id; /* First allowed entity id */ - ecs_entity_t max_id; /* Last allowed entity id */ + ecs_entity_t last_component_id; /**< Last issued component entity id */ + ecs_entity_t last_id; /**< Last issued entity id */ + ecs_entity_t min_id; /**< First allowed entity id */ + ecs_entity_t max_id; /**< Last allowed entity id */ - ecs_ftime_t delta_time_raw; /* Raw delta time (no time scaling) */ - ecs_ftime_t delta_time; /* Time passed to or computed by ecs_progress */ - ecs_ftime_t time_scale; /* Time scale applied to delta_time */ - ecs_ftime_t target_fps; /* Target fps */ - ecs_ftime_t frame_time_total; /* Total time spent processing a frame */ - ecs_ftime_t system_time_total; /* Total time spent in systems */ - ecs_ftime_t emit_time_total; /* Total time spent notifying observers */ - ecs_ftime_t merge_time_total; /* Total time spent in merges */ - ecs_ftime_t world_time_total; /* Time elapsed in simulation */ - ecs_ftime_t world_time_total_raw; /* Time elapsed in simulation (no scaling) */ - ecs_ftime_t rematch_time_total; /* Time spent on query rematching */ + ecs_ftime_t delta_time_raw; /**< Raw delta time (no time scaling) */ + ecs_ftime_t delta_time; /**< Time passed to or computed by ecs_progress */ + ecs_ftime_t time_scale; /**< Time scale applied to delta_time */ + ecs_ftime_t target_fps; /**< Target fps */ + ecs_ftime_t frame_time_total; /**< Total time spent processing a frame */ + ecs_ftime_t system_time_total; /**< Total time spent in systems */ + ecs_ftime_t emit_time_total; /**< Total time spent notifying observers */ + ecs_ftime_t merge_time_total; /**< Total time spent in merges */ + ecs_ftime_t world_time_total; /**< Time elapsed in simulation */ + ecs_ftime_t world_time_total_raw; /**< Time elapsed in simulation (no scaling) */ + ecs_ftime_t rematch_time_total; /**< Time spent on query rematching */ - int64_t frame_count_total; /* Total number of frames */ - int64_t merge_count_total; /* Total number of merges */ - int64_t rematch_count_total; /* Total number of rematches */ + int64_t frame_count_total; /**< Total number of frames */ + int64_t merge_count_total; /**< Total number of merges */ + int64_t rematch_count_total; /**< Total number of rematches */ - int64_t id_create_total; /* Total number of times a new id was created */ - int64_t id_delete_total; /* Total number of times an id was deleted */ - int64_t table_create_total; /* Total number of times a table was created */ - int64_t table_delete_total; /* Total number of times a table was deleted */ - int64_t pipeline_build_count_total; /* Total number of pipeline builds */ - int64_t systems_ran_frame; /* Total number of systems ran in last frame */ - int64_t observers_ran_frame; /* Total number of times observer was invoked */ + int64_t id_create_total; /**< Total number of times a new id was created */ + int64_t id_delete_total; /**< Total number of times an id was deleted */ + int64_t table_create_total; /**< Total number of times a table was created */ + int64_t table_delete_total; /**< Total number of times a table was deleted */ + int64_t pipeline_build_count_total; /**< Total number of pipeline builds */ + int64_t systems_ran_frame; /**< Total number of systems ran in last frame */ + int64_t observers_ran_frame; /**< Total number of times observer was invoked */ - int32_t id_count; /* Number of ids in the world (excluding wildcards) */ - int32_t tag_id_count; /* Number of tag (no data) ids in the world */ - int32_t component_id_count; /* Number of component (data) ids in the world */ - int32_t pair_id_count; /* Number of pair ids in the world */ - int32_t wildcard_id_count; /* Number of wildcard ids */ + int32_t id_count; /**< Number of ids in the world (excluding wildcards) */ + int32_t tag_id_count; /**< Number of tag (no data) ids in the world */ + int32_t component_id_count; /**< Number of component (data) ids in the world */ + int32_t pair_id_count; /**< Number of pair ids in the world */ + int32_t wildcard_id_count; /**< Number of wildcard ids */ - int32_t table_count; /* Number of tables */ - int32_t tag_table_count; /* Number of tag-only tables */ - int32_t trivial_table_count; /* Number of tables with trivial components (no lifecycle callbacks) */ - int32_t empty_table_count; /* Number of tables without entities */ - int32_t table_record_count; /* Total number of table records (entries in table caches) */ - int32_t table_storage_count; /* Total number of table storages */ + int32_t table_count; /**< Number of tables */ + int32_t tag_table_count; /**< Number of tag-only tables */ + int32_t trivial_table_count; /**< Number of tables with trivial components (no lifecycle callbacks) */ + int32_t empty_table_count; /**< Number of tables without entities */ + int32_t table_record_count; /**< Total number of table records (entries in table caches) */ + int32_t table_storage_count; /**< Total number of table storages */ /* -- Command counts -- */ struct { - int64_t add_count; /* add commands processed */ - int64_t remove_count; /* remove commands processed */ - int64_t delete_count; /* delete commands processed */ - int64_t clear_count; /* clear commands processed */ - int64_t set_count; /* set commands processed */ - int64_t get_mut_count; /* get_mut/emplace commands processed */ - int64_t modified_count; /* modified commands processed */ - int64_t other_count; /* other commands processed */ - int64_t discard_count; /* commands discarded, happens when entity is no longer alive when running the command */ - int64_t batched_entity_count; /* entities for which commands were batched */ - int64_t batched_command_count; /* commands batched */ + int64_t add_count; /**< add commands processed */ + int64_t remove_count; /**< remove commands processed */ + int64_t delete_count; /**< delete commands processed */ + int64_t clear_count; /**< clear commands processed */ + int64_t set_count; /**< set commands processed */ + int64_t get_mut_count; /**< get_mut/emplace commands processed */ + int64_t modified_count; /**< modified commands processed */ + int64_t other_count; /**< other commands processed */ + int64_t discard_count; /**< commands discarded, happens when entity is no longer alive when running the command */ + int64_t batched_entity_count; /**< entities for which commands were batched */ + int64_t batched_command_count; /**< commands batched */ } cmd; - const char *name_prefix; /* Value set by ecs_set_name_prefix. Used + const char *name_prefix; /**< Value set by ecs_set_name_prefix. Used * to remove library prefixes of symbol * names (such as Ecs, ecs_) when * registering them as names. */ @@ -4167,17 +4198,49 @@ typedef struct ecs_world_info_t { /** Type that contains information about a query group. */ typedef struct ecs_query_group_info_t { - int32_t match_count; /* How often tables have been matched/unmatched */ - int32_t table_count; /* Number of tables in group */ - void *ctx; /* Group context, returned by on_group_create */ + int32_t match_count; /**< How often tables have been matched/unmatched */ + int32_t table_count; /**< Number of tables in group */ + void *ctx; /**< Group context, returned by on_group_create */ } ecs_query_group_info_t; /** @} */ +/** + * @defgroup builtin_components Builtin component types. + * @brief Types that represent builtin components. + * @{ + */ + +/** A (string) identifier. Used as pair with EcsName and EcsSymbol tags */ +typedef struct EcsIdentifier { + char *value; /**< Identifier string */ + ecs_size_t length; /**< Length of identifier */ + uint64_t hash; /**< Hash of current value */ + uint64_t index_hash; /**< Hash of existing record in current index */ + ecs_hashmap_t *index; /**< Current index */ +} EcsIdentifier; + +/** Component information. */ +typedef struct EcsComponent { + ecs_size_t size; /**< Component size */ + ecs_size_t alignment; /**< Component alignment */ +} EcsComponent; + +/** Component for storing a poly object */ +typedef struct EcsPoly { + ecs_poly_t *poly; /**< Pointer to poly object */ +} EcsPoly; + +/** Component for iterable entities */ +typedef ecs_iterable_t EcsIterable; + +/** @} */ +/** @} */ + /* Only include deprecated definitions if deprecated addon is required */ #ifdef FLECS_DEPRECATED /** - * @file deprecated.h + * @file addons/deprecated.h * @brief The deprecated addon contains deprecated operations. */ @@ -4200,15 +4263,16 @@ extern "C" { #endif - /** - * @defgroup id_flags Id Flags + * @defgroup api_constants API Constants + * @brief Public API constants. * @{ */ -/* Id flags are bits that can be set on an id. Flags can store information - * about how an id should be interpreted (for example, as a pair) or can be used - * to enable features (such as OVERRIDE). +/** + * @defgroup id_flags Component id flags. + * @brief Id flags are bits that can be set on an id (ecs_id_t). + * @{ */ /** Bit added to flags to differentiate between id flags and generation */ @@ -4217,7 +4281,7 @@ extern "C" { /** Indicates that the id is a pair. */ FLECS_API extern const ecs_id_t ECS_PAIR; -/** Automatically override component when its inherited */ +/** Automatically override component when it is inherited */ FLECS_API extern const ecs_id_t ECS_OVERRIDE; /** Adds bitset to storage which allows component to be enabled/disabled */ @@ -4228,13 +4292,12 @@ FLECS_API extern const ecs_id_t ECS_AND; /** @} */ - /** - * @defgroup builtin_tags Builtin components & tags + * @defgroup builtin_tags Builtin component ids. * @{ */ -/** Builtin component ids */ +/* Builtin component ids */ FLECS_API extern const ecs_entity_t ecs_id(EcsComponent); FLECS_API extern const ecs_entity_t ecs_id(EcsIdentifier); FLECS_API extern const ecs_entity_t ecs_id(EcsIterable); @@ -4247,41 +4310,41 @@ FLECS_API extern const ecs_entity_t EcsObserver; FLECS_API extern const ecs_entity_t EcsSystem; FLECS_API extern const ecs_entity_t ecs_id(EcsTickSource); -/** Pipeline module component ids */ +/* Pipeline module component ids */ FLECS_API extern const ecs_entity_t ecs_id(EcsPipelineQuery); -/** Timer module component ids */ +/* Timer module component ids */ FLECS_API extern const ecs_entity_t ecs_id(EcsTimer); FLECS_API extern const ecs_entity_t ecs_id(EcsRateFilter); /** Root scope for builtin flecs entities */ FLECS_API extern const ecs_entity_t EcsFlecs; -/* Core module scope */ +/** Core module scope */ FLECS_API extern const ecs_entity_t EcsFlecsCore; -/* Entity associated with world (used for "attaching" components to world) */ +/** Entity associated with world (used for "attaching" components to world) */ FLECS_API extern const ecs_entity_t EcsWorld; -/* Wildcard entity ("*"). Matches any id, returns all matches. */ +/** Wildcard entity ("*"). Matches any id, returns all matches. */ FLECS_API extern const ecs_entity_t EcsWildcard; -/* Any entity ("_"). Matches any id, returns only the first. */ +/** Any entity ("_"). Matches any id, returns only the first. */ FLECS_API extern const ecs_entity_t EcsAny; -/* This entity. Default source for queries. */ +/** This entity. Default source for queries. */ FLECS_API extern const ecs_entity_t EcsThis; -/* Variable entity ("$"). Used in expressions to prefix variable names */ +/** Variable entity ("$"). Used in expressions to prefix variable names */ FLECS_API extern const ecs_entity_t EcsVariable; -/* Marks a relationship as transitive. +/** Marks a relationship as transitive. * Behavior: * if R(X, Y) and R(Y, Z) then R(X, Z) */ FLECS_API extern const ecs_entity_t EcsTransitive; -/* Marks a relatoinship as reflexive. +/** Marks a relatoinship as reflexive. * Behavior: * R(X, X) == true */ @@ -4303,13 +4366,13 @@ FLECS_API extern const ecs_entity_t EcsFinal; */ FLECS_API extern const ecs_entity_t EcsDontInherit; -/* Marks relationship as commutative. +/** Marks relationship as commutative. * Behavior: * if R(X, Y) then R(Y, X) */ FLECS_API extern const ecs_entity_t EcsSymmetric; -/* Can be added to relationship to indicate that the relationship can only occur +/** Can be added to relationship to indicate that the relationship can only occur * once on an entity. Adding a 2nd instance will replace the 1st. * * Behavior: @@ -4317,10 +4380,10 @@ FLECS_API extern const ecs_entity_t EcsSymmetric; */ FLECS_API extern const ecs_entity_t EcsExclusive; -/* Marks a relationship as acyclic. Acyclic relationships may not form cycles. */ +/** Marks a relationship as acyclic. Acyclic relationships may not form cycles. */ FLECS_API extern const ecs_entity_t EcsAcyclic; -/* Ensure that a component always is added together with another component. +/** Ensure that a component always is added together with another component. * * Behavior: * If With(R, O) and R(X) then O(X) @@ -4328,7 +4391,7 @@ FLECS_API extern const ecs_entity_t EcsAcyclic; */ FLECS_API extern const ecs_entity_t EcsWith; -/* Ensure that relationship target is child of specified entity. +/** Ensure that relationship target is child of specified entity. * * Behavior: * If OneOf(R, O) and R(X, Y), Y must be a child of O @@ -4336,112 +4399,113 @@ FLECS_API extern const ecs_entity_t EcsWith; */ FLECS_API extern const ecs_entity_t EcsOneOf; -/* Can be added to relationship to indicate that it should never hold data, even +/** Can be added to relationship to indicate that it should never hold data, even * when it or the relationship target is a component. */ FLECS_API extern const ecs_entity_t EcsTag; -/* Tag to indicate that relationship is stored as union. Union relationships enable +/** Tag to indicate that relationship is stored as union. Union relationships enable * changing the target of a union without switching tables. Union relationships * are also marked as exclusive. */ FLECS_API extern const ecs_entity_t EcsUnion; -/* Tag to indicate name identifier */ +/** Tag to indicate name identifier */ FLECS_API extern const ecs_entity_t EcsName; -/* Tag to indicate symbol identifier */ +/** Tag to indicate symbol identifier */ FLECS_API extern const ecs_entity_t EcsSymbol; -/* Tag to indicate alias identifier */ +/** Tag to indicate alias identifier */ FLECS_API extern const ecs_entity_t EcsAlias; -/* Used to express parent-child relationships. */ +/** Used to express parent-child relationships. */ FLECS_API extern const ecs_entity_t EcsChildOf; -/* Used to express inheritance relationships. */ +/** Used to express inheritance relationships. */ FLECS_API extern const ecs_entity_t EcsIsA; -/* Used to express dependency relationships */ +/** Used to express dependency relationships */ FLECS_API extern const ecs_entity_t EcsDependsOn; -/* Used to express a slot (used with prefab inheritance) */ +/** Used to express a slot (used with prefab inheritance) */ FLECS_API extern const ecs_entity_t EcsSlotOf; -/* Tag added to module entities */ +/** Tag added to module entities */ FLECS_API extern const ecs_entity_t EcsModule; -/* Tag to indicate an entity/component/system is private to a module */ +/** Tag to indicate an entity/component/system is private to a module */ FLECS_API extern const ecs_entity_t EcsPrivate; -/* Tag added to prefab entities. Any entity with this tag is automatically +/** Tag added to prefab entities. Any entity with this tag is automatically * ignored by filters/queries, unless EcsPrefab is explicitly added. */ FLECS_API extern const ecs_entity_t EcsPrefab; -/* When this tag is added to an entity it is skipped by all queries/filters */ +/** When this tag is added to an entity it is skipped by all queries/filters */ FLECS_API extern const ecs_entity_t EcsDisabled; -/* Event. Triggers when an id (component, tag, pair) is added to an entity */ +/** Event. Triggers when an id (component, tag, pair) is added to an entity */ FLECS_API extern const ecs_entity_t EcsOnAdd; -/* Event. Triggers when an id (component, tag, pair) is removed from an entity */ +/** Event. Triggers when an id (component, tag, pair) is removed from an entity */ FLECS_API extern const ecs_entity_t EcsOnRemove; -/* Event. Triggers when a component is set for an entity */ +/** Event. Triggers when a component is set for an entity */ FLECS_API extern const ecs_entity_t EcsOnSet; -/* Event. Triggers when a component is unset for an entity */ +/** Event. Triggers when a component is unset for an entity */ FLECS_API extern const ecs_entity_t EcsUnSet; -/* Event. Exactly-once observer for when an entity matches/unmatches a filter */ +/** Event. Exactly-once observer for when an entity matches/unmatches a filter */ FLECS_API extern const ecs_entity_t EcsMonitor; -/* Event. Triggers when an entity is deleted. +/** Event. Triggers when an entity is deleted. * Also used as relationship for defining cleanup behavior, see: * https://github.com/SanderMertens/flecs/blob/master/docs/Relationships.md#cleanup-properties */ FLECS_API extern const ecs_entity_t EcsOnDelete; -/* Event. Triggers when a table is created. */ +/** Event. Triggers when a table is created. */ // FLECS_API extern const ecs_entity_t EcsOnCreateTable; -/* Event. Triggers when a table is deleted. */ +/** Event. Triggers when a table is deleted. */ // FLECS_API extern const ecs_entity_t EcsOnDeleteTable; -/* Event. Triggers when a table becomes empty (doesn't emit on creation). */ +/** Event. Triggers when a table becomes empty (doesn't emit on creation). */ FLECS_API extern const ecs_entity_t EcsOnTableEmpty; -/* Event. Triggers when a table becomes non-empty. */ +/** Event. Triggers when a table becomes non-empty. */ FLECS_API extern const ecs_entity_t EcsOnTableFill; -/* Relationship used to define what should happen when a target entity (second +/** Relationship used to define what should happen when a target entity (second * element of a pair) is deleted. For details see: * https://github.com/SanderMertens/flecs/blob/master/docs/Relationships.md#cleanup-properties */ FLECS_API extern const ecs_entity_t EcsOnDeleteTarget; -/* Remove cleanup policy. Must be used as target in pair with EcsOnDelete or +/** Remove cleanup policy. Must be used as target in pair with EcsOnDelete or * EcsOnDeleteTarget. */ FLECS_API extern const ecs_entity_t EcsRemove; -/* Delete cleanup policy. Must be used as target in pair with EcsOnDelete or +/** Delete cleanup policy. Must be used as target in pair with EcsOnDelete or * EcsOnDeleteTarget. */ FLECS_API extern const ecs_entity_t EcsDelete; -/* Panic cleanup policy. Must be used as target in pair with EcsOnDelete or +/** Panic cleanup policy. Must be used as target in pair with EcsOnDelete or * EcsOnDeleteTarget. */ FLECS_API extern const ecs_entity_t EcsPanic; -/* Used like (EcsDefaultChildComponent, Component). When added to an entity, +/** Used like (EcsDefaultChildComponent, Component). When added to an entity, * this informs serialization formats which component to use when a value is * assigned to an entity without specifying the component. This is intended as * a hint, serialization formats are not required to use it. Adding this * component does not change the behavior of core ECS operations. */ FLECS_API extern const ecs_entity_t EcsDefaultChildComponent; -/* Tag used to indicate query is empty */ +/** Tag used to indicate query is empty */ FLECS_API extern const ecs_entity_t EcsEmpty; /* Pipeline module tags */ FLECS_API extern const ecs_entity_t ecs_id(EcsPipeline); +FLECS_API extern const ecs_entity_t EcsOnStart; FLECS_API extern const ecs_entity_t EcsPreFrame; FLECS_API extern const ecs_entity_t EcsOnLoad; FLECS_API extern const ecs_entity_t EcsPostLoad; @@ -4454,23 +4518,29 @@ FLECS_API extern const ecs_entity_t EcsOnStore; FLECS_API extern const ecs_entity_t EcsPostFrame; FLECS_API extern const ecs_entity_t EcsPhase; -/* Value used to quickly check if component is builtin. This is used to quickly +/** Value used to quickly check if component is builtin. This is used to quickly * filter out tables with builtin components (for example for ecs_delete) */ #define EcsLastInternalComponentId (ecs_id(EcsPoly)) -/* The first user-defined component starts from this id. Ids up to this number +/** The first user-defined component starts from this id. Ids up to this number * are reserved for builtin components */ #define EcsFirstUserComponentId (32) -/* The first user-defined entity starts from this id. Ids up to this number +/** The first user-defined entity starts from this id. Ids up to this number * are reserved for builtin components */ #define EcsFirstUserEntityId (ECS_HI_COMPONENT_ID + 128) /** @} */ - +/** @} */ /** - * @defgroup world_api World API + * @defgroup world_api World + * @brief Functions for working with `ecs_world_t`. + * @{ + */ + +/** + * @defgroup world_creation_deletion Creation & Deletion * @{ */ @@ -4537,6 +4607,46 @@ void ecs_atfini( ecs_fini_action_t action, void *ctx); +/** @} */ + +/** + * @defgroup world_frame Frame functions + * @{ + */ + +/** Begin frame. + * When an application does not use ecs_progress to control the main loop, it + * can still use Flecs features such as FPS limiting and time measurements. This + * operation needs to be invoked whenever a new frame is about to get processed. + * + * Calls to ecs_frame_begin must always be followed by ecs_frame_end. + * + * The function accepts a delta_time parameter, which will get passed to + * systems. This value is also used to compute the amount of time the function + * needs to sleep to ensure it does not exceed the target_fps, when it is set. + * When 0 is provided for delta_time, the time will be measured. + * + * This function should only be ran from the main thread. + * + * @param world The world. + * @param delta_time Time elapsed since the last frame. + * @return The provided delta_time, or measured time if 0 was provided. + */ +FLECS_API +ecs_ftime_t ecs_frame_begin( + ecs_world_t *world, + ecs_ftime_t delta_time); + +/** End frame. + * This operation must be called at the end of the frame, and always after + * ecs_frame_begin. + * + * @param world The world. + */ +FLECS_API +void ecs_frame_end( + ecs_world_t *world); + /** Register action to be executed once after frame. * Post frame actions are typically used for calling operations that cannot be * invoked during iteration, such as changing the number of threads. @@ -4568,33 +4678,315 @@ FLECS_API bool ecs_should_quit( const ecs_world_t *world); -/** Register hooks for component. - * Hooks allow for the execution of user code when components are constructed, - * copied, moved, destructed, added, removed or set. Hooks can be assigned as - * as long as a component has not yet been used (added to an entity). - * - * The hooks that are currently set can be accessed with ecs_get_type_info. +/** Measure frame time. + * Frame time measurements measure the total time passed in a single frame, and + * how much of that time was spent on systems and on merging. + * + * Frame time measurements add a small constant-time overhead to an application. + * When an application sets a target FPS, frame time measurements are enabled by + * default. * * @param world The world. - * @param id The component id for which to register the actions - * @param hooks Type that contains the component actions. + * @param enable Whether to enable or disable frame time measuring. + */ +FLECS_API void ecs_measure_frame_time( + ecs_world_t *world, + bool enable); + +/** Measure system time. + * System time measurements measure the time spent in each system. + * + * System time measurements add overhead to every system invocation and + * therefore have a small but measurable impact on application performance. + * System time measurements must be enabled before obtaining system statistics. + * + * @param world The world. + * @param enable Whether to enable or disable system time measuring. + */ +FLECS_API void ecs_measure_system_time( + ecs_world_t *world, + bool enable); + +/** Set target frames per second (FPS) for application. + * Setting the target FPS ensures that ecs_progress is not invoked faster than + * the specified FPS. When enabled, ecs_progress tracks the time passed since + * the last invocation, and sleeps the remaining time of the frame (if any). + * + * This feature ensures systems are ran at a consistent interval, as well as + * conserving CPU time by not running systems more often than required. + * + * Note that ecs_progress only sleeps if there is time left in the frame. Both + * time spent in flecs as time spent outside of flecs are taken into + * account. + * + * @param world The world. + * @param fps The target FPS. */ FLECS_API -void ecs_set_hooks_id( +void ecs_set_target_fps( ecs_world_t *world, - ecs_entity_t id, - const ecs_type_hooks_t *hooks); + ecs_ftime_t fps); -/** Get hooks for component. +/** @} */ + +/** + * @defgroup commands Commands + * @{ + */ + +/** Begin readonly mode. + * Readonly mode guarantees that no mutations will occur on the world, which + * makes the world safe to access from multiple threads. While the world is in + * readonly mode, operations are deferred. + * + * Note that while similar to ecs_defer_begin, deferring only does not guarantee + * the world is not mutated. Operations that are not deferred (like creating a + * query) update data structures on the world and are allowed when deferring is + * enabled, but not when the world is in readonly mode. + * + * A call to ecs_readonly_begin must be followed up with ecs_readonly_end. + * + * The ecs_progress() function automatically enables readonly mode while systems + * are executed. + * + * When a world has more than one stage, the specific stage must be provided to + * mutating ECS operations. Failing to do so will throw a readonly assert. A + * world typically has more than one stage when using threads. An example: + * + * ecs_set_stage_count(world, 2); + * ecs_stage_t *stage = ecs_get_stage(world, 1); + * + * ecs_readonly_begin(world); + * ecs_add(world, e, Tag); // readonly assert + * ecs_add(stage, e, Tag); // OK + * + * @param world The world + * @return Whether world is in readonly mode. + */ +FLECS_API +bool ecs_readonly_begin( + ecs_world_t *world); + +/** End readonly mode. + * This operation ends readonly mode, and must be called after + * ecs_readonly_begin. Operations that were deferred while the world was in + * readonly mode will be flushed. + * + * @param world The world + */ +FLECS_API +void ecs_readonly_end( + ecs_world_t *world); + +/** Merge world or stage. + * When automatic merging is disabled, an application can call this + * operation on either an individual stage, or on the world which will merge + * all stages. This operation may only be called when staging is not enabled + * (either after progress() or after readonly_end()). + * + * This operation may be called on an already merged stage or world. + * + * @param world The world. + */ +FLECS_API +void ecs_merge( + ecs_world_t *world); + +/** Defer operations until end of frame. + * When this operation is invoked while iterating, operations inbetween the + * defer_begin and defer_end operations are executed at the end of the frame. + * + * This operation is thread safe. * * @param world The world. - * @param id The component id for which to retrieve the hooks. - * @return The hooks for the component, or NULL if not registered. + * @return true if world changed from non-deferred mode to deferred mode. */ FLECS_API -const ecs_type_hooks_t* ecs_get_hooks_id( +bool ecs_defer_begin( + ecs_world_t *world); + +/** Test if deferring is enabled for current stage. + * + * @param world The world. + * @return True if deferred, false if not. + */ +FLECS_API +bool ecs_is_deferred( + const ecs_world_t *world); + +/** End block of operations to defer. + * See defer_begin. + * + * This operation is thread safe. + * + * @param world The world. + * @return true if world changed from deferred mode to non-deferred mode. + */ +FLECS_API +bool ecs_defer_end( + ecs_world_t *world); + +/** Suspend deferring but do not flush queue. + * This operation can be used to do an undeferred operation while not flushing + * the operations in the queue. + * + * An application should invoke ecs_defer_resume before ecs_defer_end is called. + * The operation may only be called when deferring is enabled. + * + * @param world The world. + */ +FLECS_API +void ecs_defer_suspend( + ecs_world_t *world); + +/** Resume deferring. + * See ecs_defer_suspend. + * + * @param world The world. + */ +FLECS_API +void ecs_defer_resume( + ecs_world_t *world); + +/** Enable/disable automerging for world or stage. + * When automerging is enabled, staged data will automatically be merged with + * the world when staging ends. This happens at the end of progress(), at a + * sync point or when readonly_end() is called. + * + * Applications can exercise more control over when data from a stage is merged + * by disabling automerging. This requires an application to explicitly call + * merge() on the stage. + * + * When this function is invoked on the world, it sets all current stages to + * the provided value and sets the default for new stages. When this function is + * invoked on a stage, automerging is only set for that specific stage. + * + * @param world The world. + * @param automerge Whether to enable or disable automerging. + */ +FLECS_API +void ecs_set_automerge( ecs_world_t *world, - ecs_entity_t id); + bool automerge); + +/** Configure world to have N stages. + * This initializes N stages, which allows applications to defer operations to + * multiple isolated defer queues. This is typically used for applications with + * multiple threads, where each thread gets its own queue, and commands are + * merged when threads are synchronized. + * + * Note that the ecs_set_threads function already creates the appropriate + * number of stages. The set_stage_count() operation is useful for applications that + * want to manage their own stages and/or threads. + * + * @param world The world. + * @param stages The number of stages. + */ +FLECS_API +void ecs_set_stage_count( + ecs_world_t *world, + int32_t stages); + +/** Get number of configured stages. + * Return number of stages set by ecs_set_stage_count. + * + * @param world The world. + * @return The number of stages used for threading. + */ +FLECS_API +int32_t ecs_get_stage_count( + const ecs_world_t *world); + +/** Get current stage id. + * The stage id can be used by an application to learn about which stage it is + * using, which typically corresponds with the worker thread id. + * + * @param world The world. + * @return The stage id. + */ +FLECS_API +int32_t ecs_get_stage_id( + const ecs_world_t *world); + +/** Get stage-specific world pointer. + * Flecs threads can safely invoke the API as long as they have a private + * context to write to, also referred to as the stage. This function returns a + * pointer to a stage, disguised as a world pointer. + * + * Note that this function does not(!) create a new world. It simply wraps the + * existing world in a thread-specific context, which the API knows how to + * unwrap. The reason the stage is returned as an ecs_world_t is so that it + * can be passed transparently to the existing API functions, vs. having to + * create a dediated API for threading. + * + * @param world The world. + * @param stage_id The index of the stage to retrieve. + * @return A thread-specific pointer to the world. + */ +FLECS_API +ecs_world_t* ecs_get_stage( + const ecs_world_t *world, + int32_t stage_id); + +/** Test whether the current world is readonly. + * This function allows the code to test whether the currently used world + * is readonly or whether it allows for writing. + * + * @param world A pointer to a stage or the world. + * @return True if the world or stage is readonly. + */ +FLECS_API +bool ecs_stage_is_readonly( + const ecs_world_t *world); + +/** Create asynchronous stage. + * An asynchronous stage can be used to asynchronously queue operations for + * later merging with the world. An asynchronous stage is similar to a regular + * stage, except that it does not allow reading from the world. + * + * Asynchronous stages are never merged automatically, and must therefore be + * manually merged with the ecs_merge function. It is not necessary to call + * defer_begin or defer_end before and after enqueuing commands, as an + * asynchronous stage unconditionally defers operations. + * + * The application must ensure that no commands are added to the stage while the + * stage is being merged. + * + * An asynchronous stage must be cleaned up by ecs_async_stage_free. + * + * @param world The world. + * @return The stage. + */ +FLECS_API +ecs_world_t* ecs_async_stage_new( + ecs_world_t *world); + +/** Free asynchronous stage. + * The provided stage must be an asynchronous stage. If a non-asynchronous stage + * is provided, the operation will fail. + * + * @param stage The stage to free. + */ +FLECS_API +void ecs_async_stage_free( + ecs_world_t *stage); + +/** Test whether provided stage is asynchronous. + * + * @param stage The stage. + * @return True when the stage is asynchronous, false for a regular stage or + * world. + */ +FLECS_API +bool ecs_stage_is_async( + ecs_world_t *stage); + +/** @} */ + +/** + * @defgroup world_misc Misc + * @{ + */ /** Set a world context. * This operation allows an application to register custom data with a world @@ -4663,17 +5055,6 @@ void ecs_set_entity_range( ecs_entity_t id_start, ecs_entity_t id_end); -/** Sets the entity's generation in the world's sparse set. - * Used for managing manual id pools. - * - * @param world The world. - * @param entity_with_generation Entity for which to set the generation with the new generation to set. - */ -FLECS_API -void ecs_set_entity_generation( - ecs_world_t *world, - ecs_entity_t entity_with_generation); - /** Enable/disable range limits. * When an application is both a receiver of range-limited entities and a * producer of range-limited entities, range checking needs to be temporarily @@ -4689,55 +5070,6 @@ bool ecs_enable_range_check( ecs_world_t *world, bool enable); -/** Measure frame time. - * Frame time measurements measure the total time passed in a single frame, and - * how much of that time was spent on systems and on merging. - * - * Frame time measurements add a small constant-time overhead to an application. - * When an application sets a target FPS, frame time measurements are enabled by - * default. - * - * @param world The world. - * @param enable Whether to enable or disable frame time measuring. - */ -FLECS_API void ecs_measure_frame_time( - ecs_world_t *world, - bool enable); - -/** Measure system time. - * System time measurements measure the time spent in each system. - * - * System time measurements add overhead to every system invocation and - * therefore have a small but measurable impact on application performance. - * System time measurements must be enabled before obtaining system statistics. - * - * @param world The world. - * @param enable Whether to enable or disable system time measuring. - */ -FLECS_API void ecs_measure_system_time( - ecs_world_t *world, - bool enable); - -/** Set target frames per second (FPS) for application. - * Setting the target FPS ensures that ecs_progress is not invoked faster than - * the specified FPS. When enabled, ecs_progress tracks the time passed since - * the last invocation, and sleeps the remaining time of the frame (if any). - * - * This feature ensures systems are ran at a consistent interval, as well as - * conserving CPU time by not running systems more often than required. - * - * Note that ecs_progress only sleeps if there is time left in the frame. Both - * time spent in flecs as time spent outside of flecs are taken into - * account. - * - * @param world The world. - * @param fps The target FPS. - */ -FLECS_API -void ecs_set_target_fps( - ecs_world_t *world, - ecs_ftime_t fps); - /** Force aperiodic actions. * The world may delay certain operations until they are necessary for the * application to function correctly. This may cause observable side effects @@ -4798,6 +5130,23 @@ int32_t ecs_delete_empty_tables( int32_t min_id_count, double time_budget_seconds); +/** Get world from poly. + * + * @param poly A pointer to a poly object. + * @return The world. + */ +FLECS_API +const ecs_world_t* ecs_get_world( + const ecs_poly_t *poly); + +/** Get entity from poly. + * + * @param poly A pointer to a poly object. + * @return Entity associated with the poly object. + */ +FLECS_API +ecs_entity_t ecs_get_entity( + const ecs_poly_t *poly); /** Test if pointer is of specified type. * Usage: @@ -4817,10 +5166,31 @@ bool _ecs_poly_is( #define ecs_poly_is(object, type)\ _ecs_poly_is(object, type##_magic) +/** Make a pair id. + * This function is equivalent to using the ecs_pair macro, and is added for + * convenience to make it easier for non C/C++ bindings to work with pairs. + * + * @param first The first element of the pair of the pair. + * @param second The target of the pair. + */ +FLECS_API +ecs_id_t ecs_make_pair( + ecs_entity_t first, + ecs_entity_t second); + +/** @} */ + /** @} */ /** - * @defgroup creating_entities Creating Entities + * @defgroup entities Entities + * @brief Functions for working with `ecs_entity_t`. + * @{ + */ + +/** + * @defgroup creating_entities Creating & Deleting + * @brief Functions for creating and deleting entities. * @{ */ @@ -4857,8 +5227,9 @@ FLECS_API ecs_entity_t ecs_new_low_id( ecs_world_t *world); -/** Create new entity. - * This operation creates a new entity with a (component) id. +/** Create new entity with (component) id. + * This operation creates a new entity with an optional (component) id. When 0 + * is passed to the id paramter, no component is added to the new entity. * * @param world The world. * @param id The component id to initialize the new entity with. @@ -4923,24 +5294,6 @@ const ecs_entity_t* ecs_bulk_init( ecs_world_t *world, const ecs_bulk_desc_t *desc); -/** Find or create a component. - * This operation creates a new component, or finds an existing one. The find or - * create behavior is the same as ecs_entity_init. - * - * When an existing component is found, the size and alignment are verified with - * the provided values. If the values do not match, the operation will fail. - * - * See the documentation of ecs_component_desc_t for more details. - * - * @param world The world. - * @param desc Component init parameters. - * @return A handle to the new or existing component, or 0 if failed. - */ -FLECS_API -ecs_entity_t ecs_component_init( - ecs_world_t *world, - const ecs_component_desc_t *desc); - /** Create N new entities. * This operation is the same as ecs_new_w_id, but creates N entities * instead of one and does not recycle ids. @@ -4974,10 +5327,37 @@ ecs_entity_t ecs_clone( ecs_entity_t src, bool copy_value); +/** Delete an entity. + * This operation will delete an entity and all of its components. The entity id + * will be recycled. Repeatedly calling ecs_delete without ecs_new or + * ecs_new_w_id will cause a memory leak as it will cause + * the list with ids that can be recycled to grow unbounded. + * + * @param world The world. + * @param entity The entity. + */ +FLECS_API +void ecs_delete( + ecs_world_t *world, + ecs_entity_t entity); + +/** Delete all entities with the specified id. + * This will delete all entities (tables) that have the specified id. The id + * may be a wildcard and/or a pair. + * + * @param world The world. + * @param id The id. + */ +FLECS_API +void ecs_delete_with( + ecs_world_t *world, + ecs_id_t id); + /** @} */ /** * @defgroup adding_removing Adding & Removing + * @brief Functions for adding and removing components. * @{ */ @@ -5037,14 +5417,77 @@ void ecs_override_id( ecs_entity_t entity, ecs_id_t id); +/** Clear all components. + * This operation will clear all components from an entity but will not delete + * the entity itself. This effectively prevents the entity id from being + * recycled. + * + * @param world The world. + * @param entity The entity. + */ +FLECS_API +void ecs_clear( + ecs_world_t *world, + ecs_entity_t entity); + + +/** Remove all instances of the specified id. + * This will remove the specified id from all entities (tables). Teh id may be + * a wildcard and/or a pair. + * + * @param world The world. + * @param id The id. + */ +FLECS_API +void ecs_remove_all( + ecs_world_t *world, + ecs_id_t id); + +/** Set current with id. + * New entities are automatically created with the specified id. + * + * @param world The world. + * @param id The id. + * @return The previous id. + */ +FLECS_API +ecs_entity_t ecs_set_with( + ecs_world_t *world, + ecs_id_t id); + +/** Get current with id. + * Get the id set with ecs_set_with. + * + * @param world The world. + * @return The last id provided to ecs_set_with. + */ +FLECS_API +ecs_id_t ecs_get_with( + const ecs_world_t *world); + /** @} */ - /** - * @defgroup enabling_disabling Enabling & Disabling components. + * @defgroup enabling_disabling Enabling & Disabling + * @brief Functions for enabling/disabling entities and components. * @{ */ +/** Enable or disable entity. + * This operation enables or disables an entity by adding or removing the + * EcsDisabled tag. A disabled entity will not be matched with any systems, + * unless the system explicitly specifies the EcsDisabled tag. + * + * @param world The world. + * @param entity The entity to enable or disable. + * @param enabled true to enable the entity, false to disable. + */ +FLECS_API +void ecs_enable( + ecs_world_t *world, + ecs_entity_t entity, + bool enabled); + /** Enable or disable component. * Enabling or disabling a component does not add or remove a component from an * entity, but prevents it from being matched with queries. This operation can @@ -5083,88 +5526,9 @@ bool ecs_is_enabled_id( /** @} */ - /** - * @defgroup pairs Pairs - * @{ - */ - -/** Make a pair id. - * This function is equivalent to using the ecs_pair macro, and is added for - * convenience to make it easier for non C/C++ bindings to work with pairs. - * - * @param first The first element of the pair of the pair. - * @param second The target of the pair. - */ -FLECS_API -ecs_id_t ecs_make_pair( - ecs_entity_t first, - ecs_entity_t second); - -/** @} */ - - -/** - * @defgroup deleting Deleting Entities and components - * @{ - */ - -/** Clear all components. - * This operation will clear all components from an entity but will not delete - * the entity itself. This effectively prevents the entity id from being - * recycled. - * - * @param world The world. - * @param entity The entity. - */ -FLECS_API -void ecs_clear( - ecs_world_t *world, - ecs_entity_t entity); - -/** Delete an entity. - * This operation will delete an entity and all of its components. The entity id - * will be recycled. Repeatedly calling ecs_delete without ecs_new or - * ecs_new_w_id will cause a memory leak as it will cause - * the list with ids that can be recycled to grow unbounded. - * - * @param world The world. - * @param entity The entity. - */ -FLECS_API -void ecs_delete( - ecs_world_t *world, - ecs_entity_t entity); - -/** Delete all entities with the specified id. - * This will delete all entities (tables) that have the specified id. The id - * may be a wildcard and/or a pair. - * - * @param world The world. - * @param id The id. - */ -FLECS_API -void ecs_delete_with( - ecs_world_t *world, - ecs_id_t id); - -/** Remove all instances of the specified id. - * This will remove the specified id from all entities (tables). Teh id may be - * a wildcard and/or a pair. - * - * @param world The world. - * @param id The id. - */ -FLECS_API -void ecs_remove_all( - ecs_world_t *world, - ecs_id_t id); - -/** @} */ - - -/** - * @defgroup getting Getting Components + * @defgroup getting Getting & Setting + * @brief Functions for getting/setting components. * @{ */ @@ -5225,14 +5589,6 @@ void ecs_ref_update( const ecs_world_t *world, ecs_ref_t *ref); -/** @} */ - - -/** - * @defgroup setting Setting Components - * @{ - */ - /** Get a mutable pointer to a component. * This operation is similar to ecs_get_id but it returns a mutable * pointer. If this operation is invoked from inside a system, the entity will @@ -5409,9 +5765,9 @@ ecs_entity_t ecs_set_id( /** @} */ - /** - * @defgroup metadata Entity Metadata + * @defgroup liveliness Entity Liveliness + * @brief Functions for testing and modifying entity liveliness. * @{ */ @@ -5460,6 +5816,22 @@ FLECS_API ecs_id_t ecs_strip_generation( ecs_entity_t e); +/** Override the generation of an entity. + * The generation count of an entity is increased each time an entity is deleted + * and is used to test whether an entity id is alive. + * + * This operation overrides the current generation of an entity with the + * specified generation, which can be useful if an entity is externally managed, + * like for external pools, savefiles or netcode. + * + * @param world The world. + * @param entity Entity for which to set the generation with the new generation. + */ +FLECS_API +void ecs_set_entity_generation( + ecs_world_t *world, + ecs_entity_t entity); + /** Get alive identifier. * In some cases an application may need to work with identifiers from which * the generation has been stripped. A typical scenario in which this happens is @@ -5541,6 +5913,14 @@ bool ecs_exists( const ecs_world_t *world, ecs_entity_t entity); +/** @} */ + +/** + * @defgroup entity_info Entity Information. + * @brief Get information from entity. + * @{ + */ + /** Get the type of an entity. * * @param world The world. @@ -5563,199 +5943,6 @@ ecs_table_t* ecs_get_table( const ecs_world_t *world, ecs_entity_t entity); -/** Get the storage table of an entity. - * The storage table of an entity has a type that only contains components, and - * excludes tags/entities/pairs that have no data. - * - * @param world The world. - * @param entity The entity. - * @return The storage table of the entity, NULL if the entity has no components. - */ -FLECS_API -ecs_table_t* ecs_get_storage_table( - const ecs_world_t *world, - ecs_entity_t entity); - -/** Get the type for an id. - * This function returnsthe type information for an id. The specified id can be - * any valid id. For the rules on how type information is determined based on - * id, see ecs_get_typeid. - * - * @param world The world. - * @param id The id. - * @return The type information of the id. - */ -FLECS_API -const ecs_type_info_t* ecs_get_type_info( - const ecs_world_t *world, - ecs_id_t id); - -/** Get the type for an id. - * This operation returns the component id for an id, if the id is associated - * with a type. For a regular component with a non-zero size (an entity with the - * EcsComponent component) the operation will return the entity itself. - * - * For an entity that does not have the EcsComponent component, or with an - * EcsComponent value with size 0, the operation will return 0. - * - * For a pair id the operation will return the type associated with the pair, by - * applying the following rules in order: - * - The first pair element is returned if it is a component - * - 0 is returned if the relationship entity has the Tag property - * - The second pair element is returned if it is a component - * - 0 is returned. - * - * @param world The world. - * @param id The id. - * @return The type id of the id. - */ -FLECS_API -ecs_entity_t ecs_get_typeid( - const ecs_world_t *world, - ecs_id_t id); - -/** Returns whether specified id a tag. - * This operation returns whether the specified type is a tag (a component - * without data/size). - * - * An id is a tag when: - * - it is an entity without the EcsComponent component - * - it has an EcsComponent with size member set to 0 - * - it is a pair where both elements are a tag - * - it is a pair where the first element has the EcsTag tag - * - * @param world The world. - * @param id The id. - * @return Whether the provided id is a tag. - */ -FLECS_API -ecs_entity_t ecs_id_is_tag( - const ecs_world_t *world, - ecs_id_t id); - -/** Returns whether specified id is in use. - * This operation returns whether an id is in use in the world. An id is in use - * if it has been added to one or more tables. - * - * @param world The world. - * @param id The id. - * @return Whether the id is in use. - */ -FLECS_API -bool ecs_id_in_use( - ecs_world_t *world, - ecs_id_t id); - -/** Get the name of an entity. - * This will return the name stored in (EcsIdentifier, EcsName). - * - * @param world The world. - * @param entity The entity. - * @return The type of the entity, NULL if the entity has no name. - */ -FLECS_API -const char* ecs_get_name( - const ecs_world_t *world, - ecs_entity_t entity); - -/** Get the symbol of an entity. - * This will return the symbol stored in (EcsIdentifier, EcsSymbol). - * - * @param world The world. - * @param entity The entity. - * @return The type of the entity, NULL if the entity has no name. - */ -FLECS_API -const char* ecs_get_symbol( - const ecs_world_t *world, - ecs_entity_t entity); - -/** Set the name of an entity. - * This will set or overwrite the name of an entity. If no entity is provided, - * a new entity will be created. - * - * The name is stored in (EcsIdentifier, EcsName). - * - * @param world The world. - * @param entity The entity. - * @param name The name. - * @return The provided entity, or a new entity if 0 was provided. - */ -FLECS_API -ecs_entity_t ecs_set_name( - ecs_world_t *world, - ecs_entity_t entity, - const char *name); - -/** Set the symbol of an entity. - * This will set or overwrite the symbol of an entity. If no entity is provided, - * a new entity will be created. - * - * The symbol is stored in (EcsIdentifier, EcsSymbol). - * - * @param world The world. - * @param entity The entity. - * @param symbol The symbol. - * @return The provided entity, or a new entity if 0 was provided. - */ -FLECS_API -ecs_entity_t ecs_set_symbol( - ecs_world_t *world, - ecs_entity_t entity, - const char *symbol); - -/** Set alias for entity. - * An entity can be looked up using its alias from the root scope without - * providing the fully qualified name if its parent. An entity can only have - * a single alias. - * - * The symbol is stored in (EcsIdentifier, EcsAlias). - * - * @param world The world. - * @param entity The entity. - * @param alias The alias. - */ -FLECS_API -void ecs_set_alias( - ecs_world_t *world, - ecs_entity_t entity, - const char *alias); - -/** Convert id flag to string. - * This operation converts a id flag to a string. - * - * @param id_flags The id flag. - * @return The id flag string, or NULL if no valid id is provided. - */ -FLECS_API -const char* ecs_id_flag_str( - ecs_id_t id_flags); - -/** Convert id to string. - * This operation interprets the structure of an id and converts it to a string. - * - * @param world The world. - * @param id The id to convert to a string. - * @return The id converted to a string. - */ -FLECS_API -char* ecs_id_str( - const ecs_world_t *world, - ecs_id_t id); - -/** Write id string to buffer. - * Same as ecs_id_str but writes result to ecs_strbuf_t. - * - * @param world The world. - * @param id The id to convert to a string. - * @param buf The buffer to write to. - */ -FLECS_API -void ecs_id_str_buf( - const ecs_world_t *world, - ecs_id_t id, - ecs_strbuf_t *buf); - /** Convert type to string. * The result of this operation must be freed with ecs_os_free. * @@ -5858,20 +6045,21 @@ ecs_entity_t ecs_get_target_for_id( ecs_entity_t rel, ecs_id_t id); -/** Enable or disable an entity. - * This operation enables or disables an entity by adding or removing the - * EcsDisabled tag. A disabled entity will not be matched with any systems, - * unless the system explicitly specifies the EcsDisabled tag. - * +/** Return depth for entity in tree for relationship rel. + * Depth is determined by counting the number of targets encountered while + * traversing up the relationship tree for rel. Only acyclic relationships are + * supported. + * * @param world The world. - * @param entity The entity to enable or disable. - * @param enabled true to enable the entity, false to disable. + * @param entity The entity. + * @param rel The relationship. + * @return The depth of the entity in the tree. */ FLECS_API -void ecs_enable( - ecs_world_t *world, +int32_t ecs_get_depth( + const ecs_world_t *world, ecs_entity_t entity, - bool enabled); + ecs_entity_t rel); /** Count entities that have the specified id. * Returns the number of entities that have the specified id. @@ -5889,10 +6077,86 @@ int32_t ecs_count_id( /** - * @defgroup lookup Lookups + * @defgroup paths Entity Names + * @brief Functions for working with entity names and paths. * @{ */ +/** Get the name of an entity. + * This will return the name stored in (EcsIdentifier, EcsName). + * + * @param world The world. + * @param entity The entity. + * @return The type of the entity, NULL if the entity has no name. + */ +FLECS_API +const char* ecs_get_name( + const ecs_world_t *world, + ecs_entity_t entity); + +/** Get the symbol of an entity. + * This will return the symbol stored in (EcsIdentifier, EcsSymbol). + * + * @param world The world. + * @param entity The entity. + * @return The type of the entity, NULL if the entity has no name. + */ +FLECS_API +const char* ecs_get_symbol( + const ecs_world_t *world, + ecs_entity_t entity); + +/** Set the name of an entity. + * This will set or overwrite the name of an entity. If no entity is provided, + * a new entity will be created. + * + * The name is stored in (EcsIdentifier, EcsName). + * + * @param world The world. + * @param entity The entity. + * @param name The name. + * @return The provided entity, or a new entity if 0 was provided. + */ +FLECS_API +ecs_entity_t ecs_set_name( + ecs_world_t *world, + ecs_entity_t entity, + const char *name); + +/** Set the symbol of an entity. + * This will set or overwrite the symbol of an entity. If no entity is provided, + * a new entity will be created. + * + * The symbol is stored in (EcsIdentifier, EcsSymbol). + * + * @param world The world. + * @param entity The entity. + * @param symbol The symbol. + * @return The provided entity, or a new entity if 0 was provided. + */ +FLECS_API +ecs_entity_t ecs_set_symbol( + ecs_world_t *world, + ecs_entity_t entity, + const char *symbol); + +/** Set alias for entity. + * An entity can be looked up using its alias from the root scope without + * providing the fully qualified name if its parent. An entity can only have + * a single alias. + * + * The symbol is stored in (EcsIdentifier, EcsAlias). + * + * @param world The world. + * @param entity The entity. + * @param alias The alias. + */ +FLECS_API +void ecs_set_alias( + ecs_world_t *world, + ecs_entity_t entity, + const char *alias); + /** Lookup an entity by name. * Returns an entity that matches the specified name. Only looks for entities in * the current scope (root if no scope is provided). @@ -5961,14 +6225,6 @@ ecs_entity_t ecs_lookup_symbol( const char *symbol, bool lookup_as_path); -/** @} */ - - -/** - * @defgroup paths Paths - * @{ - */ - /** Get a path identifier for an entity. * This operation creates a path that contains the names of the entities from * the specified parent to the provided entity, separated by the provided @@ -6060,14 +6316,6 @@ ecs_entity_t ecs_add_path_w_sep( const char *sep, const char *prefix); -/** @} */ - - -/** - * @defgroup scopes Scopes - * @{ - */ - /** Set the current scope. * This operation sets the scope of the current stage to the provided entity. * As a result new entities will be created in this scope, and lookups will be @@ -6095,28 +6343,6 @@ FLECS_API ecs_entity_t ecs_get_scope( const ecs_world_t *world); -/** Set current with id. - * New entities are automatically created with the specified id. - * - * @param world The world. - * @param id The id. - * @return The previous id. - */ -FLECS_API -ecs_entity_t ecs_set_with( - ecs_world_t *world, - ecs_id_t id); - -/** Get current with id. - * Get the id set with ecs_set_with. - * - * @param world The world. - * @return The last id provided to ecs_set_with. - */ -FLECS_API -ecs_id_t ecs_get_with( - const ecs_world_t *world); - /** Set a name prefix for newly created entities. * This is a utility that lets C modules use prefixed names for C types and * C functions, while using names for the entity names that do not have the @@ -6172,9 +6398,239 @@ ecs_entity_t* ecs_get_lookup_path( /** @} */ +/** @} */ /** - * @defgroup terms Terms + * @defgroup components Components + * @brief Functions for registering and working with components. + * @{ + */ + +/** Find or create a component. + * This operation creates a new component, or finds an existing one. The find or + * create behavior is the same as ecs_entity_init. + * + * When an existing component is found, the size and alignment are verified with + * the provided values. If the values do not match, the operation will fail. + * + * See the documentation of ecs_component_desc_t for more details. + * + * @param world The world. + * @param desc Component init parameters. + * @return A handle to the new or existing component, or 0 if failed. + */ +FLECS_API +ecs_entity_t ecs_component_init( + ecs_world_t *world, + const ecs_component_desc_t *desc); + +/** Register hooks for component. + * Hooks allow for the execution of user code when components are constructed, + * copied, moved, destructed, added, removed or set. Hooks can be assigned as + * as long as a component has not yet been used (added to an entity). + * + * The hooks that are currently set can be accessed with ecs_get_type_info. + * + * @param world The world. + * @param id The component id for which to register the actions + * @param hooks Type that contains the component actions. + */ +FLECS_API +void ecs_set_hooks_id( + ecs_world_t *world, + ecs_entity_t id, + const ecs_type_hooks_t *hooks); + +/** Get hooks for component. + * + * @param world The world. + * @param id The component id for which to retrieve the hooks. + * @return The hooks for the component, or NULL if not registered. + */ +FLECS_API +const ecs_type_hooks_t* ecs_get_hooks_id( + ecs_world_t *world, + ecs_entity_t id); + +/** @} */ + +/** + * @defgroup ids Ids + * @brief Functions for working with `ecs_id_t`. + * @{ + */ + +/** Returns whether specified id a tag. + * This operation returns whether the specified type is a tag (a component + * without data/size). + * + * An id is a tag when: + * - it is an entity without the EcsComponent component + * - it has an EcsComponent with size member set to 0 + * - it is a pair where both elements are a tag + * - it is a pair where the first element has the EcsTag tag + * + * @param world The world. + * @param id The id. + * @return Whether the provided id is a tag. + */ +FLECS_API +ecs_entity_t ecs_id_is_tag( + const ecs_world_t *world, + ecs_id_t id); + +/** Returns whether specified id is in use. + * This operation returns whether an id is in use in the world. An id is in use + * if it has been added to one or more tables. + * + * @param world The world. + * @param id The id. + * @return Whether the id is in use. + */ +FLECS_API +bool ecs_id_in_use( + ecs_world_t *world, + ecs_id_t id); + +/** Get the type for an id. + * This function returnsthe type information for an id. The specified id can be + * any valid id. For the rules on how type information is determined based on + * id, see ecs_get_typeid. + * + * @param world The world. + * @param id The id. + * @return The type information of the id. + */ +FLECS_API +const ecs_type_info_t* ecs_get_type_info( + const ecs_world_t *world, + ecs_id_t id); + +/** Get the type for an id. + * This operation returns the component id for an id, if the id is associated + * with a type. For a regular component with a non-zero size (an entity with the + * EcsComponent component) the operation will return the entity itself. + * + * For an entity that does not have the EcsComponent component, or with an + * EcsComponent value with size 0, the operation will return 0. + * + * For a pair id the operation will return the type associated with the pair, by + * applying the following rules in order: + * - The first pair element is returned if it is a component + * - 0 is returned if the relationship entity has the Tag property + * - The second pair element is returned if it is a component + * - 0 is returned. + * + * @param world The world. + * @param id The id. + * @return The type id of the id. + */ +FLECS_API +ecs_entity_t ecs_get_typeid( + const ecs_world_t *world, + ecs_id_t id); + +/** Utility to match an id with a pattern. + * This operation returns true if the provided pattern matches the provided + * id. The pattern may contain a wildcard (or wildcards, when a pair). + * + * @param id The id. + * @param pattern The pattern to compare with. + */ +FLECS_API +bool ecs_id_match( + ecs_id_t id, + ecs_id_t pattern); + +/** Utility to check if id is a pair. + * + * @param id The id. + * @return True if id is a pair. + */ +FLECS_API +bool ecs_id_is_pair( + ecs_id_t id); + +/** Utility to check if id is a wildcard. + * + * @param id The id. + * @return True if id is a wildcard or a pair containing a wildcard. + */ +FLECS_API +bool ecs_id_is_wildcard( + ecs_id_t id); + +/** Utility to check if id is valid. + * A valid id is an id that can be added to an entity. Invalid ids are: + * - ids that contain wildcards + * - ids that contain invalid entities + * - ids that are 0 or contain 0 entities + * + * Note that the same rules apply to removing from an entity, with the exception + * of wildcards. + * + * @param world The world. + * @param id The id. + * @return True if the id is valid. + */ +FLECS_API +bool ecs_id_is_valid( + const ecs_world_t *world, + ecs_id_t id); + +/** Get flags associated with id. + * This operation returns the internal flags (see api_flags.h) that are + * associated with the provided id. + * + * @param world The world. + * @param id The id. + * @return Flags associated with the id, or 0 if the id is not in use. + */ +FLECS_API +ecs_flags32_t ecs_id_get_flags( + const ecs_world_t *world, + ecs_id_t id); + +/** Convert id flag to string. + * This operation converts a id flag to a string. + * + * @param id_flags The id flag. + * @return The id flag string, or NULL if no valid id is provided. + */ +FLECS_API +const char* ecs_id_flag_str( + ecs_id_t id_flags); + +/** Convert id to string. + * This operation interprets the structure of an id and converts it to a string. + * + * @param world The world. + * @param id The id to convert to a string. + * @return The id converted to a string. + */ +FLECS_API +char* ecs_id_str( + const ecs_world_t *world, + ecs_id_t id); + +/** Write id string to buffer. + * Same as ecs_id_str but writes result to ecs_strbuf_t. + * + * @param world The world. + * @param id The id to convert to a string. + * @param buf The buffer to write to. + */ +FLECS_API +void ecs_id_str_buf( + const ecs_world_t *world, + ecs_id_t id, + ecs_strbuf_t *buf); + +/** @} */ + +/** + * @defgroup filters Filters + * @brief Functions for working with `ecs_term_t` and `ecs_filter_t`. * @{ */ @@ -6296,7 +6752,6 @@ int ecs_term_finalize( const ecs_world_t *world, ecs_term_t *term); - /** Copy resources of a term to another term. * This operation copies one term to another term. If the source term contains * allocated resources (such as identifiers), they will be duplicated so that @@ -6333,75 +6788,6 @@ FLECS_API void ecs_term_fini( ecs_term_t *term); -/** Utility to match an id with a pattern. - * This operation returns true if the provided pattern matches the provided - * id. The pattern may contain a wildcard (or wildcards, when a pair). - * - * @param id The id. - * @param pattern The pattern to compare with. - */ -FLECS_API -bool ecs_id_match( - ecs_id_t id, - ecs_id_t pattern); - -/** Utility to check if id is a pair. - * - * @param id The id. - * @return True if id is a pair. - */ -FLECS_API -bool ecs_id_is_pair( - ecs_id_t id); - -/** Utility to check if id is a wildcard. - * - * @param id The id. - * @return True if id is a wildcard or a pair containing a wildcard. - */ -FLECS_API -bool ecs_id_is_wildcard( - ecs_id_t id); - -/** Utility to check if id is valid. - * A valid id is an id that can be added to an entity. Invalid ids are: - * - ids that contain wildcards - * - ids that contain invalid entities - * - ids that are 0 or contain 0 entities - * - * Note that the same rules apply to removing from an entity, with the exception - * of wildcards. - * - * @param world The world. - * @param id The id. - * @return True if the id is valid. - */ -FLECS_API -bool ecs_id_is_valid( - const ecs_world_t *world, - ecs_id_t id); - -/** Get flags associated with id. - * This operation returns the internal flags (see api_flags.h) that are - * associated with the provided id. - * - * @param world The world. - * @param id The id. - * @return Flags associated with the id, or 0 if the id is not in use. - */ -FLECS_API -ecs_flags32_t ecs_id_get_flags( - const ecs_world_t *world, - ecs_id_t id); - -/** @} */ - - -/** - * @defgroup filters Filters - * @{ - */ - /** Initialize filter * A filter is a lightweight object that can be used to query for entities in * a world. Filters, as opposed to queries, do not cache results. They are @@ -6427,7 +6813,7 @@ ecs_flags32_t ecs_id_get_flags( */ FLECS_API ecs_filter_t * ecs_filter_init( - const ecs_world_t *world, + ecs_world_t *world, const ecs_filter_desc_t *desc); /** Deinitialize filter. @@ -6577,6 +6963,7 @@ void ecs_filter_copy( /** * @defgroup queries Queries + * @brief Functions for working with `ecs_query_t`. * @{ */ @@ -6878,55 +7265,49 @@ FLECS_API int32_t ecs_query_entity_count( const ecs_query_t *query); -/** Get entity associated with query. - */ -FLECS_API -ecs_entity_t ecs_query_entity( - const ecs_query_t *query); - /** @} */ - /** * @defgroup observer Observers + * @brief Functions for working with events and observers. + * @{ */ typedef struct ecs_event_desc_t { - /* The event id. Only triggers for the specified event will be notified */ + /** The event id. Only triggers for the specified event will be notified */ ecs_entity_t event; - /* Component ids. Only triggers with a matching component id will be + /** Component ids. Only triggers with a matching component id will be * notified. Observers are guaranteed to get notified once, even if they * match more than one id. */ const ecs_type_t *ids; - /* The table for which to notify. */ + /** The table for which to notify. */ ecs_table_t *table; - /* Optional 2nd table to notify. This can be used to communicate the + /** Optional 2nd table to notify. This can be used to communicate the * previous or next table, in case an entity is moved between tables. */ ecs_table_t *other_table; - /* Limit notified entities to ones starting from offset (row) in table */ + /** Limit notified entities to ones starting from offset (row) in table */ int32_t offset; - /* Limit number of notified entities to count. offset+count must be less + /** Limit number of notified entities to count. offset+count must be less * than the total number of entities in the table. If left to 0, it will be * automatically determined by doing ecs_table_count(table) - offset. */ int32_t count; - /* Optional context. Assigned to iter param member */ + /** Single-entity alternative to setting table / offset / count */ + ecs_entity_t entity; + + /** Optional context. Assigned to iter param member */ const void *param; - /* Observable (usually the world) */ + /** Observable (usually the world) */ ecs_poly_t *observable; - /* Table events apply to tables, not the entities in the table. When - * enabled, (up)set triggers are not notified. */ - bool table_event; - - /* When set, events will only be propagated by traversing the relationship */ - ecs_entity_t relationship; + /** Event flags */ + ecs_flags32_t flags; } ecs_event_desc_t; /** Send event. @@ -6963,13 +7344,6 @@ void ecs_emit( ecs_world_t *world, ecs_event_desc_t *desc); -/** @} */ - - -/** - * @defgroup observer Observers - */ - /** Create observer. * Observers are like triggers, but can subscribe for multiple terms. An * observer only triggers when the source of the event meets all terms. @@ -7009,9 +7383,9 @@ void* ecs_get_observer_binding_ctx( /** @} */ - /** * @defgroup iterator Iterators + * @brief Functions for working with `ecs_iter_t`. * @{ */ @@ -7104,6 +7478,17 @@ FLECS_API bool ecs_iter_is_true( ecs_iter_t *it); +/** Get first matching entity from iterator. + * After this operation the application should treat the iterator as if it has + * been iterated until completion. + * + * @param it The iterator. + * @return The first matching entity, or 0 if no entities were matched. + */ +FLECS_API +ecs_entity_t ecs_iter_first( + ecs_iter_t *it); + /** Set value for iterator variable. * This constrains the iterator to return only results for which the variable * equals the specified value. The default value for all variables is @@ -7440,496 +7825,12 @@ FLECS_API char* ecs_iter_str( const ecs_iter_t *it); -/** Find the column index for a given id. - * This operation finds the index of a column in the current type for the - * specified id. For example, if an entity has type Position, Velocity, and the - * application requests the id for the Velocity component, this function will - * return 1. - * - * Note that the column index returned by this function starts from 0, as - * opposed to 1 for the terms. The reason for this is that the returned index - * is equivalent to using the ecs_type_get_index function. - * - * This operation can be used to request columns that are not requested by a - * query. For example, a query may request Position, Velocity, but an entity - * may also have Mass. With this function the iterator can request the data for - * Mass as well, when used in combination with ecs_iter_column. - * - * @param it The iterator. - * @return The type of the currently iterated entity/entities. - */ -FLECS_API -int32_t ecs_iter_find_column( - const ecs_iter_t *it, - ecs_id_t id); - -/** Obtain data for a column index. - * This operation can be used with the id obtained from ecs_iter_find_column to - * request data from the currently iterated over entity/entities that is not - * requested by the query. - * - * The data in the returned pointer can be accessed using the same index as - * the one used to access the arrays returned by the ecs_field function. - * - * The provided size must be either 0 or must match the size of the datatype - * of the returned array. If the size does not match, the operation may assert. - * The size can be dynamically obtained with ecs_iter_column_size. - * - * Note that this function can be used together with iter::type to - * dynamically iterate all data that the matched entities have. An application - * can use the ecs_vector_count function to obtain the number of elements in a - * type. All indices from 0..type->count are valid column indices. - * - * Additionally, note that this provides unprotected access to the column data. - * An iterator cannot know or prevent accessing columns that are not queried for - * and thus applications should only use this when it can be guaranteed that - * there are no other threads reading/writing the same column data. - * - * @param it The iterator. - * @param size The size of the column. - * @param index The index of the column. - * @return The data belonging to the column. - */ -FLECS_API -void* ecs_iter_column_w_size( - const ecs_iter_t *it, - size_t size, - int32_t index); - -/** Obtain size for a column index. - * This operation obtains the size for a column. The size is equal to the size - * of the datatype associated with the column. - * - * @param it The iterator. - * @param index The index of the column. - * @return The size belonging to the column. - */ -FLECS_API -size_t ecs_iter_column_size( - const ecs_iter_t *it, - int32_t index); - -/** @} */ - - -/** - * @defgroup staging Staging - * @{ - */ - -/** Begin frame. - * When an application does not use ecs_progress to control the main loop, it - * can still use Flecs features such as FPS limiting and time measurements. This - * operation needs to be invoked whenever a new frame is about to get processed. - * - * Calls to ecs_frame_begin must always be followed by ecs_frame_end. - * - * The function accepts a delta_time parameter, which will get passed to - * systems. This value is also used to compute the amount of time the function - * needs to sleep to ensure it does not exceed the target_fps, when it is set. - * When 0 is provided for delta_time, the time will be measured. - * - * This function should only be ran from the main thread. - * - * @param world The world. - * @param delta_time Time elapsed since the last frame. - * @return The provided delta_time, or measured time if 0 was provided. - */ -FLECS_API -ecs_ftime_t ecs_frame_begin( - ecs_world_t *world, - ecs_ftime_t delta_time); - -/** End frame. - * This operation must be called at the end of the frame, and always after - * ecs_frame_begin. - * - * @param world The world. - */ -FLECS_API -void ecs_frame_end( - ecs_world_t *world); - -/** Begin readonly mode. - * Readonly mode guarantees that no mutations will occur on the world, which - * makes the world safe to access from multiple threads. While the world is in - * readonly mode, operations are deferred. - * - * Note that while similar to ecs_defer_begin, deferring only does not guarantee - * the world is not mutated. Operations that are not deferred (like creating a - * query) update data structures on the world and are allowed when deferring is - * enabled, but not when the world is in readonly mode. - * - * A call to ecs_readonly_begin must be followed up with ecs_readonly_end. - * - * The ecs_progress() function automatically enables readonly mode while systems - * are executed. - * - * When a world has more than one stage, the specific stage must be provided to - * mutating ECS operations. Failing to do so will throw a readonly assert. A - * world typically has more than one stage when using threads. An example: - * - * ecs_set_stage_count(world, 2); - * ecs_stage_t *stage = ecs_get_stage(world, 1); - * - * ecs_readonly_begin(world); - * ecs_add(world, e, Tag); // readonly assert - * ecs_add(stage, e, Tag); // OK - * - * @param world The world - * @return Whether world is in readonly mode. - */ -FLECS_API -bool ecs_readonly_begin( - ecs_world_t *world); - -/** End readonly mode. - * This operation ends readonly mode, and must be called after - * ecs_readonly_begin. Operations that were deferred while the world was in - * readonly mode will be flushed. - * - * @param world The world - */ -FLECS_API -void ecs_readonly_end( - ecs_world_t *world); - -/** Merge world or stage. - * When automatic merging is disabled, an application can call this - * operation on either an individual stage, or on the world which will merge - * all stages. This operation may only be called when staging is not enabled - * (either after progress() or after readonly_end()). - * - * This operation may be called on an already merged stage or world. - * - * @param world The world. - */ -FLECS_API -void ecs_merge( - ecs_world_t *world); - -/** Defer operations until end of frame. - * When this operation is invoked while iterating, operations inbetween the - * defer_begin and defer_end operations are executed at the end of the frame. - * - * This operation is thread safe. - * - * @param world The world. - * @return true if world changed from non-deferred mode to deferred mode. - */ -FLECS_API -bool ecs_defer_begin( - ecs_world_t *world); - -/** Test if deferring is enabled for current stage. - * - * @param world The world. - * @return True if deferred, false if not. - */ -FLECS_API -bool ecs_is_deferred( - const ecs_world_t *world); - -/** End block of operations to defer. - * See defer_begin. - * - * This operation is thread safe. - * - * @param world The world. - * @return true if world changed from deferred mode to non-deferred mode. - */ -FLECS_API -bool ecs_defer_end( - ecs_world_t *world); - -/** Suspend deferring but do not flush queue. - * This operation can be used to do an undeferred operation while not flushing - * the operations in the queue. - * - * An application should invoke ecs_defer_resume before ecs_defer_end is called. - * The operation may only be called when deferring is enabled. - * - * @param world The world. - */ -FLECS_API -void ecs_defer_suspend( - ecs_world_t *world); - -/** Resume deferring. - * See ecs_defer_suspend. - * - * @param world The world. - */ -FLECS_API -void ecs_defer_resume( - ecs_world_t *world); - -/** Enable/disable automerging for world or stage. - * When automerging is enabled, staged data will automatically be merged with - * the world when staging ends. This happens at the end of progress(), at a - * sync point or when readonly_end() is called. - * - * Applications can exercise more control over when data from a stage is merged - * by disabling automerging. This requires an application to explicitly call - * merge() on the stage. - * - * When this function is invoked on the world, it sets all current stages to - * the provided value and sets the default for new stages. When this function is - * invoked on a stage, automerging is only set for that specific stage. - * - * @param world The world. - * @param automerge Whether to enable or disable automerging. - */ -FLECS_API -void ecs_set_automerge( - ecs_world_t *world, - bool automerge); - -/** Configure world to have N stages. - * This initializes N stages, which allows applications to defer operations to - * multiple isolated defer queues. This is typically used for applications with - * multiple threads, where each thread gets its own queue, and commands are - * merged when threads are synchronized. - * - * Note that the ecs_set_threads function already creates the appropriate - * number of stages. The set_stage_count() operation is useful for applications that - * want to manage their own stages and/or threads. - * - * @param world The world. - * @param stages The number of stages. - */ -FLECS_API -void ecs_set_stage_count( - ecs_world_t *world, - int32_t stages); - -/** Get number of configured stages. - * Return number of stages set by ecs_set_stage_count. - * - * @param world The world. - * @return The number of stages used for threading. - */ -FLECS_API -int32_t ecs_get_stage_count( - const ecs_world_t *world); - -/** Get current stage id. - * The stage id can be used by an application to learn about which stage it is - * using, which typically corresponds with the worker thread id. - * - * @param world The world. - * @return The stage id. - */ -FLECS_API -int32_t ecs_get_stage_id( - const ecs_world_t *world); - -/** Get stage-specific world pointer. - * Flecs threads can safely invoke the API as long as they have a private - * context to write to, also referred to as the stage. This function returns a - * pointer to a stage, disguised as a world pointer. - * - * Note that this function does not(!) create a new world. It simply wraps the - * existing world in a thread-specific context, which the API knows how to - * unwrap. The reason the stage is returned as an ecs_world_t is so that it - * can be passed transparently to the existing API functions, vs. having to - * create a dediated API for threading. - * - * @param world The world. - * @param stage_id The index of the stage to retrieve. - * @return A thread-specific pointer to the world. - */ -FLECS_API -ecs_world_t* ecs_get_stage( - const ecs_world_t *world, - int32_t stage_id); - -/** Get actual world from world. - * - * @param world A pointer to a stage or the world. - * @return The world. - */ -FLECS_API -const ecs_world_t* ecs_get_world( - const ecs_poly_t *world); - -/** Test whether the current world is readonly. - * This function allows the code to test whether the currently used world - * is readonly or whether it allows for writing. - * - * @param world A pointer to a stage or the world. - * @return True if the world or stage is readonly. - */ -FLECS_API -bool ecs_stage_is_readonly( - const ecs_world_t *world); - -/** Create asynchronous stage. - * An asynchronous stage can be used to asynchronously queue operations for - * later merging with the world. An asynchronous stage is similar to a regular - * stage, except that it does not allow reading from the world. - * - * Asynchronous stages are never merged automatically, and must therefore be - * manually merged with the ecs_merge function. It is not necessary to call - * defer_begin or defer_end before and after enqueuing commands, as an - * asynchronous stage unconditionally defers operations. - * - * The application must ensure that no commands are added to the stage while the - * stage is being merged. - * - * An asynchronous stage must be cleaned up by ecs_async_stage_free. - * - * @param world The world. - * @return The stage. - */ -FLECS_API -ecs_world_t* ecs_async_stage_new( - ecs_world_t *world); - -/** Free asynchronous stage. - * The provided stage must be an asynchronous stage. If a non-asynchronous stage - * is provided, the operation will fail. - * - * @param stage The stage to free. - */ -FLECS_API -void ecs_async_stage_free( - ecs_world_t *stage); - -/** Test whether provided stage is asynchronous. - * - * @param stage The stage. - * @return True when the stage is asynchronous, false for a regular stage or - * world. - */ -FLECS_API -bool ecs_stage_is_async( - ecs_world_t *stage); /** @} */ /** - * @defgroup id_search_functions Search functions for component ids. - * @brief Low-level functions to search for component ids in table types. - * @{ - */ - -/** Search for component id in table type. - * This operation returns the index of first occurrance of the id in the table - * type. The id may be a wildcard. - * - * When id_out is provided, the function will assign it with the found id. The - * found id may be different from the provided id if it is a wildcard. - * - * This is a constant time operation. - * - * @param world The world. - * @param table The table. - * @param id The id to search for. - * @param id_out If provided, it will be set to the found id (optional). - * @return The index of the id in the table type. - */ -FLECS_API -int32_t ecs_search( - const ecs_world_t *world, - const ecs_table_t *table, - ecs_id_t id, - ecs_id_t *id_out); - -/** Search for component id in table type starting from an offset. - * This operation is the same as ecs_search, but starts searching from an offset - * in the table type. - * - * This operation is typically called in a loop where the resulting index is - * used in the next iteration as offset: - * - * int32_t index = -1; - * while ((index = ecs_search_offset(world, table, offset, id, NULL))) { - * // do stuff - * } - * - * Depending on how the operation is used it is either linear or constant time. - * When the id has the form (id) or (rel, *) and the operation is invoked as - * in the above example, it is guaranteed to be constant time. - * - * If the provided id has the form (*, tgt) the operation takes linear time. The - * reason for this is that ids for an target are not packed together, as they - * are sorted relationship first. - * - * If the id at the offset does not match the provided id, the operation will do - * a linear search to find a matching id. - * - * @param world The world. - * @param table The table. - * @param offset Offset from where to start searching. - * @param id The id to search for. - * @param id_out If provided, it will be set to the found id (optional). - * @return The index of the id in the table type. - */ -FLECS_API -int32_t ecs_search_offset( - const ecs_world_t *world, - const ecs_table_t *table, - int32_t offset, - ecs_id_t id, - ecs_id_t *id_out); - -/** Search for component/relationship id in table type starting from an offset. - * This operation is the same as ecs_search_offset, but has the additional - * capability of traversing relationships to find a component. For example, if - * an application wants to find a component for either the provided table or a - * prefab (using the IsA relationship) of that table, it could use the operation - * like this: - * - * int32_t index = ecs_search_relation( - * world, // the world - * table, // the table - * 0, // offset 0 - * ecs_id(Position), // the component id - * EcsIsA, // the relationship to traverse - * 0, // start at depth 0 (the table itself) - * 0, // no depth limit - * NULL, // (optional) entity on which component was found - * NULL, // see above - * NULL); // internal type with information about matched id - * - * The operation searches depth first. If a table type has 2 IsA relationships, the - * operation will first search the IsA tree of the first relationship. - * - * When choosing betwen ecs_search, ecs_search_offset and ecs_search_relation, - * the simpler the function the better its performance. - * - * @param world The world. - * @param table The table. - * @param offset Offset from where to start searching. - * @param id The id to search for. - * @param rel The relationship to traverse (optional). - * @param flags Whether to search EcsSelf and/or EcsUp. - * @param subject_out If provided, it will be set to the matched entity. - * @param id_out If provided, it will be set to the found id (optional). - * @param tr_out Internal datatype. - * @return The index of the id in the table type. - */ -FLECS_API -int32_t ecs_search_relation( - const ecs_world_t *world, - const ecs_table_t *table, - int32_t offset, - ecs_id_t id, - ecs_entity_t rel, - ecs_flags32_t flags, /* EcsSelf and/or EcsUp */ - ecs_entity_t *subject_out, - ecs_id_t *id_out, - struct ecs_table_record_t **tr_out); - -/** @} */ - -/** - * @defgroup table_functions Public table operations - * @brief Low-level table functions. These functions are intended to enable the - * creation of higher-level operations. It is not recommended to use - * these operations directly in application code as they do not provide - * the same safety guarantees as the other APIs. + * @defgroup tables Tables + * @brief Functions for working with `ecs_table_t`. * @{ */ @@ -7946,12 +7847,15 @@ const ecs_type_t* ecs_table_get_type( * This operation returns the component array for the provided index. * * @param table The table. + * @param index The index of the column (corresponds with element in type). + * @param offset The index of the first row to return (0 for entire column). * @return The component array, or NULL if the index is not a component. */ FLECS_API void* ecs_table_get_column( - ecs_table_t *table, - int32_t index); + const ecs_table_t *table, + int32_t index, + int32_t offset); /** Get column index for id. * This operation returns the index for an id in the table's type. @@ -7967,6 +7871,37 @@ int32_t ecs_table_get_index( const ecs_table_t *table, ecs_id_t id); +/** Get column from table by component id. + * This operation returns the component array for the provided component id. + * + * @param table The table. + * @param id The component id for the column. + * @param offset The index of the first row to return (0 for entire column). + * @return The component array, or NULL if the index is not a component. + */ +FLECS_API +void* ecs_table_get_id( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_id_t id, + int32_t offset); + +/** Return depth for table in tree for relationship rel. + * Depth is determined by counting the number of targets encountered while + * traversing up the relationship tree for rel. Only acyclic relationships are + * supported. + * + * @param world The world. + * @param table The table. + * @param rel The relationship. + * @return The depth of the table in the tree. + */ +FLECS_API +int32_t ecs_table_get_depth( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_entity_t rel); + /** Get storage type for table. * * @param table The table. @@ -8084,8 +8019,7 @@ void ecs_table_swap_rows( ecs_world_t* world, ecs_table_t* table, int32_t row_1, - int32_t row_2 -); + int32_t row_2); /** Commit (move) entity to a table. * This operation moves an entity from its current table to the specified @@ -8131,10 +8065,118 @@ void* ecs_record_get_column( int32_t column, size_t c_size); +/** Search for component id in table type. + * This operation returns the index of first occurrance of the id in the table + * type. The id may be a wildcard. + * + * When id_out is provided, the function will assign it with the found id. The + * found id may be different from the provided id if it is a wildcard. + * + * This is a constant time operation. + * + * @param world The world. + * @param table The table. + * @param id The id to search for. + * @param id_out If provided, it will be set to the found id (optional). + * @return The index of the id in the table type. + */ +FLECS_API +int32_t ecs_search( + const ecs_world_t *world, + const ecs_table_t *table, + ecs_id_t id, + ecs_id_t *id_out); + +/** Search for component id in table type starting from an offset. + * This operation is the same as ecs_search, but starts searching from an offset + * in the table type. + * + * This operation is typically called in a loop where the resulting index is + * used in the next iteration as offset: + * + * int32_t index = -1; + * while ((index = ecs_search_offset(world, table, offset, id, NULL))) { + * // do stuff + * } + * + * Depending on how the operation is used it is either linear or constant time. + * When the id has the form (id) or (rel, *) and the operation is invoked as + * in the above example, it is guaranteed to be constant time. + * + * If the provided id has the form (*, tgt) the operation takes linear time. The + * reason for this is that ids for an target are not packed together, as they + * are sorted relationship first. + * + * If the id at the offset does not match the provided id, the operation will do + * a linear search to find a matching id. + * + * @param world The world. + * @param table The table. + * @param offset Offset from where to start searching. + * @param id The id to search for. + * @param id_out If provided, it will be set to the found id (optional). + * @return The index of the id in the table type. + */ +FLECS_API +int32_t ecs_search_offset( + const ecs_world_t *world, + const ecs_table_t *table, + int32_t offset, + ecs_id_t id, + ecs_id_t *id_out); + +/** Search for component/relationship id in table type starting from an offset. + * This operation is the same as ecs_search_offset, but has the additional + * capability of traversing relationships to find a component. For example, if + * an application wants to find a component for either the provided table or a + * prefab (using the IsA relationship) of that table, it could use the operation + * like this: + * + * int32_t index = ecs_search_relation( + * world, // the world + * table, // the table + * 0, // offset 0 + * ecs_id(Position), // the component id + * EcsIsA, // the relationship to traverse + * 0, // start at depth 0 (the table itself) + * 0, // no depth limit + * NULL, // (optional) entity on which component was found + * NULL, // see above + * NULL); // internal type with information about matched id + * + * The operation searches depth first. If a table type has 2 IsA relationships, the + * operation will first search the IsA tree of the first relationship. + * + * When choosing betwen ecs_search, ecs_search_offset and ecs_search_relation, + * the simpler the function the better its performance. + * + * @param world The world. + * @param table The table. + * @param offset Offset from where to start searching. + * @param id The id to search for. + * @param rel The relationship to traverse (optional). + * @param flags Whether to search EcsSelf and/or EcsUp. + * @param subject_out If provided, it will be set to the matched entity. + * @param id_out If provided, it will be set to the found id (optional). + * @param tr_out Internal datatype. + * @return The index of the id in the table type. + */ +FLECS_API +int32_t ecs_search_relation( + const ecs_world_t *world, + const ecs_table_t *table, + int32_t offset, + ecs_id_t id, + ecs_entity_t rel, + ecs_flags32_t flags, /* EcsSelf and/or EcsUp */ + ecs_entity_t *subject_out, + ecs_id_t *id_out, + struct ecs_table_record_t **tr_out); + /** @} */ /** - * @defgroup values API for dynamic values. + * @defgroup values Values * @brief Construct, destruct, copy and move dynamically created values. * @{ */ @@ -8299,16 +8341,39 @@ int ecs_value_move_ctor( void* dst, void *src); +/** @} */ + +/** @} */ + /** - * @file flecs_c.h - * @brief Extends the core API with convenience functions/macros for C applications. + * @defgroup c_addons Addons + * @brief C APIs for addons. + * + * \ingroup c + * + * @{ + * @} + */ + +/** + * @file addons/flecs_c.h + * @brief Extends the core API with convenience macros for C applications. */ #ifndef FLECS_C_ #define FLECS_C_ /** - * @defgroup create_macros Macros that help with creation of ECS objects. + * @defgroup flecs_c Macro API + * @brief Convenience macro's for C API + * + * \ingroup c + * @{ + */ + +/** + * @defgroup flecs_c_creation Creation macro's + * @brief Convenience macro's for creating entities, components and observers * @{ */ @@ -8316,6 +8381,14 @@ int ecs_value_move_ctor( #define ECS_DECLARE(id)\ ecs_entity_t id, ecs_id(id) +/** Forward declare an entity. */ +#define ECS_ENTITY_DECLARE ECS_DECLARE + +/** Define a forward declared entity. + * + * Example: + * ECS_ENTITY_DEFINE(world, MyEntity, Position, Velocity); + */ #define ECS_ENTITY_DEFINE(world, id_, ...) \ { \ ecs_entity_desc_t desc = {0}; \ @@ -8329,24 +8402,64 @@ int ecs_value_move_ctor( (void)id_; \ (void)ecs_id(id_); +/** Declare & define an entity. + * + * Example: + * ECS_ENTITY(world, MyEntity, Position, Velocity); + */ #define ECS_ENTITY(world, id, ...) \ ecs_entity_t ecs_id(id); \ ecs_entity_t id = 0; \ ECS_ENTITY_DEFINE(world, id, __VA_ARGS__); -#define ECS_TAG_DEFINE(world, id) ECS_ENTITY_DEFINE(world, id, 0) -#define ECS_TAG(world, id) ECS_ENTITY(world, id, 0) +/** Forward declare a tag. */ +#define ECS_TAG_DECLARE ECS_DECLARE +/** Define a forward declared tag. + * + * Example: + * ECS_TAG_DEFINE(world, MyTag); + */ +#define ECS_TAG_DEFINE(world, id) ECS_ENTITY_DEFINE(world, id, 0) + +/** Declare & define a tag. + * + * Example: + * ECS_TAG(world, MyTag); + */ +#define ECS_TAG(world, id) ECS_ENTITY(world, id, 0) + +/** Forward declare a prefab. */ +#define ECS_PREFAB_DECLARE ECS_DECLARE + +/** Define a forward declared prefab. + * + * Example: + * ECS_PREFAB_DEFINE(world, MyPrefab, Position, Velocity); + */ #define ECS_PREFAB_DEFINE(world, id, ...) ECS_ENTITY_DEFINE(world, id, Prefab, __VA_ARGS__) -#define ECS_PREFAB(world, id, ...) ECS_ENTITY(world, id, Prefab, __VA_ARGS__) -/* Use for declaring component identifiers */ +/** Declare & define a prefab. + * + * Example: + * ECS_PREFAB(world, MyPrefab, Position, Velocity); + */ +#define ECS_PREFAB(world, id, ...) ECS_ENTITY(world, id, Prefab, __VA_ARGS__) + +/** Forward declare a component. */ #define ECS_COMPONENT_DECLARE(id) ecs_entity_t ecs_id(id) + +/** Define a forward declared component. + * + * Example: + * ECS_COMPONENT_DEFINE(world, Position); + */ #define ECS_COMPONENT_DEFINE(world, id_) \ {\ ecs_component_desc_t desc = {0}; \ ecs_entity_desc_t edesc = {0}; \ edesc.id = ecs_id(id_); \ + edesc.use_low_id = true; \ edesc.name = #id_; \ edesc.symbol = #id_; \ desc.entity = ecs_entity_init(world, &edesc); \ @@ -8356,15 +8469,24 @@ int ecs_value_move_ctor( ecs_assert(ecs_id(id_) != 0, ECS_INVALID_PARAMETER, NULL);\ } +/** Declare & define a component. + * + * Example: + * ECS_COMPONENT(world, Position); + */ #define ECS_COMPONENT(world, id)\ ecs_entity_t ecs_id(id) = 0;\ ECS_COMPONENT_DEFINE(world, id);\ (void)ecs_id(id) -/* Use for declaring observer and system identifiers */ -#define ECS_SYSTEM_DECLARE(id) ecs_entity_t ecs_id(id) +/* Forward declare an observer. */ +#define ECS_OBSERVER_DECLARE(id) ecs_entity_t ecs_id(id) -/* Observers */ +/** Define a forward declared observer. + * + * Example: + * ECS_OBSERVER_DEFINE(world, AddPosition, EcsOnAdd, Position); + */ #define ECS_OBSERVER_DEFINE(world, id_, kind, ...)\ {\ ecs_observer_desc_t desc = {0};\ @@ -8379,6 +8501,11 @@ int ecs_value_move_ctor( ecs_assert(ecs_id(id_) != 0, ECS_INVALID_PARAMETER, NULL);\ } +/** Declare & define an observer. + * + * Example: + * ECS_OBSERVER(world, AddPosition, EcsOnAdd, Position); + */ #define ECS_OBSERVER(world, id, kind, ...)\ ecs_entity_t ecs_id(id) = 0; \ ECS_OBSERVER_DEFINE(world, id, kind, __VA_ARGS__);\ @@ -8386,27 +8513,436 @@ int ecs_value_move_ctor( (void)ecs_id(id);\ (void)id; -/** @} */ - -/** - * @defgroup init_macros Convenience macros for using init functions with less code - * @{ +/** Shorthand for creating an entity with ecs_entity_init. + * + * Example: + * ecs_entity(world, { + * .name = "MyEntity" + * }); */ - #define ecs_entity(world, ...)\ ecs_entity_init(world, &(ecs_entity_desc_t) __VA_ARGS__ ) +/** Shorthand for creating a filter with ecs_filter_init. + * + * Example: + * ecs_filter(world, { + * .terms = {{ ecs_id(Position) }} + * }); + */ #define ecs_filter(world, ...)\ ecs_filter_init(world, &(ecs_filter_desc_t) __VA_ARGS__ ) +/** Shorthand for creating a query with ecs_query_init. + * + * Example: + * ecs_query(world, { + * .filter.terms = {{ ecs_id(Position) }} + * }); + */ #define ecs_query(world, ...)\ ecs_query_init(world, &(ecs_query_desc_t) __VA_ARGS__ ) +/** Shorthand for creating an observer with ecs_observer_init. + * + * Example: + * ecs_observer(world, { + * .filter.terms = {{ ecs_id(Position) }}, + * .events = { EcsOnAdd }, + * .callback = AddPosition + * }); + */ #define ecs_observer(world, ...)\ ecs_observer_init(world, &(ecs_observer_desc_t) __VA_ARGS__ ) +/** @} */ + /** - * @defgroup sorting_macros Convenience macros that help with component comparison and sorting + * @defgroup flecs_c_type_safe Type Safe API + * @brief Macro's that wrap around core functions to provide a "type safe" API in C + * @{ + */ + +/** + * @defgroup flecs_c_entities Entity API + * @{ + */ + +/** + * @defgroup flecs_c_creation_deletion Creation & Deletion + * @{ + */ + +#define ecs_new(world, T) ecs_new_w_id(world, ecs_id(T)) + +#define ecs_new_w_pair(world, first, second)\ + ecs_new_w_id(world, ecs_pair(first, second)) + +#define ecs_bulk_new(world, component, count)\ + ecs_bulk_new_w_id(world, ecs_id(component), count) + +#define ecs_new_entity(world, n)\ + ecs_entity_init(world, &(ecs_entity_desc_t){\ + .name = n,\ + }) + +#define ecs_new_prefab(world, n)\ + ecs_entity_init(world, &(ecs_entity_desc_t){\ + .name = n,\ + .add = {EcsPrefab}\ + }) + +#define ecs_delete_children(world, parent)\ + ecs_delete_with(world, ecs_pair(EcsChildOf, parent)) + +/** @} */ + +/** + * @defgroup flecs_c_adding_removing Adding & Removing + * @{ + */ + +#define ecs_add(world, entity, T)\ + ecs_add_id(world, entity, ecs_id(T)) + +#define ecs_add_pair(world, subject, first, second)\ + ecs_add_id(world, subject, ecs_pair(first, second)) + + +#define ecs_remove(world, entity, T)\ + ecs_remove_id(world, entity, ecs_id(T)) + +#define ecs_remove_pair(world, subject, first, second)\ + ecs_remove_id(world, subject, ecs_pair(first, second)) + + +#define ecs_override(world, entity, T)\ + ecs_override_id(world, entity, ecs_id(T)) + +#define ecs_override_pair(world, subject, first, second)\ + ecs_override_id(world, subject, ecs_pair(first, second)) + +/** @} */ + +/** + * @defgroup flecs_c_getting_setting Getting & Setting + * @{ + */ + +#define ecs_set_ptr(world, entity, component, ptr)\ + ecs_set_id(world, entity, ecs_id(component), sizeof(component), ptr) + +#define ecs_set(world, entity, component, ...)\ + ecs_set_id(world, entity, ecs_id(component), sizeof(component), &(component)__VA_ARGS__) + +#define ecs_set_pair(world, subject, First, second, ...)\ + ecs_set_id(world, subject,\ + ecs_pair(ecs_id(First), second),\ + sizeof(First), &(First)__VA_ARGS__) + +#define ecs_set_pair_second(world, subject, first, Second, ...)\ + ecs_set_id(world, subject,\ + ecs_pair(first, ecs_id(Second)),\ + sizeof(Second), &(Second)__VA_ARGS__) + +#define ecs_set_pair_object ecs_set_pair_second + +#define ecs_set_override(world, entity, T, ...)\ + ecs_add_id(world, entity, ECS_OVERRIDE | ecs_id(T));\ + ecs_set(world, entity, T, __VA_ARGS__) + +#define ecs_emplace(world, entity, T)\ + (ECS_CAST(T*, ecs_emplace_id(world, entity, ecs_id(T)))) + +#define ecs_get(world, entity, T)\ + (ECS_CAST(const T*, ecs_get_id(world, entity, ecs_id(T)))) + +#define ecs_get_pair(world, subject, First, second)\ + (ECS_CAST(const First*, ecs_get_id(world, subject,\ + ecs_pair(ecs_id(First), second)))) + +#define ecs_get_pair_second(world, subject, first, Second)\ + (ECS_CAST(const Second*, ecs_get_id(world, subject,\ + ecs_pair(first, ecs_id(Second))))) + +#define ecs_get_pair_object ecs_get_pair_second + +#define ecs_record_get(world, record, T)\ + (ECS_CAST(const T*, ecs_record_get_id(world, record, ecs_id(T)))) + +#define ecs_record_get_pair(world, record, First, second)\ + (ECS_CAST(const First*, ecs_record_get_id(world, record, \ + ecs_pair(ecs_id(First), second)))) + +#define ecs_record_get_pair_second(world, record, first, Second)\ + (ECS_CAST(const Second*, ecs_record_get_id(world, record,\ + ecs_pair(first, ecs_id(Second))))) + +#define ecs_record_get_mut(world, record, T)\ + (ECS_CAST(T*, ecs_record_get_mut_id(world, record, ecs_id(T)))) + +#define ecs_record_get_mut_pair(world, record, First, second)\ + (ECS_CAST(First*, ecs_record_get_mut_id(world, record, \ + ecs_pair(ecs_id(First), second)))) + +#define ecs_record_get_mut_pair_second(world, record, first, Second)\ + (ECS_CAST(Second*, ecs_record_get_mut_id(world, record,\ + ecs_pair(first, ecs_id(Second))))) + +#define ecs_record_get_mut_pair_object ecs_record_get_mut_pair_second + +#define ecs_ref_init(world, entity, T)\ + ecs_ref_init_id(world, entity, ecs_id(T)) + +#define ecs_ref_get(world, ref, T)\ + (ECS_CAST(const T*, ecs_ref_get_id(world, ref, ecs_id(T)))) + +#define ecs_get_mut(world, entity, T)\ + (ECS_CAST(T*, ecs_get_mut_id(world, entity, ecs_id(T)))) + +#define ecs_get_mut_pair(world, subject, First, second)\ + (ECS_CAST(First*, ecs_get_mut_id(world, subject,\ + ecs_pair(ecs_id(First), second)))) + +#define ecs_get_mut_pair_second(world, subject, first, Second)\ + (ECS_CAST(Second*, ecs_get_mut_id(world, subject,\ + ecs_pair(first, ecs_id(Second))))) + +#define ecs_get_mut_pair_object ecs_get_mut_pair_second + +#define ecs_modified(world, entity, component)\ + ecs_modified_id(world, entity, ecs_id(component)) + +#define ecs_modified_pair(world, subject, first, second)\ + ecs_modified_id(world, subject, ecs_pair(first, second)) + +/** @} */ + +/** + * @defgroup flecs_c_singletons Singletons + * @{ + */ + +#define ecs_singleton_add(world, comp)\ + ecs_add(world, ecs_id(comp), comp) + +#define ecs_singleton_remove(world, comp)\ + ecs_remove(world, ecs_id(comp), comp) + +#define ecs_singleton_get(world, comp)\ + ecs_get(world, ecs_id(comp), comp) + +#define ecs_singleton_set(world, comp, ...)\ + ecs_set(world, ecs_id(comp), comp, __VA_ARGS__) + +#define ecs_singleton_get_mut(world, comp)\ + ecs_get_mut(world, ecs_id(comp), comp) + +#define ecs_singleton_modified(world, comp)\ + ecs_modified(world, ecs_id(comp), comp) + +/** @} */ + +/** + * @defgroup flecs_c_has Has, Owns, Shares + * @{ + */ + +#define ecs_has(world, entity, T)\ + ecs_has_id(world, entity, ecs_id(T)) + +#define ecs_has_pair(world, entity, first, second)\ + ecs_has_id(world, entity, ecs_pair(first, second)) + +#define ecs_owns_id(world, entity, id)\ + (ecs_search(world, ecs_get_table(world, entity), id, 0) != -1) + +#define ecs_owns_pair(world, entity, first, second)\ + ecs_owns_id(world, entity, ecs_pair(first, second)) + +#define ecs_owns(world, entity, T)\ + ecs_owns_id(world, entity, ecs_id(T)) + +#define ecs_shares_id(world, entity, id)\ + (ecs_search_relation(world, ecs_get_table(world, entity), 0, ecs_id(id), \ + EcsIsA, 1, 0, 0, 0, 0) != -1) + +#define ecs_shares_pair(world, entity, first, second)\ + (ecs_shares_id(world, entity, ecs_pair(first, second))) + +#define ecs_shares(world, entity, T)\ + (ecs_shares_id(world, entity, ecs_id(T))) + +/** @} */ + +/** + * @defgroup flecs_c_enable_disable Enabling & Disabling + * @{ + */ + +#define ecs_enable_component(world, entity, T, enable)\ + ecs_enable_id(world, entity, ecs_id(T), enable) + +#define ecs_is_enabled_component(world, entity, T)\ + ecs_is_enabled_id(world, entity, ecs_id(T)) + +#define ecs_enable_pair(world, entity, First, second, enable)\ + ecs_enable_id(world, entity, ecs_pair(ecs_id(First), second), enable) + +#define ecs_is_enabled_pair(world, entity, First, second)\ + ecs_is_enabled_id(world, entity, ecs_pair(ecs_id(First), second)) + +/** @} */ + +/** + * @defgroup flecs_c_entity_names Entity Names + * @{ + */ + +#define ecs_lookup_path(world, parent, path)\ + ecs_lookup_path_w_sep(world, parent, path, ".", NULL, true) + +#define ecs_lookup_fullpath(world, path)\ + ecs_lookup_path_w_sep(world, 0, path, ".", NULL, true) + +#define ecs_get_path(world, parent, child)\ + ecs_get_path_w_sep(world, parent, child, ".", NULL) + +#define ecs_get_fullpath(world, child)\ + ecs_get_path_w_sep(world, 0, child, ".", NULL) + +#define ecs_get_fullpath_buf(world, child, buf)\ + ecs_get_path_w_sep_buf(world, 0, child, ".", NULL, buf) + +#define ecs_new_from_path(world, parent, path)\ + ecs_new_from_path_w_sep(world, parent, path, ".", NULL) + +#define ecs_new_from_fullpath(world, path)\ + ecs_new_from_path_w_sep(world, 0, path, ".", NULL) + +#define ecs_add_path(world, entity, parent, path)\ + ecs_add_path_w_sep(world, entity, parent, path, ".", NULL) + +#define ecs_add_fullpath(world, entity, path)\ + ecs_add_path_w_sep(world, entity, 0, path, ".", NULL) + +/** @} */ + +/** @} */ + +/** + * @defgroup flecs_c_components Component API + * @{ + */ + +#define ecs_set_hooks(world, T, ...)\ + ecs_set_hooks_id(world, ecs_id(T), &(ecs_type_hooks_t)__VA_ARGS__) + +#define ecs_get_hooks(world, T)\ + ecs_get_hooks_id(world, ecs_id(T)); + +/** Declare a constructor. + * Example: + * ECS_CTOR(MyType, ptr, { ptr->value = NULL; }); + */ +#define ECS_CTOR(type, var, ...)\ + ECS_XTOR_IMPL(type, ctor, var, __VA_ARGS__) + +/** Declare a destructor. + * Example: + * ECS_DTOR(MyType, ptr, { free(ptr->value); }); + */ +#define ECS_DTOR(type, var, ...)\ + ECS_XTOR_IMPL(type, dtor, var, __VA_ARGS__) + +/** Declare a copy action. + * Example: + * ECS_COPY(MyType, dst, src, { dst->value = strdup(src->value); }); + */ +#define ECS_COPY(type, dst_var, src_var, ...)\ + ECS_COPY_IMPL(type, dst_var, src_var, __VA_ARGS__) + +/** Declare a move action. + * Example: + * ECS_MOVE(MyType, dst, src, { dst->value = src->value; src->value = 0; }); + */ +#define ECS_MOVE(type, dst_var, src_var, ...)\ + ECS_MOVE_IMPL(type, dst_var, src_var, __VA_ARGS__) + +/** Declare component hooks. + * Example: + * ECS_ON_SET(MyType, ptr, { printf("%d\n", ptr->value); }); + */ +#define ECS_ON_ADD(type, ptr, ...)\ + ECS_HOOK_IMPL(type, ecs_on_add(type), ptr, __VA_ARGS__) +#define ECS_ON_REMOVE(type, ptr, ...)\ + ECS_HOOK_IMPL(type, ecs_on_remove(type), ptr, __VA_ARGS__) +#define ECS_ON_SET(type, ptr, ...)\ + ECS_HOOK_IMPL(type, ecs_on_set(type), ptr, __VA_ARGS__) + +/* Map from typename to function name of component lifecycle action */ +#define ecs_ctor(type) type##_ctor +#define ecs_dtor(type) type##_dtor +#define ecs_copy(type) type##_copy +#define ecs_move(type) type##_move +#define ecs_on_set(type) type##_on_set +#define ecs_on_add(type) type##_on_add +#define ecs_on_remove(type) type##_on_remove + +/** @} */ + +/** + * @defgroup flecs_c_ids Id API + * @{ + */ + +#define ecs_count(world, type)\ + ecs_count_id(world, ecs_id(type)) + +/** @} */ + +/** + * @defgroup flecs_c_iterators Iterator API + * @{ + */ + +#define ecs_field(it, T, index)\ + (ECS_CAST(T*, ecs_field_w_size(it, sizeof(T), index))) + +/** @} */ + +/** + * @defgroup flecs_c_tables Table API + * @{ + */ + +#define ecs_table_get(world, table, T, offset)\ + (ECS_CAST(T*, ecs_table_get_id(world, table, ecs_id(T), offset))) + +#define ecs_table_get_pair(world, table, First, second, offset)\ + (ECS_CAST(First*, ecs_table_get_id(world, table, ecs_pair(ecs_id(First), second), offset))) + +#define ecs_table_get_pair_second(world, table, first, Second, offset)\ + (ECS_CAST(Second*, ecs_table_get_id(world, table, ecs_pair(first, ecs_id(Second)), offset))) + +/** @} */ + +/** + * @defgroup flecs_c_values Value API + * @{ + */ + +#define ecs_value(T, ptr) ((ecs_value_t){ecs_id(T), ptr}) +#define ecs_value_new_t(world, T) ecs_value_new(world, ecs_id(T)) + +/** @} */ + +/** @} */ + +/** + * @defgroup flecs_c_table_sorting Table sorting + * @brief Convenience macro's for sorting tables. + * * @{ */ #define ecs_sort_table(id) ecs_id(id##_sort_table) @@ -8518,286 +9054,9 @@ int ecs_value_move_ctor( /** @} */ /** - * @defgroup function_macros Convenience macros that wrap ECS operations - * @{ - */ - - -/* -- World API -- */ - -#define ecs_set_hooks(world, T, ...)\ - ecs_set_hooks_id(world, ecs_id(T), &(ecs_type_hooks_t)__VA_ARGS__) - -#define ecs_get_hooks(world, T)\ - ecs_get_hooks_id(world, ecs_id(T)); - -/* -- New -- */ - -#define ecs_new(world, T) ecs_new_w_id(world, ecs_id(T)) - -#define ecs_new_w_pair(world, first, second)\ - ecs_new_w_id(world, ecs_pair(first, second)) - -#define ecs_bulk_new(world, component, count)\ - ecs_bulk_new_w_id(world, ecs_id(component), count) - -#define ecs_new_entity(world, n)\ - ecs_entity_init(world, &(ecs_entity_desc_t){\ - .name = n,\ - }) - -#define ecs_new_prefab(world, n)\ - ecs_entity_init(world, &(ecs_entity_desc_t){\ - .name = n,\ - .add = {EcsPrefab}\ - }) - -/* -- Add -- */ - -#define ecs_add(world, entity, T)\ - ecs_add_id(world, entity, ecs_id(T)) - -#define ecs_add_pair(world, subject, first, second)\ - ecs_add_id(world, subject, ecs_pair(first, second)) - - -/* -- Remove -- */ - -#define ecs_remove(world, entity, T)\ - ecs_remove_id(world, entity, ecs_id(T)) - -#define ecs_remove_pair(world, subject, first, second)\ - ecs_remove_id(world, subject, ecs_pair(first, second)) - - -/* -- Override -- */ - -#define ecs_override(world, entity, T)\ - ecs_override_id(world, entity, ecs_id(T)) - -#define ecs_override_pair(world, subject, first, second)\ - ecs_override_id(world, subject, ecs_pair(first, second)) - - -/* -- Bulk remove/delete -- */ - -#define ecs_delete_children(world, parent)\ - ecs_delete_with(world, ecs_pair(EcsChildOf, parent)) - - -/* -- Set -- */ - -#define ecs_set_ptr(world, entity, component, ptr)\ - ecs_set_id(world, entity, ecs_id(component), sizeof(component), ptr) - -#define ecs_set(world, entity, component, ...)\ - ecs_set_id(world, entity, ecs_id(component), sizeof(component), &(component)__VA_ARGS__) - -#define ecs_set_pair(world, subject, First, second, ...)\ - ecs_set_id(world, subject,\ - ecs_pair(ecs_id(First), second),\ - sizeof(First), &(First)__VA_ARGS__) - -#define ecs_set_pair_second(world, subject, first, Second, ...)\ - ecs_set_id(world, subject,\ - ecs_pair(first, ecs_id(Second)),\ - sizeof(Second), &(Second)__VA_ARGS__) - -#define ecs_set_pair_object ecs_set_pair_second - -#define ecs_set_override(world, entity, T, ...)\ - ecs_add_id(world, entity, ECS_OVERRIDE | ecs_id(T));\ - ecs_set(world, entity, T, __VA_ARGS__) - -/* -- Emplace -- */ - -#define ecs_emplace(world, entity, T)\ - (ECS_CAST(T*, ecs_emplace_id(world, entity, ecs_id(T)))) - -/* -- Get -- */ - -#define ecs_get(world, entity, T)\ - (ECS_CAST(const T*, ecs_get_id(world, entity, ecs_id(T)))) - -#define ecs_get_pair(world, subject, First, second)\ - (ECS_CAST(const First*, ecs_get_id(world, subject,\ - ecs_pair(ecs_id(First), second)))) - -#define ecs_get_pair_second(world, subject, first, Second)\ - (ECS_CAST(const Second*, ecs_get_id(world, subject,\ - ecs_pair(first, ecs_id(Second))))) - -#define ecs_get_pair_object ecs_get_pair_second - -/* -- Get from record -- */ - -#define ecs_record_get(world, record, T)\ - (ECS_CAST(const T*, ecs_record_get_id(world, record, ecs_id(T)))) - -#define ecs_record_get_pair(world, record, First, second)\ - (ECS_CAST(const First*, ecs_record_get_id(world, record, \ - ecs_pair(ecs_id(First), second)))) - -#define ecs_record_get_pair_second(world, record, first, Second)\ - (ECS_CAST(const Second*, ecs_record_get_id(world, record,\ - ecs_pair(first, ecs_id(Second))))) - -/* -- Get mut from record -- */ - -#define ecs_record_get_mut(world, record, T)\ - (ECS_CAST(T*, ecs_record_get_mut_id(world, record, ecs_id(T)))) - -#define ecs_record_get_mut_pair(world, record, First, second)\ - (ECS_CAST(First*, ecs_record_get_mut_id(world, record, \ - ecs_pair(ecs_id(First), second)))) - -#define ecs_record_get_mut_pair_second(world, record, first, Second)\ - (ECS_CAST(Second*, ecs_record_get_mut_id(world, record,\ - ecs_pair(first, ecs_id(Second))))) - -#define ecs_record_get_mut_pair_object ecs_record_get_mut_pair_second - -/* -- Ref -- */ - -#define ecs_ref_init(world, entity, T)\ - ecs_ref_init_id(world, entity, ecs_id(T)) - -#define ecs_ref_get(world, ref, T)\ - (ECS_CAST(const T*, ecs_ref_get_id(world, ref, ecs_id(T)))) - -/* -- Get mut & Modified -- */ - -#define ecs_get_mut(world, entity, T)\ - (ECS_CAST(T*, ecs_get_mut_id(world, entity, ecs_id(T)))) - -#define ecs_get_mut_pair(world, subject, First, second)\ - (ECS_CAST(First*, ecs_get_mut_id(world, subject,\ - ecs_pair(ecs_id(First), second)))) - -#define ecs_get_mut_pair_second(world, subject, first, Second)\ - (ECS_CAST(Second*, ecs_get_mut_id(world, subject,\ - ecs_pair(first, ecs_id(Second))))) - -#define ecs_get_mut_pair_object ecs_get_mut_pair_second - -#define ecs_modified(world, entity, component)\ - ecs_modified_id(world, entity, ecs_id(component)) - -#define ecs_modified_pair(world, subject, first, second)\ - ecs_modified_id(world, subject, ecs_pair(first, second)) - - -/* -- Singletons -- */ - -#define ecs_singleton_add(world, comp)\ - ecs_add(world, ecs_id(comp), comp) - -#define ecs_singleton_remove(world, comp)\ - ecs_remove(world, ecs_id(comp), comp) - -#define ecs_singleton_get(world, comp)\ - ecs_get(world, ecs_id(comp), comp) - -#define ecs_singleton_set(world, comp, ...)\ - ecs_set(world, ecs_id(comp), comp, __VA_ARGS__) - -#define ecs_singleton_get_mut(world, comp)\ - ecs_get_mut(world, ecs_id(comp), comp) - -#define ecs_singleton_modified(world, comp)\ - ecs_modified(world, ecs_id(comp), comp) - - -/* -- Has, Owns & Shares -- */ - -#define ecs_has(world, entity, T)\ - ecs_has_id(world, entity, ecs_id(T)) - -#define ecs_has_pair(world, entity, first, second)\ - ecs_has_id(world, entity, ecs_pair(first, second)) - -#define ecs_owns_id(world, entity, id)\ - (ecs_search(world, ecs_get_table(world, entity), id, 0) != -1) - -#define ecs_owns_pair(world, entity, first, second)\ - ecs_owns_id(world, entity, ecs_pair(first, second)) - -#define ecs_owns(world, entity, T)\ - ecs_owns_id(world, entity, ecs_id(T)) - -#define ecs_shares_id(world, entity, id)\ - (ecs_search_relation(world, ecs_get_table(world, entity), 0, ecs_id(id), \ - EcsIsA, 1, 0, 0, 0, 0) != -1) - -#define ecs_shares_pair(world, entity, first, second)\ - (ecs_shares_id(world, entity, ecs_pair(first, second))) - -#define ecs_shares(world, entity, T)\ - (ecs_shares_id(world, entity, ecs_id(T))) - - -/* -- Enable / Disable component -- */ - -#define ecs_enable_component(world, entity, T, enable)\ - ecs_enable_id(world, entity, ecs_id(T), enable) - -#define ecs_is_enabled_component(world, entity, T)\ - ecs_is_enabled_id(world, entity, ecs_id(T)) - -#define ecs_enable_pair(world, entity, First, second, enable)\ - ecs_enable_id(world, entity, ecs_pair(ecs_id(First), second), enable) - -#define ecs_is_enabled_pair(world, entity, First, second)\ - ecs_is_enabled_id(world, entity, ecs_pair(ecs_id(First), second)) - -/* -- Count -- */ - -#define ecs_count(world, type)\ - ecs_count_id(world, ecs_id(type)) - - -/* -- Lookups & Paths -- */ - -#define ecs_lookup_path(world, parent, path)\ - ecs_lookup_path_w_sep(world, parent, path, ".", NULL, true) - -#define ecs_lookup_fullpath(world, path)\ - ecs_lookup_path_w_sep(world, 0, path, ".", NULL, true) - -#define ecs_get_path(world, parent, child)\ - ecs_get_path_w_sep(world, parent, child, ".", NULL) - -#define ecs_get_fullpath(world, child)\ - ecs_get_path_w_sep(world, 0, child, ".", NULL) - -#define ecs_get_fullpath_buf(world, child, buf)\ - ecs_get_path_w_sep_buf(world, 0, child, ".", NULL, buf) - -#define ecs_new_from_path(world, parent, path)\ - ecs_new_from_path_w_sep(world, parent, path, ".", NULL) - -#define ecs_new_from_fullpath(world, path)\ - ecs_new_from_path_w_sep(world, 0, path, ".", NULL) - -#define ecs_add_path(world, entity, parent, path)\ - ecs_add_path_w_sep(world, entity, parent, path, ".", NULL) - -#define ecs_add_fullpath(world, entity, path)\ - ecs_add_path_w_sep(world, entity, 0, path, ".", NULL) - - -/* -- Iterators -- */ - -#define ecs_field(it, T, index)\ - (ECS_CAST(T*, ecs_field_w_size(it, sizeof(T), index))) - -#define ecs_iter_column(it, T, index)\ - (ECS_CAST(T*, ecs_iter_column_w_size(it, sizeof(T), index))) - -/** @} */ - -/** - * @defgroup utilities Utility macros for commonly used operations + * @defgroup flecs_c_misc Misc + * @brief Misc convenience macro's. + * * @{ */ @@ -8805,71 +9064,6 @@ int ecs_value_move_ctor( #define ecs_childof(e) ecs_pair(EcsChildOf, e) #define ecs_dependson(e) ecs_pair(EcsDependsOn, e) -/** @} */ - -/** - * @defgroup values Utility macro's for values - * @{ - */ - -#define ecs_value(T, ptr) ((ecs_value_t){ecs_id(T), ptr}) -#define ecs_value_new_t(world, T) ecs_value_new(world, ecs_id(T)) - -/** @} */ - -/** - * @defgroup temporary_macros Temp macros for easing the transition to v3 - * @{ - */ - -/** Declare a constructor. - * Example: - * ECS_CTOR(MyType, ptr, { ptr->value = NULL; }); - */ -#define ECS_CTOR(type, var, ...)\ - ECS_XTOR_IMPL(type, ctor, var, __VA_ARGS__) - -/** Declare a destructor. - * Example: - * ECS_DTOR(MyType, ptr, { free(ptr->value); }); - */ -#define ECS_DTOR(type, var, ...)\ - ECS_XTOR_IMPL(type, dtor, var, __VA_ARGS__) - -/** Declare a copy action. - * Example: - * ECS_COPY(MyType, dst, src, { dst->value = strdup(src->value); }); - */ -#define ECS_COPY(type, dst_var, src_var, ...)\ - ECS_COPY_IMPL(type, dst_var, src_var, __VA_ARGS__) - -/** Declare a move action. - * Example: - * ECS_MOVE(MyType, dst, src, { dst->value = src->value; src->value = 0; }); - */ -#define ECS_MOVE(type, dst_var, src_var, ...)\ - ECS_MOVE_IMPL(type, dst_var, src_var, __VA_ARGS__) - -/** Declare component hooks - * Example: - * ECS_ON_SET(MyType, ptr, { printf("%d\n", ptr->value); }); - */ -#define ECS_ON_ADD(type, ptr, ...)\ - ECS_HOOK_IMPL(type, ecs_on_add(type), ptr, __VA_ARGS__) -#define ECS_ON_REMOVE(type, ptr, ...)\ - ECS_HOOK_IMPL(type, ecs_on_remove(type), ptr, __VA_ARGS__) -#define ECS_ON_SET(type, ptr, ...)\ - ECS_HOOK_IMPL(type, ecs_on_set(type), ptr, __VA_ARGS__) - -/* Map from typename to function name of component lifecycle action */ -#define ecs_ctor(type) type##_ctor -#define ecs_dtor(type) type##_dtor -#define ecs_copy(type) type##_copy -#define ecs_move(type) type##_move -#define ecs_on_set(type) type##_on_set -#define ecs_on_add(type) type##_on_add -#define ecs_on_remove(type) type##_on_remove - #define ecs_query_new(world, q_expr)\ ecs_query_init(world, &(ecs_query_desc_t){\ .filter.expr = q_expr\ @@ -8882,6 +9076,7 @@ int ecs_value_move_ctor( /** @} */ +/** @} */ #endif // FLECS_C_ @@ -8976,10 +9171,89 @@ int ecs_value_move_ctor( #ifdef FLECS_NO_REST #undef FLECS_REST #endif +#ifdef FLECS_NO_JOURNAL +#undef FLECS_JOURNAL +#endif -/* Always included, if disabled log functions are replaced with dummy macros */ +/* Always included, if disabled functions are replaced with dummy macros */ /** - * @file log.h + * @file addons/journal.h + * @brief Journaling addon that logs API functions. + * + * The journaling addon traces API calls. The trace is formatted as runnable + * C code, which allows for (partially) reproducing the behavior of an app + * with the journaling trace. + * + * The journaling addon is disabled by default. Enabling it can have a + * significant impact on performance. + */ + +#ifdef FLECS_JOURNAL + +#ifndef FLECS_LOG +#define FLECS_LOG +#endif + +#ifndef FLECS_JOURNAL_H +#define FLECS_JOURNAL_H + +/** + * @defgroup c_addons_journal Journal + * @brief Journaling addon (disabled by default). + * + * \ingroup c_addons + * @{ + */ + +/* Trace when log level is at or higher than level */ +#define FLECS_JOURNAL_LOG_LEVEL (0) + +#ifdef __cplusplus +extern "C" { +#endif + +/* Journaling API, meant to be used by internals. */ + +typedef enum ecs_journal_kind_t { + EcsJournalNew, + EcsJournalMove, + EcsJournalClear, + EcsJournalDelete, + EcsJournalDeleteWith, + EcsJournalRemoveAll, + EcsJournalTableEvents +} ecs_journal_kind_t; + +FLECS_DBG_API +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_DBG_API +void flecs_journal_end(void); + +#define flecs_journal(...)\ + flecs_journal_begin(__VA_ARGS__);\ + flecs_journal_end(); + +#ifdef __cplusplus +} +#endif // __cplusplus +#endif // FLECS_JOURNAL_H +#else +#define flecs_journal_begin(...) +#define flecs_journal_end(...) +#define flecs_journal(...) + +/** @} */ + +#endif // FLECS_JOURNAL + +/** + * @file addons/log.h * @brief Logging addon. * * The logging addon provides an API for (debug) tracing and reporting errors @@ -9014,6 +9288,14 @@ extern "C" { #ifdef FLECS_LOG +/** + * @defgroup c_addons_log Log + * @brief Logging functions. + * + * \ingroup c_addons + * @{ + */ + //////////////////////////////////////////////////////////////////////////////// //// Tracing //////////////////////////////////////////////////////////////////////////////// @@ -9086,6 +9368,22 @@ const char* ecs_strerror( //// Logging functions (do nothing when logging is enabled) //////////////////////////////////////////////////////////////////////////////// +FLECS_API +void _ecs_print( + int32_t level, + const char *file, + int32_t line, + const char *fmt, + ...); + +FLECS_API +void _ecs_printv( + int level, + const char *file, + int32_t line, + const char *fmt, + va_list args); + FLECS_API void _ecs_log( int32_t level, @@ -9144,6 +9442,12 @@ void _ecs_parser_errorv( #ifndef FLECS_LEGACY /* C89 doesn't support variadic macros */ /* Base logging function. Accepts a custom level */ +#define ecs_print(level, ...)\ + _ecs_print(level, __FILE__, __LINE__, __VA_ARGS__) + +#define ecs_printv(level, fmt, args)\ + _ecs_printv(level, __FILE__, __LINE__, fmt, args) + #define ecs_log(level, ...)\ _ecs_log(level, __FILE__, __LINE__, __VA_ARGS__) @@ -9288,13 +9592,13 @@ void _ecs_parser_errorv( #define ecs_log_push() _ecs_log_push(0) #define ecs_log_pop() _ecs_log_pop(0) -/** Abort +/** Abort. * Unconditionally aborts process. */ #define ecs_abort(error_code, ...)\ _ecs_abort(error_code, __FILE__, __LINE__, __VA_ARGS__);\ ecs_os_abort(); abort(); /* satisfy compiler/static analyzers */ -/** Assert +/** Assert. * Aborts if condition is false, disabled in debug mode. */ #if defined(FLECS_NDEBUG) && !defined(FLECS_KEEP_ASSERT) #define ecs_assert(condition, error_code, ...) @@ -9310,7 +9614,7 @@ void _ecs_parser_errorv( ecs_assert(var, error_code, __VA_ARGS__);\ (void)var -/** Debug assert +/** Debug assert. * Assert that is only valid in debug mode (ignores FLECS_KEEP_ASSERT) */ #ifndef FLECS_NDEBUG #define ecs_dbg_assert(condition, error_code, ...) ecs_assert(condition, error_code, __VA_ARGS__) @@ -9324,7 +9628,7 @@ void _ecs_parser_errorv( goto error;\ } -/** Check +/** Check. * goto error if condition is false. */ #if defined(FLECS_NDEBUG) && !defined(FLECS_KEEP_ASSERT) #define ecs_check(condition, error_code, ...) ecs_dummy_check @@ -9341,7 +9645,7 @@ void _ecs_parser_errorv( #endif #endif // FLECS_NDEBUG -/** Panic +/** Panic. * goto error when FLECS_SOFT_ASSERT is defined, otherwise abort */ #if defined(FLECS_NDEBUG) && !defined(FLECS_KEEP_ASSERT) #define ecs_throw(error_code, ...) ecs_dummy_check @@ -9502,6 +9806,8 @@ int ecs_log_last_error(void); } #endif +/** @} */ + #endif // FLECS_LOG_H @@ -9522,7 +9828,7 @@ int ecs_log_last_error(void); #error "FLECS_NO_APP failed: APP is required by other addons" #endif /** - * @file app.h + * @file addons/app.h * @brief App addon. * * The app addon is a wrapper around the application's main loop. Its main @@ -9544,23 +9850,31 @@ int ecs_log_last_error(void); extern "C" { #endif +/** + * @defgroup c_addons_app App + * @brief Optional addon for running the main application loop. + * + * \ingroup c_addons + * @{ + */ + /** Callback type for init action. */ typedef int(*ecs_app_init_action_t)( ecs_world_t *world); /** Used with ecs_app_run. */ typedef struct ecs_app_desc_t { - ecs_ftime_t target_fps; /* Target FPS. */ - ecs_ftime_t delta_time; /* Frame time increment (0 for measured values) */ - int32_t threads; /* Number of threads. */ - int32_t frames; /* Number of frames to run (0 for infinite) */ - bool enable_rest; /* Allows HTTP clients to access ECS data */ - bool enable_monitor; /* Periodically collect statistics */ + ecs_ftime_t target_fps; /**< Target FPS. */ + ecs_ftime_t delta_time; /**< Frame time increment (0 for measured values) */ + int32_t threads; /**< Number of threads. */ + int32_t frames; /**< Number of frames to run (0 for infinite) */ + bool enable_rest; /**< Allows HTTP clients to access ECS data */ + bool enable_monitor; /**< Periodically collect statistics */ - ecs_app_init_action_t init; /* If set, function is ran before starting the + ecs_app_init_action_t init; /**< If set, function is ran before starting the * main loop. */ - void *ctx; /* Reserved for custom run/frame actions */ + void *ctx; /**< Reserved for custom run/frame actions */ } ecs_app_desc_t; /** Callback type for run action. */ @@ -9620,6 +9934,8 @@ FLECS_API int ecs_app_set_frame_action( ecs_app_frame_action_t callback); +/** @} */ + #ifdef __cplusplus } #endif @@ -9634,7 +9950,7 @@ int ecs_app_set_frame_action( #error "FLECS_NO_REST failed: REST is required by other addons" #endif /** - * @file rest.h + * @file addons/rest.h * @brief REST API addon. * * A small REST API that uses the HTTP server and JSON serializer to provide @@ -9645,6 +9961,14 @@ int ecs_app_set_frame_action( #ifdef FLECS_REST +/** + * @defgroup c_addons_rest Rest + * @brief REST API for querying and mutating entities. + * + * \ingroup c_addons + * @{ + */ + /* Used for the HTTP server */ #ifndef FLECS_HTTP #define FLECS_HTTP @@ -9674,15 +9998,32 @@ extern "C" { #define ECS_REST_DEFAULT_PORT (27750) -/* Component that instantiates the REST API */ +/** Component that instantiates the REST API */ FLECS_API extern const ecs_entity_t ecs_id(EcsRest); typedef struct { - uint16_t port; /* Port of server (optional, default = 27750) */ - char *ipaddr; /* Interface address (optional, default = 0.0.0.0) */ + uint16_t port; /**< Port of server (optional, default = 27750) */ + char *ipaddr; /**< Interface address (optional, default = 0.0.0.0) */ void *impl; } EcsRest; +/* Global statistics */ +extern int64_t ecs_rest_request_count; +extern int64_t ecs_rest_entity_count; +extern int64_t ecs_rest_entity_error_count; +extern int64_t ecs_rest_query_count; +extern int64_t ecs_rest_query_error_count; +extern int64_t ecs_rest_query_name_count; +extern int64_t ecs_rest_query_name_error_count; +extern int64_t ecs_rest_query_name_from_cache_count; +extern int64_t ecs_rest_enable_count; +extern int64_t ecs_rest_enable_error_count; +extern int64_t ecs_rest_delete_count; +extern int64_t ecs_rest_delete_error_count; +extern int64_t ecs_rest_world_stats_count; +extern int64_t ecs_rest_pipeline_stats_count; +extern int64_t ecs_rest_stats_error_count; + /* Module import */ FLECS_API void FlecsRestImport( @@ -9694,6 +10035,8 @@ void FlecsRestImport( #endif +/** @} */ + #endif #endif @@ -9702,7 +10045,7 @@ void FlecsRestImport( #error "FLECS_NO_TIMER failed: TIMER is required by other addons" #endif /** - * @file timer.h + * @file addons/timer.h * @brief Timer module. * * Timers can be used to trigger actions at periodic or one-shot intervals. They @@ -9711,6 +10054,14 @@ void FlecsRestImport( #ifdef FLECS_TIMER +/** + * @defgroup c_addons_timer Timer + * @brief Run systems at a time interval. + * + * \ingroup c_addons + * @{ + */ + #ifndef FLECS_MODULE #define FLECS_MODULE #endif @@ -9726,33 +10077,24 @@ void FlecsRestImport( extern "C" { #endif - -//////////////////////////////////////////////////////////////////////////////// -//// Components -//////////////////////////////////////////////////////////////////////////////// - /** Component used for one shot/interval timer functionality */ typedef struct EcsTimer { - ecs_ftime_t timeout; /* Timer timeout period */ - ecs_ftime_t time; /* Incrementing time value */ - int32_t fired_count; /* Number of times ticked */ - bool active; /* Is the timer active or not */ - bool single_shot; /* Is this a single shot timer */ + ecs_ftime_t timeout; /**< Timer timeout period */ + ecs_ftime_t time; /**< Incrementing time value */ + int32_t fired_count; /**< Number of times ticked */ + bool active; /**< Is the timer active or not */ + bool single_shot; /**< Is this a single shot timer */ } EcsTimer; -/* Apply a rate filter to a tick source */ +/** Apply a rate filter to a tick source */ typedef struct EcsRateFilter { - ecs_entity_t src; /* Source of the rate filter */ - int32_t rate; /* Rate of the rate filter */ - int32_t tick_count; /* Number of times the rate filter ticked */ - ecs_ftime_t time_elapsed; /* Time elapsed since last tick */ + ecs_entity_t src; /**< Source of the rate filter */ + int32_t rate; /**< Rate of the rate filter */ + int32_t tick_count; /**< Number of times the rate filter ticked */ + ecs_ftime_t time_elapsed; /**< Time elapsed since last tick */ } EcsRateFilter; -//////////////////////////////////////////////////////////////////////////////// -//// Timer API -//////////////////////////////////////////////////////////////////////////////// - /** Set timer timeout. * This operation executes any systems associated with the timer after the * specified timeout value. If the entity contains an existing timer, the @@ -9943,6 +10285,8 @@ void FlecsTimerImport( #endif +/** @} */ + #endif #endif @@ -9951,7 +10295,7 @@ void FlecsTimerImport( #error "FLECS_NO_PIPELINE failed: PIPELINE is required by other addons" #endif /** - * @file pipeline.h + * @file addons/pipeline.h * @brief Pipeline module. * * The pipeline module provides support for running systems automatically and @@ -9967,6 +10311,14 @@ void FlecsTimerImport( #ifdef FLECS_PIPELINE +/** + * @defgroup c_addons_pipeline Pipeline + * @brief Pipelines order and schedule systems for execution. + * + * \ingroup c_addons + * @{ + */ + #ifndef FLECS_MODULE #define FLECS_MODULE #endif @@ -10145,6 +10497,8 @@ void FlecsPipelineImport( #endif +/** @} */ + #endif #endif @@ -10153,7 +10507,7 @@ void FlecsPipelineImport( #error "FLECS_NO_SYSTEM failed: SYSTEM is required by other addons" #endif /** - * @file system.h + * @file addons/system.h * @brief System module. * * The system module allows for creating and running systems. A system is a @@ -10163,6 +10517,14 @@ void FlecsPipelineImport( #ifdef FLECS_SYSTEM +/** + * @defgroup c_addons_system System + * @brief Systems are a query + function that can be ran manually or by a pipeline. + * + * \ingroup c_addons + * @{ + */ + #ifndef FLECS_MODULE #define FLECS_MODULE #endif @@ -10174,34 +10536,25 @@ void FlecsPipelineImport( extern "C" { #endif - -//////////////////////////////////////////////////////////////////////////////// -//// Components -//////////////////////////////////////////////////////////////////////////////// - -/* Component used to provide a tick source to systems */ +/** Component used to provide a tick source to systems */ typedef struct EcsTickSource { - bool tick; /* True if providing tick */ - ecs_ftime_t time_elapsed; /* Time elapsed since last tick */ + bool tick; /**< True if providing tick */ + ecs_ftime_t time_elapsed; /**< Time elapsed since last tick */ } EcsTickSource; -//////////////////////////////////////////////////////////////////////////////// -//// Systems API -//////////////////////////////////////////////////////////////////////////////// - -/* Use with ecs_system_init */ +/** Use with ecs_system_init */ typedef struct ecs_system_desc_t { int32_t _canary; - /* Existing entity to associate with system (optional) */ + /** Existing entity to associate with system (optional) */ ecs_entity_t entity; - /* System query parameters */ + /** System query parameters */ ecs_query_desc_t query; - /* Callback that is invoked when a system is ran. When left to NULL, the - * default system runner is used, which calls the "callback" action for each - * result returned from the system's query. + /** Callback that is invoked when a system is ran. + * When left to NULL, the default system runner is used, which calls the + * "callback" action for each result returned from the system's query. * * It should not be assumed that the input iterator can always be iterated * with ecs_query_next. When a system is multithreaded and/or paged, the @@ -10213,47 +10566,56 @@ typedef struct ecs_system_desc_t { * testing whether the it->next value is equal to ecs_query_next. */ ecs_run_action_t run; - /* Callback that is ran for each result returned by the system's query. This + /** Callback that is ran for each result returned by the system's query. This * means that this callback can be invoked multiple times per system per * frame, typically once for each matching table. */ ecs_iter_action_t callback; - /* Context to be passed to callback (as ecs_iter_t::param) */ + /** Context to be passed to callback (as ecs_iter_t::param) */ void *ctx; - /* Binding context, for when system is implemented in other language */ + /** Binding context, for when system is implemented in other language */ void *binding_ctx; - /* Functions that are invoked during system cleanup to free context data. + /** Functions that are invoked during system cleanup to free context data. * When set, functions are called unconditionally, even when the ctx * pointers are NULL. */ ecs_ctx_free_t ctx_free; ecs_ctx_free_t binding_ctx_free; - /* Interval in seconds at which the system should run */ + /** Interval in seconds at which the system should run */ ecs_ftime_t interval; - /* Rate at which the system should run */ + /** Rate at which the system should run */ int32_t rate; - /* External tick soutce that determines when system ticks */ + /** External tick soutce that determines when system ticks */ ecs_entity_t tick_source; - /* If true, system will be ran on multiple threads */ + /** If true, system will be ran on multiple threads */ bool multi_threaded; - /* If true, system will have access to actuall world. Cannot be true at the + /** If true, system will have access to actuall world. Cannot be true at the * same time as multi_threaded. */ - bool no_staging; + bool no_readonly; } ecs_system_desc_t; -/* Create a system */ +/** Create a system */ FLECS_API ecs_entity_t ecs_system_init( ecs_world_t *world, const ecs_system_desc_t *desc); #ifndef FLECS_LEGACY + +/** Forward declare a system. */ +#define ECS_SYSTEM_DECLARE(id) ecs_entity_t ecs_id(id) + +/** Define a forward declared system. + * + * Example: + * ECS_SYSTEM_DEFINE(world, Move, EcsOnUpdate, Position, Velocity); + */ #define ECS_SYSTEM_DEFINE(world, id_, phase, ...) \ { \ ecs_system_desc_t desc = {0}; \ @@ -10269,12 +10631,32 @@ ecs_entity_t ecs_system_init( } \ ecs_assert(ecs_id(id_) != 0, ECS_INVALID_PARAMETER, NULL); +/** Declare & define a system. + * + * Example: + * ECS_SYSTEM(world, Move, EcsOnUpdate, Position, Velocity); + */ #define ECS_SYSTEM(world, id, phase, ...) \ ecs_entity_t ecs_id(id) = 0; ECS_SYSTEM_DEFINE(world, id, phase, __VA_ARGS__);\ ecs_entity_t id = ecs_id(id);\ (void)ecs_id(id);\ (void)id; +/** Shorthand for creating a system with ecs_system_init. + * + * Example: + * ecs_system(world, { + * .entity = ecs_entity(world, { + * .name = "MyEntity", + * .add = { ecs_dependson(EcsOnUpdate) } + * }, + * .query.filter.terms = { + * { ecs_id(Position) }, + * { ecs_id(Velocity) } + * }, + * .callback = Move + * }); + */ #define ecs_system(world, ...)\ ecs_system_init(world, &(ecs_system_desc_t) __VA_ARGS__ ) @@ -10401,12 +10783,7 @@ void* ecs_get_system_ctx( FLECS_API void* ecs_get_system_binding_ctx( const ecs_world_t *world, - ecs_entity_t system); - - -//////////////////////////////////////////////////////////////////////////////// -//// Module -//////////////////////////////////////////////////////////////////////////////// + ecs_entity_t system); FLECS_API void FlecsSystemImport( @@ -10418,6 +10795,8 @@ void FlecsSystemImport( #endif +/** @} */ + #endif #endif @@ -10426,7 +10805,7 @@ void FlecsSystemImport( #error "FLECS_NO_STATS failed: STATS is required by other addons" #endif /** - * @file stats.h + * @file addons/stats.h * @brief Statistics addon. * * The statistics addon enables an application to obtain detailed metrics about @@ -10435,6 +10814,14 @@ void FlecsSystemImport( #ifdef FLECS_STATS +/** + * @defgroup c_addons_stats Stats + * @brief Collection of statistics for world, queries, systems and pipelines. + * + * \ingroup c_addons + * @{ + */ + #ifndef FLECS_STATS_H #define FLECS_STATS_H @@ -10451,56 +10838,56 @@ typedef struct ecs_gauge_t { ecs_float_t max[ECS_STAT_WINDOW]; } ecs_gauge_t; -/* Monotonically increasing counter */ +/** Monotonically increasing counter */ typedef struct ecs_counter_t { - ecs_gauge_t rate; /* Keep track of deltas too */ - ecs_float_t value[ECS_STAT_WINDOW]; + ecs_gauge_t rate; /**< Keep track of deltas too */ + double value[ECS_STAT_WINDOW]; } ecs_counter_t; -/* Make all metrics the same size, so we can iterate over fields */ +/** Make all metrics the same size, so we can iterate over fields */ typedef union ecs_metric_t { ecs_gauge_t gauge; ecs_counter_t counter; } ecs_metric_t; typedef struct ecs_world_stats_t { - int32_t first_; + int64_t first_; /* Entities */ struct { - ecs_metric_t count; /* Number of entities */ - ecs_metric_t not_alive_count; /* Number of not alive (recyclable) entity ids */ + ecs_metric_t count; /**< Number of entities */ + ecs_metric_t not_alive_count; /**< Number of not alive (recyclable) entity ids */ } entities; /* Components and ids */ struct { - ecs_metric_t count; /* Number of ids (excluding wildcards) */ - ecs_metric_t tag_count; /* Number of tag ids (ids without data) */ - ecs_metric_t component_count; /* Number of components ids (ids with data) */ - ecs_metric_t pair_count; /* Number of pair ids */ - ecs_metric_t wildcard_count; /* Number of wildcard ids */ - ecs_metric_t type_count; /* Number of registered types */ - ecs_metric_t create_count; /* Number of times id has been created */ - ecs_metric_t delete_count; /* Number of times id has been deleted */ + ecs_metric_t count; /**< Number of ids (excluding wildcards) */ + ecs_metric_t tag_count; /**< Number of tag ids (ids without data) */ + ecs_metric_t component_count; /**< Number of components ids (ids with data) */ + ecs_metric_t pair_count; /**< Number of pair ids */ + ecs_metric_t wildcard_count; /**< Number of wildcard ids */ + ecs_metric_t type_count; /**< Number of registered types */ + ecs_metric_t create_count; /**< Number of times id has been created */ + ecs_metric_t delete_count; /**< Number of times id has been deleted */ } ids; /* Tables */ struct { - ecs_metric_t count; /* Number of tables */ - ecs_metric_t empty_count; /* Number of empty tables */ - ecs_metric_t tag_only_count; /* Number of tables with only tags */ - ecs_metric_t trivial_only_count; /* Number of tables with only trivial components */ - ecs_metric_t record_count; /* Number of table cache records */ - ecs_metric_t storage_count; /* Number of table storages */ - ecs_metric_t create_count; /* Number of times table has been created */ - ecs_metric_t delete_count; /* Number of times table has been deleted */ + ecs_metric_t count; /**< Number of tables */ + ecs_metric_t empty_count; /**< Number of empty tables */ + ecs_metric_t tag_only_count; /**< Number of tables with only tags */ + ecs_metric_t trivial_only_count; /**< Number of tables with only trivial components */ + ecs_metric_t record_count; /**< Number of table cache records */ + ecs_metric_t storage_count; /**< Number of table storages */ + ecs_metric_t create_count; /**< Number of times table has been created */ + ecs_metric_t delete_count; /**< Number of times table has been deleted */ } tables; /* Queries & events */ struct { - ecs_metric_t query_count; /* Number of queries */ - ecs_metric_t observer_count; /* Number of observers */ - ecs_metric_t system_count; /* Number of systems */ + ecs_metric_t query_count; /**< Number of queries */ + ecs_metric_t observer_count; /**< Number of observers */ + ecs_metric_t system_count; /**< Number of systems */ } queries; /* Commands */ @@ -10518,59 +10905,89 @@ typedef struct ecs_world_stats_t { ecs_metric_t batched_count; } commands; + /* Frame data */ struct { - /* Frame data */ - ecs_metric_t frame_count; /* Number of frames processed. */ - ecs_metric_t merge_count; /* Number of merges executed. */ - ecs_metric_t rematch_count; /* Number of query rematches */ - ecs_metric_t pipeline_build_count; /* Number of system pipeline rebuilds (occurs when an inactive system becomes active). */ - ecs_metric_t systems_ran; /* Number of systems ran. */ - ecs_metric_t observers_ran; /* Number of times an observer was invoked. */ - ecs_metric_t event_emit_count; /* Number of events emitted */ + ecs_metric_t frame_count; /**< Number of frames processed. */ + ecs_metric_t merge_count; /**< Number of merges executed. */ + ecs_metric_t rematch_count; /**< Number of query rematches */ + ecs_metric_t pipeline_build_count; /**< Number of system pipeline rebuilds (occurs when an inactive system becomes active). */ + ecs_metric_t systems_ran; /**< Number of systems ran. */ + ecs_metric_t observers_ran; /**< Number of times an observer was invoked. */ + ecs_metric_t event_emit_count; /**< Number of events emitted */ } frame; + /* Timing */ struct { - /* Timing */ - ecs_metric_t world_time_raw; /* Actual time passed since simulation start (first time progress() is called) */ - ecs_metric_t world_time; /* Simulation time passed since simulation start. Takes into account time scaling */ - ecs_metric_t frame_time; /* Time spent processing a frame. Smaller than world_time_total when load is not 100% */ - ecs_metric_t system_time; /* Time spent on running systems. */ - ecs_metric_t emit_time; /* Time spent on notifying observers. */ - ecs_metric_t merge_time; /* Time spent on merging commands. */ - ecs_metric_t rematch_time; /* Time spent on rematching. */ - ecs_metric_t fps; /* Frames per second. */ - ecs_metric_t delta_time; /* Delta_time. */ + ecs_metric_t world_time_raw; /**< Actual time passed since simulation start (first time progress() is called) */ + ecs_metric_t world_time; /**< Simulation time passed since simulation start. Takes into account time scaling */ + ecs_metric_t frame_time; /**< Time spent processing a frame. Smaller than world_time_total when load is not 100% */ + ecs_metric_t system_time; /**< Time spent on running systems. */ + ecs_metric_t emit_time; /**< Time spent on notifying observers. */ + ecs_metric_t merge_time; /**< Time spent on merging commands. */ + ecs_metric_t rematch_time; /**< Time spent on rematching. */ + ecs_metric_t fps; /**< Frames per second. */ + ecs_metric_t delta_time; /**< Delta_time. */ } performance; struct { /* Memory allocation data */ - ecs_metric_t alloc_count; /* Allocs per frame */ - ecs_metric_t realloc_count; /* Reallocs per frame */ - ecs_metric_t free_count; /* Frees per frame */ - ecs_metric_t outstanding_alloc_count; /* Difference between allocs & frees */ + ecs_metric_t alloc_count; /**< Allocs per frame */ + ecs_metric_t realloc_count; /**< Reallocs per frame */ + ecs_metric_t free_count; /**< Frees per frame */ + ecs_metric_t outstanding_alloc_count; /**< Difference between allocs & frees */ /* Memory allocator data */ - ecs_metric_t block_alloc_count; /* Block allocations per frame */ - ecs_metric_t block_free_count; /* Block frees per frame */ - ecs_metric_t block_outstanding_alloc_count; /* Difference between allocs & frees */ - ecs_metric_t stack_alloc_count; /* Page allocations per frame */ - ecs_metric_t stack_free_count; /* Page frees per frame */ - ecs_metric_t stack_outstanding_alloc_count; /* Difference between allocs & frees */ + ecs_metric_t block_alloc_count; /**< Block allocations per frame */ + ecs_metric_t block_free_count; /**< Block frees per frame */ + ecs_metric_t block_outstanding_alloc_count; /**< Difference between allocs & frees */ + ecs_metric_t stack_alloc_count; /**< Page allocations per frame */ + ecs_metric_t stack_free_count; /**< Page frees per frame */ + ecs_metric_t stack_outstanding_alloc_count; /**< Difference between allocs & frees */ } memory; - int32_t last_; + /* REST statistics */ + struct { + ecs_metric_t request_count; + ecs_metric_t entity_count; + ecs_metric_t entity_error_count; + ecs_metric_t query_count; + ecs_metric_t query_error_count; + ecs_metric_t query_name_count; + ecs_metric_t query_name_error_count; + ecs_metric_t query_name_from_cache_count; + ecs_metric_t enable_count; + ecs_metric_t enable_error_count; + ecs_metric_t world_stats_count; + ecs_metric_t pipeline_stats_count; + ecs_metric_t stats_error_count; + } rest; + + /* HTTP statistics */ + struct { + ecs_metric_t request_received_count; + ecs_metric_t request_invalid_count; + ecs_metric_t request_handled_ok_count; + ecs_metric_t request_handled_error_count; + ecs_metric_t request_not_handled_count; + ecs_metric_t request_preflight_count; + ecs_metric_t send_ok_count; + ecs_metric_t send_error_count; + ecs_metric_t busy_count; + } http; + + int64_t last_; /** Current position in ringbuffer */ int32_t t; } ecs_world_stats_t; -/* Statistics for a single query (use ecs_query_stats_get) */ +/** Statistics for a single query (use ecs_query_stats_get) */ typedef struct ecs_query_stats_t { - int32_t first_; - ecs_metric_t matched_table_count; /* Matched non-empty tables */ - ecs_metric_t matched_empty_table_count; /* Matched empty tables */ - ecs_metric_t matched_entity_count; /* Number of matched entities */ - int32_t last_; + int64_t first_; + ecs_metric_t matched_table_count; /**< Matched non-empty tables */ + ecs_metric_t matched_empty_table_count; /**< Matched empty tables */ + ecs_metric_t matched_entity_count; /**< Number of matched entities */ + int64_t last_; /** Current position in ringbuffer */ int32_t t; @@ -10578,14 +10995,14 @@ typedef struct ecs_query_stats_t { /** Statistics for a single system (use ecs_system_stats_get) */ typedef struct ecs_system_stats_t { - int32_t first_; - ecs_metric_t time_spent; /* Time spent processing a system */ - ecs_metric_t invoke_count; /* Number of times system is invoked */ - ecs_metric_t active; /* Whether system is active (is matched with >0 entities) */ - ecs_metric_t enabled; /* Whether system is enabled */ - int32_t last_; + int64_t first_; + ecs_metric_t time_spent; /**< Time spent processing a system */ + ecs_metric_t invoke_count; /**< Number of times system is invoked */ + ecs_metric_t active; /**< Whether system is active (is matched with >0 entities) */ + ecs_metric_t enabled; /**< Whether system is enabled */ + int64_t last_; - bool task; /* Is system a task */ + bool task; /**< Is system a task */ ecs_query_stats_t query; } ecs_system_stats_t; @@ -10603,9 +11020,9 @@ typedef struct ecs_pipeline_stats_t { /** Current position in ringbuffer */ int32_t t; - int32_t system_count; /* Number of systems in pipeline */ - int32_t active_system_count; /* Number of active systems in pipeline */ - int32_t rebuild_count; /* Number of times pipeline has rebuilt */ + int32_t system_count; /**< Number of systems in pipeline */ + int32_t active_system_count; /**< Number of active systems in pipeline */ + int32_t rebuild_count; /**< Number of times pipeline has rebuilt */ } ecs_pipeline_stats_t; /** Get world statistics. @@ -10807,6 +11224,8 @@ void ecs_metric_copy( #endif +/** @} */ + #endif #endif @@ -10815,7 +11234,7 @@ void ecs_metric_copy( #error "FLECS_NO_MONITOR failed: MONITOR is required by other addons" #endif /** - * @file doc.h + * @file addons/doc.h * @brief Doc module. * * The monitor module automatically tracks statistics from the stats addon and @@ -10824,6 +11243,14 @@ void ecs_metric_copy( #ifdef FLECS_MONITOR +/** + * @defgroup c_addons_monitor Monitor + * @brief The monitor addon periodically tracks statistics for the world and systems. + * + * \ingroup c_addons + * @{ + */ + #ifndef FLECS_MONITOR_H #define FLECS_MONITOR_H @@ -10875,6 +11302,8 @@ void FlecsMonitorImport( #endif +/** @} */ + #endif #endif @@ -10883,7 +11312,7 @@ void FlecsMonitorImport( #error "FLECS_NO_COREDOC failed: COREDOC is required by other addons" #endif /** - * @file coredoc.h + * @file addons/coredoc.h * @brief Core doc module. * * The core doc module imports documentation and reflection data for core @@ -10907,12 +11336,22 @@ void FlecsMonitorImport( extern "C" { #endif +/** + * @defgroup c_addons_coredoc Coredoc + * @brief Module that adds documentation and reflection to core entities. + * + * \ingroup c_addons + * @{ + */ + /* Module import */ FLECS_API void FlecsCoreDocImport( ecs_world_t *world); +/* @} */ + #ifdef __cplusplus } #endif @@ -10927,7 +11366,7 @@ void FlecsCoreDocImport( #error "FLECS_NO_DOC failed: DOC is required by other addons" #endif /** - * @file doc.h + * @file addons/doc.h * @brief Doc module. * * The doc module allows for documenting entities (and thus components, systems) @@ -10949,6 +11388,14 @@ void FlecsCoreDocImport( extern "C" { #endif +/** + * @defgroup c_addons_doc Doc + * @brief Utilities for documenting entities, components and systems. + * + * \ingroup c_addons + * @{ + */ + FLECS_API extern const ecs_entity_t ecs_id(EcsDocDescription); FLECS_API extern const ecs_entity_t EcsDocBrief; FLECS_API extern const ecs_entity_t EcsDocDetail; @@ -11089,6 +11536,8 @@ FLECS_API void FlecsDocImport( ecs_world_t *world); +/* @} */ + #ifdef __cplusplus } #endif @@ -11103,7 +11552,7 @@ void FlecsDocImport( #error "FLECS_NO_JSON failed: JSON is required by other addons" #endif /** - * @file json.h + * @file addons/json.h * @brief JSON parser addon. * * Parse expression strings into component values. Entity identifiers, @@ -11121,6 +11570,14 @@ void FlecsDocImport( #ifndef FLECS_JSON_H #define FLECS_JSON_H +/** + * @defgroup c_addons_json Json + * @brief Functions for serializing to/from JSON. + * + * \ingroup c_addons + * @{ + */ + #ifdef __cplusplus extern "C" { #endif @@ -11150,6 +11607,17 @@ const char* ecs_parse_json( void *data_out, const ecs_parse_json_desc_t *desc); +/** Parse JSON object with multiple component values into entity. The format + * is the same as the one outputted by ecs_entity_to_json, but at the moment + * only supports the "ids" and "values" member. + */ +FLECS_API +const char* ecs_parse_json_values( + ecs_world_t *world, + ecs_entity_t e, + const char *ptr, + const ecs_parse_json_desc_t *desc); + /** Serialize value into JSON string. * This operation serializes a value of the provided type to a JSON string. The * memory pointed to must be large enough to contain a value of the used type. @@ -11250,18 +11718,18 @@ int ecs_type_info_to_json_buf( /** Used with ecs_iter_to_json. */ typedef struct ecs_entity_to_json_desc_t { - bool serialize_path; /* Serialize full pathname */ - bool serialize_meta_ids; /* Serialize 'meta' ids (Name, ChildOf, etc) */ - bool serialize_label; /* Serialize doc name */ - bool serialize_brief; /* Serialize brief doc description */ - bool serialize_link; /* Serialize doc link (URL) */ - bool serialize_color; /* Serialize doc color */ - bool serialize_id_labels; /* Serialize labels of (component) ids */ - bool serialize_base; /* Serialize base components */ - bool serialize_private; /* Serialize private components */ - bool serialize_hidden; /* Serialize ids hidden by override */ - bool serialize_values; /* Serialize component values */ - bool serialize_type_info; /* Serialize type info (requires serialize_values) */ + bool serialize_path; /**< Serialize full pathname */ + bool serialize_meta_ids; /**< Serialize 'meta' ids (Name, ChildOf, etc) */ + bool serialize_label; /**< Serialize doc name */ + bool serialize_brief; /**< Serialize brief doc description */ + bool serialize_link; /**< Serialize doc link (URL) */ + bool serialize_color; /**< Serialize doc color */ + bool serialize_id_labels; /**< Serialize labels of (component) ids */ + bool serialize_base; /**< Serialize base components */ + bool serialize_private; /**< Serialize private components */ + bool serialize_hidden; /**< Serialize ids hidden by override */ + bool serialize_values; /**< Serialize component values */ + bool serialize_type_info; /**< Serialize type info (requires serialize_values) */ } ecs_entity_to_json_desc_t; #define ECS_ENTITY_TO_JSON_INIT (ecs_entity_to_json_desc_t){true, false,\ @@ -11300,20 +11768,20 @@ int ecs_entity_to_json_buf( /** Used with ecs_iter_to_json. */ typedef struct ecs_iter_to_json_desc_t { - bool serialize_term_ids; /* Include term (query) component ids */ - bool serialize_ids; /* Include actual (matched) component ids */ - bool serialize_sources; /* Include sources */ - bool serialize_variables; /* Include variables */ - bool serialize_is_set; /* Include is_set (for optional terms) */ - bool serialize_values; /* Include component values */ - bool serialize_entities; /* Include entities (for This terms) */ - bool serialize_entity_labels; /* Include doc name for entities */ - bool serialize_entity_ids; /* Include numerical ids for entities */ - bool serialize_variable_labels; /* Include doc name for variables */ - bool serialize_variable_ids; /* Include numerical ids for variables */ - bool serialize_colors; /* Include doc color for entities */ - bool measure_eval_duration; /* Include evaluation duration */ - bool serialize_type_info; /* Include type information */ + bool serialize_term_ids; /**< Serialize term (query) component ids */ + bool serialize_ids; /**< Serialize actual (matched) component ids */ + bool serialize_sources; /**< Serialize sources */ + bool serialize_variables; /**< Serialize variables */ + bool serialize_is_set; /**< Serialize is_set (for optional terms) */ + bool serialize_values; /**< Serialize component values */ + bool serialize_entities; /**< Serialize entities (for This terms) */ + bool serialize_entity_labels; /**< Serialize doc name for entities */ + bool serialize_entity_ids; /**< Serialize numerical ids for entities */ + bool serialize_variable_labels; /**< Serialize doc name for variables */ + bool serialize_variable_ids; /**< Serialize numerical ids for variables */ + bool serialize_colors; /**< Serialize doc color for entities */ + bool measure_eval_duration; /**< Serialize evaluation duration */ + bool serialize_type_info; /**< Serialize type information */ } ecs_iter_to_json_desc_t; #define ECS_ITER_TO_JSON_INIT (ecs_iter_to_json_desc_t){\ @@ -11368,6 +11836,8 @@ int ecs_iter_to_json_buf( #endif +/** @} */ + #endif #endif @@ -11381,7 +11851,7 @@ int ecs_iter_to_json_buf( #error "FLECS_NO_UNITS failed: UNITS is required by other addons" #endif /** - * @file units.h + * @file addons/units.h * @brief Units module. * * Builtin standard units. The units addon is not imported by default, even if @@ -11400,6 +11870,14 @@ int ecs_iter_to_json_buf( #ifdef FLECS_UNITS +/** + * @defgroup c_addons_units Units. + * @brief Common unit annotations for reflection framework. + * + * \ingroup c_addons + * @{ + */ + #ifndef FLECS_MODULE #define FLECS_MODULE #endif @@ -11415,9 +11893,13 @@ int ecs_iter_to_json_buf( extern "C" { #endif -//////////////////////////////////////////////////////////////////////////////// -//// Unit prefixes -//////////////////////////////////////////////////////////////////////////////// +/** + * @defgroup c_addons_units_prefixes Prefixes + * @brief Prefixes to indicate unit count (e.g. Kilo, Mega) + * + * \ingroup c_addons_units + * @{ + */ FLECS_API extern ECS_DECLARE(EcsUnitPrefixes); /* Parent scope for prefixes */ @@ -11451,10 +11933,14 @@ FLECS_API extern ECS_DECLARE(EcsExbi); FLECS_API extern ECS_DECLARE(EcsZebi); FLECS_API extern ECS_DECLARE(EcsYobi); +/** @} */ -//////////////////////////////////////////////////////////////////////////////// -//// Units & quantities -//////////////////////////////////////////////////////////////////////////////// +/** + * @defgroup c_addons_units_duration Duration + * + * \ingroup c_addons_units + * @{ + */ FLECS_API extern ECS_DECLARE(EcsDuration); FLECS_API extern ECS_DECLARE(EcsPicoSeconds); @@ -11466,25 +11952,88 @@ FLECS_API extern ECS_DECLARE(EcsMinutes); FLECS_API extern ECS_DECLARE(EcsHours); FLECS_API extern ECS_DECLARE(EcsDays); +/** @} */ + +/** + * @defgroup c_addons_units_time Time + * + * \ingroup c_addons_units + * @{ + */ + FLECS_API extern ECS_DECLARE(EcsTime); FLECS_API extern ECS_DECLARE(EcsDate); +/** @} */ + +/** + * @defgroup c_addons_units_mass Mass + * + * \ingroup c_addons_units + * @{ + */ + FLECS_API extern ECS_DECLARE(EcsMass); FLECS_API extern ECS_DECLARE(EcsGrams); FLECS_API extern ECS_DECLARE(EcsKiloGrams); +/** @} */ + +/** + * @defgroup c_addons_units_electric_Current Electric Current + * + * \ingroup c_addons_units + * @{ + */ + FLECS_API extern ECS_DECLARE(EcsElectricCurrent); FLECS_API extern ECS_DECLARE(EcsAmpere); +/** @} */ + +/** + * @defgroup c_addons_units_amount Amount + * + * \ingroup c_addons_units + * @{ + */ + FLECS_API extern ECS_DECLARE(EcsAmount); FLECS_API extern ECS_DECLARE(EcsMole); +/** @} */ + +/** + * @defgroup c_addons_units_luminous_intensity Luminous Intensity + * + * \ingroup c_addons_units + * @{ + */ + FLECS_API extern ECS_DECLARE(EcsLuminousIntensity); FLECS_API extern ECS_DECLARE(EcsCandela); +/** @} */ + +/** + * @defgroup c_addons_units_force Force + * + * \ingroup c_addons_units + * @{ + */ + FLECS_API extern ECS_DECLARE(EcsForce); FLECS_API extern ECS_DECLARE(EcsNewton); +/** @} */ + +/** + * @defgroup c_addons_units_length Length + * + * \ingroup c_addons_units + * @{ + */ + FLECS_API extern ECS_DECLARE(EcsLength); FLECS_API extern ECS_DECLARE(EcsMeters); FLECS_API extern ECS_DECLARE(EcsPicoMeters); @@ -11494,23 +12043,58 @@ FLECS_API extern ECS_DECLARE(EcsMilliMeters); FLECS_API extern ECS_DECLARE(EcsCentiMeters); FLECS_API extern ECS_DECLARE(EcsKiloMeters); FLECS_API extern ECS_DECLARE(EcsMiles); +FLECS_API extern ECS_DECLARE(EcsPixels); + +/** @} */ + +/** + * @defgroup c_addons_units_pressure Pressure + * + * \ingroup c_addons_units + * @{ + */ FLECS_API extern ECS_DECLARE(EcsPressure); FLECS_API extern ECS_DECLARE(EcsPascal); FLECS_API extern ECS_DECLARE(EcsBar); +/** @} */ + +/** + * @defgroup c_addons_units_speed Speed + * + * \ingroup c_addons_units + * @{ + */ + FLECS_API extern ECS_DECLARE(EcsSpeed); FLECS_API extern ECS_DECLARE(EcsMetersPerSecond); FLECS_API extern ECS_DECLARE(EcsKiloMetersPerSecond); FLECS_API extern ECS_DECLARE(EcsKiloMetersPerHour); FLECS_API extern ECS_DECLARE(EcsMilesPerHour); +/** @} */ + +/** + * @defgroup c_addons_units_temperature Temperature + * + * \ingroup c_addons_units + * @{ + */ + FLECS_API extern ECS_DECLARE(EcsTemperature); FLECS_API extern ECS_DECLARE(EcsKelvin); FLECS_API extern ECS_DECLARE(EcsCelsius); FLECS_API extern ECS_DECLARE(EcsFahrenheit); -FLECS_API extern ECS_DECLARE(EcsAcceleration); +/** @} */ + +/** + * @defgroup c_addons_units_data Data + * + * \ingroup c_addons_units + * @{ + */ FLECS_API extern ECS_DECLARE(EcsData); FLECS_API extern ECS_DECLARE(EcsBits); @@ -11525,6 +12109,15 @@ FLECS_API extern ECS_DECLARE(EcsKibiBytes); FLECS_API extern ECS_DECLARE(EcsMebiBytes); FLECS_API extern ECS_DECLARE(EcsGibiBytes); +/** @} */ + +/** + * @defgroup c_addons_units_datarate Data Rate + * + * \ingroup c_addons_units + * @{ + */ + FLECS_API extern ECS_DECLARE(EcsDataRate); FLECS_API extern ECS_DECLARE(EcsBitsPerSecond); FLECS_API extern ECS_DECLARE(EcsKiloBitsPerSecond); @@ -11535,12 +12128,52 @@ FLECS_API extern ECS_DECLARE(EcsKiloBytesPerSecond); FLECS_API extern ECS_DECLARE(EcsMegaBytesPerSecond); FLECS_API extern ECS_DECLARE(EcsGigaBytesPerSecond); -FLECS_API extern ECS_DECLARE(EcsPercentage); +/** @} */ + +/** + * @defgroup c_addons_units_duration Duration + * + * \ingroup c_addons_units + * @{ + */ FLECS_API extern ECS_DECLARE(EcsAngle); FLECS_API extern ECS_DECLARE(EcsRadians); FLECS_API extern ECS_DECLARE(EcsDegrees); +/** @} */ + +/** + * @defgroup c_addons_units_angle Angle + * + * \ingroup c_addons_units + * @{ + */ + +FLECS_API extern ECS_DECLARE(EcsFrequency); +FLECS_API extern ECS_DECLARE(EcsHertz); +FLECS_API extern ECS_DECLARE(EcsKiloHertz); +FLECS_API extern ECS_DECLARE(EcsMegaHertz); +FLECS_API extern ECS_DECLARE(EcsGigaHertz); + +/** @} */ + +/** + * @defgroup c_addons_units_uri Uri + * + * \ingroup c_addons_units + * @{ + */ + +FLECS_API extern ECS_DECLARE(EcsUri); +FLECS_API extern ECS_DECLARE(EcsUriHyperlink); +FLECS_API extern ECS_DECLARE(EcsUriImage); +FLECS_API extern ECS_DECLARE(EcsUriFile); + +/** @} */ + +FLECS_API extern ECS_DECLARE(EcsAcceleration); +FLECS_API extern ECS_DECLARE(EcsPercentage); FLECS_API extern ECS_DECLARE(EcsBel); FLECS_API extern ECS_DECLARE(EcsDeciBel); @@ -11558,6 +12191,8 @@ void FlecsUnitsImport( #endif +/** @} */ + #endif #endif @@ -11566,7 +12201,7 @@ void FlecsUnitsImport( #error "FLECS_NO_META failed: META is required by other addons" #endif /** - * @file meta.h + * @file addons/meta.h * @brief Meta addon. * * The meta addon enables reflecting on component data. Types are stored as @@ -11621,6 +12256,14 @@ void FlecsUnitsImport( #ifdef FLECS_META +/** + * @defgroup c_addons_meta Meta + * @brief Flecs reflection framework. + * + * \ingroup c_addons + * @{ + */ + #include #ifndef FLECS_MODULE @@ -11660,7 +12303,7 @@ typedef float ecs_f32_t; typedef double ecs_f64_t; typedef char* ecs_string_t; -/** Meta module component ids */ +/* Meta module component ids */ FLECS_API extern const ecs_entity_t ecs_id(EcsMetaType); FLECS_API extern const ecs_entity_t ecs_id(EcsMetaTypeSerialized); FLECS_API extern const ecs_entity_t ecs_id(EcsPrimitive); @@ -11675,7 +12318,7 @@ FLECS_API extern const ecs_entity_t ecs_id(EcsUnitPrefix); FLECS_API extern const ecs_entity_t EcsConstant; FLECS_API extern const ecs_entity_t EcsQuantity; -/** Primitive type component ids */ +/* Primitive type component ids */ FLECS_API extern const ecs_entity_t ecs_id(ecs_bool_t); FLECS_API extern const ecs_entity_t ecs_id(ecs_char_t); FLECS_API extern const ecs_entity_t ecs_id(ecs_byte_t); @@ -11708,10 +12351,10 @@ typedef enum ecs_type_kind_t { /** Component that is automatically added to every type with the right kind. */ typedef struct EcsMetaType { ecs_type_kind_t kind; - bool existing; /* Did the type exist or is it populated from reflection */ - bool partial; /* Is the reflection data a partial type description */ - ecs_size_t size; /* Computed size */ - ecs_size_t alignment; /* Computed alignment */ + bool existing; /**< Did the type exist or is it populated from reflection */ + bool partial; /**< Is the reflection data a partial type description */ + ecs_size_t size; /**< Computed size */ + ecs_size_t alignment; /**< Computed alignment */ } EcsMetaType; typedef enum ecs_primitive_kind_t { @@ -11746,54 +12389,54 @@ typedef struct EcsMember { int32_t offset; } EcsMember; -/* Element type of members vector in EcsStruct */ +/** Element type of members vector in EcsStruct */ typedef struct ecs_member_t { - /* Must be set when used with ecs_struct_desc_t */ + /** Must be set when used with ecs_struct_desc_t */ const char *name; ecs_entity_t type; - /* May be set when used with ecs_struct_desc_t */ + /** May be set when used with ecs_struct_desc_t */ int32_t count; int32_t offset; - /* May be set when used with ecs_struct_desc_t, will be auto-populated if + /** May be set when used with ecs_struct_desc_t, will be auto-populated if * type entity is also a unit */ ecs_entity_t unit; - /* Should not be set by ecs_struct_desc_t */ + /** Should not be set by ecs_struct_desc_t */ ecs_size_t size; ecs_entity_t member; } ecs_member_t; typedef struct EcsStruct { - /* Populated from child entities with Member component */ + /** Populated from child entities with Member component */ ecs_vector_t *members; /* vector */ } EcsStruct; typedef struct ecs_enum_constant_t { - /* Must be set when used with ecs_enum_desc_t */ + /** Must be set when used with ecs_enum_desc_t */ const char *name; - /* May be set when used with ecs_enum_desc_t */ + /** May be set when used with ecs_enum_desc_t */ int32_t value; - /* Should not be set by ecs_enum_desc_t */ + /** Should not be set by ecs_enum_desc_t */ ecs_entity_t constant; } ecs_enum_constant_t; typedef struct EcsEnum { - /* Populated from child entities with Constant component */ + /** Populated from child entities with Constant component */ ecs_map_t *constants; /* map */ } EcsEnum; typedef struct ecs_bitmask_constant_t { - /* Must be set when used with ecs_bitmask_desc_t */ + /** Must be set when used with ecs_bitmask_desc_t */ const char *name; - /* May be set when used with ecs_bitmask_desc_t */ + /** May be set when used with ecs_bitmask_desc_t */ ecs_flags32_t value; - /* Should not be set by ecs_bitmask_desc_t */ + /** Should not be set by ecs_bitmask_desc_t */ ecs_entity_t constant; } ecs_bitmask_constant_t; @@ -11812,7 +12455,7 @@ typedef struct EcsVector { } EcsVector; -/** Units */ +/* Units */ /* Helper type to describe translation between two units. Note that this * is not intended as a generic approach to unit conversions (e.g. from celsius @@ -11822,25 +12465,25 @@ typedef struct EcsVector { * Note that power is applied to the factor. When describing a translation of * 1000, either use {factor = 1000, power = 1} or {factor = 1, power = 3}. */ typedef struct ecs_unit_translation_t { - int32_t factor; /* Factor to apply (e.g. "1000", "1000000", "1024") */ - int32_t power; /* Power to apply to factor (e.g. "1", "3", "-9") */ + int32_t factor; /**< Factor to apply (e.g. "1000", "1000000", "1024") */ + int32_t power; /**< Power to apply to factor (e.g. "1", "3", "-9") */ } ecs_unit_translation_t; typedef struct EcsUnit { char *symbol; - ecs_entity_t prefix; /* Order of magnitude prefix relative to derived */ - ecs_entity_t base; /* Base unit (e.g. "meters") */ - ecs_entity_t over; /* Over unit (e.g. "per second") */ - ecs_unit_translation_t translation; /* Translation for derived unit */ + ecs_entity_t prefix; /**< Order of magnitude prefix relative to derived */ + ecs_entity_t base; /**< Base unit (e.g. "meters") */ + ecs_entity_t over; /**< Over unit (e.g. "per second") */ + ecs_unit_translation_t translation; /**< Translation for derived unit */ } EcsUnit; typedef struct EcsUnitPrefix { - char *symbol; /* Symbol of prefix (e.g. "K", "M", "Ki") */ - ecs_unit_translation_t translation; /* Translation of prefix */ + char *symbol; /**< Symbol of prefix (e.g. "K", "M", "Ki") */ + ecs_unit_translation_t translation; /**< Translation of prefix */ } EcsUnitPrefix; -/** Serializer utilities */ +/* Serializer utilities */ typedef enum ecs_meta_type_op_kind_t { EcsOpArray, @@ -11848,12 +12491,12 @@ typedef enum ecs_meta_type_op_kind_t { EcsOpPush, EcsOpPop, - EcsOpScope, /* Marks last constant that can open/close a scope */ + EcsOpScope, /**< Marks last constant that can open/close a scope */ EcsOpEnum, EcsOpBitmask, - EcsOpPrimitive, /* Marks first constant that's a primitive */ + EcsOpPrimitive, /**< Marks first constant that's a primitive */ EcsOpBool, EcsOpChar, @@ -11877,37 +12520,38 @@ typedef enum ecs_meta_type_op_kind_t { typedef struct ecs_meta_type_op_t { ecs_meta_type_op_kind_t kind; - ecs_size_t offset; /* Offset of current field */ - int32_t count; - const char *name; /* Name of value (only used for struct members) */ - int32_t op_count; /* Number of operations until next field or end */ - ecs_size_t size; /* Size of type of operation */ + ecs_size_t offset; /**< Offset of current field */ + int32_t count; + const char *name; /**< Name of value (only used for struct members) */ + int32_t op_count; /**< Number of operations until next field or end */ + ecs_size_t size; /**< Size of type of operation */ ecs_entity_t type; ecs_entity_t unit; - ecs_hashmap_t *members; /* string -> member index (structs only) */ + ecs_hashmap_t *members; /**< string -> member index (structs only) */ } ecs_meta_type_op_t; typedef struct EcsMetaTypeSerialized { - ecs_vector_t* ops; /* vector */ + ecs_vector_t* ops; /**< vector */ } EcsMetaTypeSerialized; -/** Deserializer utilities */ +/* Deserializer utilities */ #define ECS_META_MAX_SCOPE_DEPTH (32) /* >32 levels of nesting is not sane */ typedef struct ecs_meta_scope_t { - ecs_entity_t type; /* The type being iterated */ - ecs_meta_type_op_t *ops; /* The type operations (see ecs_meta_type_op_t) */ - int32_t op_count; /* Number of operations in ops array to process */ - int32_t op_cur; /* Current operation */ - int32_t elem_cur; /* Current element (for collections) */ - void *ptr; /* Pointer to the value being iterated */ + ecs_entity_t type; /**< The type being iterated */ + ecs_meta_type_op_t *ops; /**< The type operations (see ecs_meta_type_op_t) */ + int32_t op_count; /**< Number of operations in ops array to process */ + int32_t op_cur; /**< Current operation */ + int32_t elem_cur; /**< Current element (for collections) */ + int32_t prev_depth; /**< Depth to restore, in case dotmember was used */ + void *ptr; /**< Pointer to the value being iterated */ - const EcsComponent *comp; /* Pointer to component, in case size/alignment is needed */ - ecs_vector_t **vector; /* Current vector, in case a vector is iterated */ - bool is_collection; /* Is the scope iterating elements? */ - bool is_inline_array; /* Is the scope iterating an inline array? */ + const EcsComponent *comp; /**< Pointer to component, in case size/alignment is needed */ + ecs_vector_t **vector; /**< Current vector, in case a vector is iterated */ + bool is_collection; /**< Is the scope iterating elements? */ + bool is_inline_array; /**< Is the scope iterating an inline array? */ } ecs_meta_scope_t; /** Type that enables iterating/populating a value using reflection data */ @@ -11916,7 +12560,7 @@ typedef struct ecs_meta_cursor_t { ecs_meta_scope_t scope[ECS_META_MAX_SCOPE_DEPTH]; int32_t depth; bool valid; - bool is_primitive_scope; /* If in root scope, this allows for a push for primitive types */ + bool is_primitive_scope; /**< If in root scope, this allows for a push for primitive types */ /* Custom entity lookup action for overriding default ecs_lookup_fullpath */ ecs_entity_t (*lookup_action)(const ecs_world_t*, const char*, void*); @@ -11951,6 +12595,12 @@ int ecs_meta_member( ecs_meta_cursor_t *cursor, const char *name); +/** Move cursor to member, supports dot-separated nested members */ +FLECS_API +int ecs_meta_dotmember( + ecs_meta_cursor_t *cursor, + const char *name); + /** Push a scope (required/only valid for structs & collections) */ FLECS_API int ecs_meta_push( @@ -11981,7 +12631,7 @@ FLECS_API const char* ecs_meta_get_member( const ecs_meta_cursor_t *cursor); -/** The set functions assign the field with the specified value. If the value +/* The set functions assign the field with the specified value. If the value * does not have the same type as the field, it will be cased to the field type. * If no valid conversion is available, the operation will fail. */ @@ -12044,7 +12694,7 @@ int ecs_meta_set_value( ecs_meta_cursor_t *cursor, const ecs_value_t *value); -/** Functions for getting members. */ +/* Functions for getting members. */ /** Get field value as boolean. */ FLECS_API @@ -12085,7 +12735,7 @@ FLECS_API ecs_entity_t ecs_meta_get_entity( const ecs_meta_cursor_t *cursor); -/** API functions for creating meta types */ +/* API functions for creating meta types */ /** Used with ecs_primitive_init. */ typedef struct ecs_primitive_desc_t { @@ -12172,27 +12822,27 @@ ecs_entity_t ecs_struct_init( /** Used with ecs_unit_init. */ typedef struct ecs_unit_desc_t { - /* Existing entity to associate with unit (optional) */ + /** Existing entity to associate with unit (optional) */ ecs_entity_t entity; - - /* Unit symbol, e.g. "m", "%", "g". (optional) */ + + /** Unit symbol, e.g. "m", "%", "g". (optional) */ const char *symbol; - /* Unit quantity, e.g. distance, percentage, weight. (optional) */ + /** Unit quantity, e.g. distance, percentage, weight. (optional) */ ecs_entity_t quantity; - /* Base unit, e.g. "meters" (optional) */ + /** Base unit, e.g. "meters" (optional) */ ecs_entity_t base; - /* Over unit, e.g. "per second" (optional) */ + /** Over unit, e.g. "per second" (optional) */ ecs_entity_t over; - /* Translation to apply to derived unit (optional) */ + /** Translation to apply to derived unit (optional) */ ecs_unit_translation_t translation; - /* Prefix indicating order of magnitude relative to the derived unit. If set - * together with "translation", the values must match. If translation is not - * set, setting prefix will autopopulate it. + /** Prefix indicating order of magnitude relative to the derived unit. If set + * together with "translation", the values must match. If translation is not + * set, setting prefix will autopopulate it. * Additionally, setting the prefix will enforce that the symbol (if set) * is consistent with the prefix symbol + symbol of the derived unit. If the * symbol is not set, it will be auto populated. */ @@ -12207,13 +12857,13 @@ ecs_entity_t ecs_unit_init( /** Used with ecs_unit_prefix_init. */ typedef struct ecs_unit_prefix_desc_t { - /* Existing entity to associate with unit prefix (optional) */ + /** Existing entity to associate with unit prefix (optional) */ ecs_entity_t entity; - - /* Unit symbol, e.g. "m", "%", "g". (optional) */ + + /** Unit symbol, e.g. "m", "%", "g". (optional) */ const char *symbol; - /* Translation to apply to derived unit (optional) */ + /** Translation to apply to derived unit (optional) */ ecs_unit_translation_t translation; } ecs_unit_prefix_desc_t; @@ -12269,6 +12919,8 @@ void FlecsMetaImport( #endif +/** @} */ + #endif #endif @@ -12277,7 +12929,7 @@ void FlecsMetaImport( #error "FLECS_NO_EXPR failed: EXPR is required by other addons" #endif /** - * @file expr.h + * @file addons/expr.h * @brief Flecs expression parser addon. * * Parse expression strings into component values. The notation is similar to @@ -12323,6 +12975,14 @@ void FlecsMetaImport( extern "C" { #endif +/** + * @defgroup c_addons_expr Expr + * @brief Serialize/deserialize values to string. + * + * \ingroup c_addons + * @{ + */ + /** Write an escaped character. * Write a character to an output string, insert escape character if necessary. * @@ -12542,6 +13202,8 @@ const char *ecs_parse_expr_token( const char *ptr, char *token); +/** @} */ + #ifdef __cplusplus } #endif @@ -12556,12 +13218,20 @@ const char *ecs_parse_expr_token( #error "FLECS_NO_META_C failed: META_C is required by other addons" #endif /** - * @file meta_c.h + * @file addons/meta_c.h * @brief Utility macros for populating reflection data in C. */ #ifdef FLECS_META_C +/** + * @defgroup c_addons_meta_c Meta Utilities + * @brief Macro utilities to automatically insert reflection data. + * + * \ingroup c_addons + * @{ + */ + #ifndef FLECS_META #define FLECS_META #endif @@ -12577,8 +13247,6 @@ const char *ecs_parse_expr_token( extern "C" { #endif -/* Public API */ - /* Macro that controls behavior of API. Usually set in module header. When the * macro is not defined, it defaults to IMPL. */ @@ -12702,6 +13370,8 @@ int ecs_meta_from_desc( #endif // FLECS_META_C_H +/** @} */ + #endif // FLECS_META_C #endif @@ -12710,7 +13380,7 @@ int ecs_meta_from_desc( #error "FLECS_NO_PLECS failed: PLECS is required by other addons" #endif /** - * @file pecs.h + * @file addons/plecs.h * @brief Plecs addon. * * Plecs is a small data definition language for instantiating entities that @@ -12745,6 +13415,14 @@ int ecs_meta_from_desc( #ifdef FLECS_PLECS +/** + * @defgroup c_addons_plecs Plecs + * @brief Data definition format for loading entity data. + * + * \ingroup c_addons + * @{ + */ + #ifndef FLECS_PARSER #define FLECS_PARSER #endif @@ -12790,6 +13468,8 @@ int ecs_plecs_from_file( #endif +/** @} */ + #endif #endif @@ -12797,9 +13477,8 @@ int ecs_plecs_from_file( #ifdef FLECS_NO_RULES #error "FLECS_NO_RULES failed: RULES is required by other addons" #endif - /** - * @file rules.h + * @file addons/rules.h * @brief Rule query engine addon. * * Rules are advanced queries that in addition to the capabilities of regular @@ -12813,6 +13492,14 @@ int ecs_plecs_from_file( #ifdef FLECS_RULES +/** + * @defgroup c_addons_rules Rules + * @brief Rules are an advanced query engine for matching against entity graphs. + * + * \ingroup c_addons + * @{ + */ + #ifndef FLECS_RULES_H #define FLECS_RULES_H @@ -12991,6 +13678,8 @@ char* ecs_rule_str( #endif // FLECS_RULES_H +/** @} */ + #endif // FLECS_RULES #endif @@ -12999,7 +13688,7 @@ char* ecs_rule_str( #error "FLECS_NO_SNAPSHOT failed: SNAPSHOT is required by other addons" #endif /** - * @file snapshot.h + * @file addons/snapshot.h * @brief Snapshot addon. * * A snapshot records the state of a world in a way so that it can be restored @@ -13012,6 +13701,14 @@ char* ecs_rule_str( #ifdef FLECS_SNAPSHOT +/** + * @defgroup c_addons_snapshot Snapshot + * @brief Save & restore world. + * + * \ingroup c_addons + * @{ + */ + #ifndef FLECS_SNAPSHOT_H #define FLECS_SNAPSHOT_H @@ -13094,6 +13791,8 @@ void ecs_snapshot_free( #endif +/** @} */ + #endif #endif @@ -13102,7 +13801,7 @@ void ecs_snapshot_free( #error "FLECS_NO_PARSER failed: PARSER is required by other addons" #endif /** - * @file parser.h + * @file addons/parser.h * @brief Parser addon. * * The parser addon parses string expressions into lists of terms, and can be @@ -13111,6 +13810,14 @@ void ecs_snapshot_free( #ifdef FLECS_PARSER +/** + * @defgroup c_addons_parser Parser + * @brief Query DSL parser and parsing utilities. + * + * \ingroup c_addons + * @{ + */ + #ifndef FLECS_PARSER_H #define FLECS_PARSER_H @@ -13176,7 +13883,8 @@ const char* ecs_parse_token( const char *name, const char *expr, const char *ptr, - char *token_out); + char *token_out, + char delim); /** Parse term in expression. * This operation parses a single term in an expression and returns a pointer @@ -13219,6 +13927,8 @@ char* ecs_parse_term( #endif // FLECS_PARSER_H +/** @} */ + #endif // FLECS_PARSER #endif @@ -13227,7 +13937,7 @@ char* ecs_parse_term( #error "FLECS_NO_HTTP failed: HTTP is required by other addons" #endif /** - * @file http.h + * @file addons/http.h * @brief HTTP addon. * * Minimalistic HTTP server that can receive and reply to simple HTTP requests. @@ -13247,6 +13957,14 @@ char* ecs_parse_term( #ifdef FLECS_HTTP +/** + * @defgroup c_addons_http Http + * @brief Simple HTTP server used for serving up REST API. + * + * \ingroup c_addons + * @{ + */ + #if !defined(FLECS_OS_API_IMPL) && !defined(FLECS_NO_OS_API_IMPL) #define FLECS_OS_API_IMPL #endif @@ -13309,16 +14027,27 @@ typedef struct { /** A reply */ typedef struct { - int code; /* default = 200 */ - ecs_strbuf_t body; /* default = "" */ - const char* status; /* default = OK */ - const char* content_type; /* default = application/json */ - ecs_strbuf_t headers; /* default = "" */ + int code; /**< default = 200 */ + ecs_strbuf_t body; /**< default = "" */ + const char* status; /**< default = OK */ + const char* content_type; /**< default = application/json */ + ecs_strbuf_t headers; /**< default = "" */ } ecs_http_reply_t; #define ECS_HTTP_REPLY_INIT \ (ecs_http_reply_t){200, ECS_STRBUF_INIT, "OK", "application/json", ECS_STRBUF_INIT} +/* Global statistics. */ +extern int64_t ecs_http_request_received_count; +extern int64_t ecs_http_request_invalid_count; +extern int64_t ecs_http_request_handled_ok_count; +extern int64_t ecs_http_request_handled_error_count; +extern int64_t ecs_http_request_not_handled_count; +extern int64_t ecs_http_request_preflight_count; +extern int64_t ecs_http_send_ok_count; +extern int64_t ecs_http_send_error_count; +extern int64_t ecs_http_busy_count; + /** Request callback. * Invoked for each valid request. The function should populate the reply and * return true. When the function returns false, the server will reply with a @@ -13330,11 +14059,11 @@ typedef bool (*ecs_http_reply_action_t)( /** Used with ecs_http_server_init. */ typedef struct { - ecs_http_reply_action_t callback; /* Function called for each request */ - void *ctx; /* Passed to callback (optional) */ - uint16_t port; /* HTTP port */ - const char *ipaddr; /* Interface to listen on (optional) */ - int32_t send_queue_wait_ms; /* Send queue wait time when empty */ + ecs_http_reply_action_t callback; /**< Function called for each request */ + void *ctx; /**< Passed to callback (optional) */ + uint16_t port; /**< HTTP port */ + const char *ipaddr; /**< Interface to listen on (optional) */ + int32_t send_queue_wait_ms; /**< Send queue wait time when empty */ } ecs_http_server_desc_t; /** Create server. @@ -13412,6 +14141,8 @@ const char* ecs_http_get_param( } #endif +/** @} */ + #endif // FLECS_HTTP_H #endif // FLECS_HTTP @@ -13422,12 +14153,20 @@ const char* ecs_http_get_param( #error "FLECS_NO_OS_API_IMPL failed: OS_API_IMPL is required by other addons" #endif /** - * @file os_api_impl.h + * @file addons/os_api_impl.h * @brief Default OS API implementation. */ #ifdef FLECS_OS_API_IMPL +/** + * @defgroup c_addons_os_api_impl OS API Implementation + * @brief Default implementation for OS API interface. + * + * \ingroup c_addons + * @{ + */ + #ifndef FLECS_OS_API_IMPL_H #define FLECS_OS_API_IMPL_H @@ -13444,6 +14183,8 @@ void ecs_set_os_api_impl(void); #endif // FLECS_OS_API_IMPL_H +/** @} */ + #endif // FLECS_OS_API_IMPL #endif @@ -13452,7 +14193,7 @@ void ecs_set_os_api_impl(void); #error "FLECS_NO_MODULE failed: MODULE is required by other addons" #endif /** - * @file module.h + * @file addons/module.h * @brief Module addon. * * The module addon allows for creating and importing modules. Flecs modules @@ -13462,6 +14203,14 @@ void ecs_set_os_api_impl(void); #ifdef FLECS_MODULE +/** + * @defgroup c_addons_module Module + * @brief Modules organize components, systems and more in reusable units of code. + * + * \ingroup c_addons + * @{ + */ + #ifndef FLECS_MODULE_H #define FLECS_MODULE_H @@ -13508,8 +14257,8 @@ ecs_entity_t ecs_import_c( ecs_module_action_t module, const char *module_name_c); -/* Import a module from a library. - * Similar to ecs_import, except that this operation will attempt to load the +/** Import a module from a library. + * Similar to ecs_import, except that this operation will attempt to load the * module from a dynamic library. * * A library may contain multiple modules, which is why both a library name and @@ -13532,16 +14281,14 @@ ecs_entity_t ecs_import_from_library( const char *library_name, const char *module_name); -/** Register a new module. - */ +/** Register a new module. */ FLECS_API ecs_entity_t ecs_module_init( ecs_world_t *world, const char *c_name, const ecs_component_desc_t *desc); -/** Define module - */ +/** Define module. */ #define ECS_MODULE_DEFINE(world, id)\ {\ ecs_component_desc_t desc = {0};\ @@ -13552,7 +14299,7 @@ ecs_entity_t ecs_module_init( #define ECS_MODULE(world, id)\ ecs_entity_t ecs_id(id) = 0; ECS_MODULE_DEFINE(world, id)\ - (void)ecs_id(id);\ + (void)ecs_id(id); /** Wrapper around ecs_import. * This macro provides a convenient way to load a module with the world. It can @@ -13568,6 +14315,8 @@ ecs_entity_t ecs_module_init( #endif +/** @} */ + #endif #endif @@ -13577,7 +14326,7 @@ ecs_entity_t ecs_module_init( #error "FLECS_NO_CPP failed: CPP is required by other addons" #endif /** - * @file flecs_cpp.h + * @file addons/flecs_cpp.h * @brief C++ utility functions * * This header contains utility functions that are accessible from both C and @@ -13641,6 +14390,7 @@ 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); @@ -13701,10 +14451,8 @@ int32_t ecs_cpp_reset_count_inc(void); } /** - * @file flecs.hpp - * @brief Flecs C++ API. - * - * Modern C++11 API + * @file addons/cpp/flecs.hpp + * @brief Flecs C++11 API. */ #pragma once @@ -13712,7 +14460,11 @@ int32_t ecs_cpp_reset_count_inc(void); // STL includes #include -// Forward declarations +/** + * @defgroup cpp C++ API + * @{ + */ + namespace flecs { @@ -13723,6 +14475,7 @@ struct entity_view; struct entity; struct type; struct table; +struct table_range; struct untyped_component; template @@ -13740,12 +14493,23 @@ struct each_invoker; } // namespace flecs // Types imported from C API -//////////////////////////////////////////////////////////////////////////////// -//// Aliases for types/constants from C API -//////////////////////////////////////////////////////////////////////////////// +/** + * @file addons/cpp/c_types.hpp + * @brief Aliases for types/constants from C API + */ + +#pragma once namespace flecs { +/** + * @defgroup cpp_globals API Types & Globals + * @brief Types & constants bridged from C API. + * + * \ingroup cpp_core + * @{ + */ + using world_t = ecs_world_t; using world_info_t = ecs_world_info_t; using query_group_info_t = ecs_query_group_info_t; @@ -13754,6 +14518,7 @@ using entity_t = ecs_entity_t; using type_t = ecs_type_t; using table_t = ecs_table_t; using filter_t = ecs_filter_t; +using observer_t = ecs_observer_t; using query_t = ecs_query_t; using rule_t = ecs_rule_t; using ref_t = ecs_ref_t; @@ -13862,15 +14627,20 @@ static const flecs::entity_t Remove = EcsRemove; static const flecs::entity_t Delete = EcsDelete; static const flecs::entity_t Panic = EcsPanic; +/** @} */ + } // C++ utilities -//////////////////////////////////////////////////////////////////////////////// -//// Flecs STL (FTL?) -//// Minimalistic utilities that allow for STL like functionality without having -//// to depend on the actual STL. -//////////////////////////////////////////////////////////////////////////////// +/** + * @file addons/cpp/utils/utils.hpp + * @brief Flecs STL (FTL?) + * + * Flecs STL (FTL?) + * Minimalistic utilities that allow for STL like functionality without having + * to depend on the actual STL. + */ // Macros so that C++ new calls can allocate using ecs_os_api memory allocation functions // Rationale: @@ -14003,8 +14773,13 @@ struct always_false { } // namespace flecs #include -// Array class. Simple std::array like utility that is mostly there to aid -// template code where template expansion would lead to an array with size 0. +/** + * @file addons/cpp/utils/array.hpp + * @brief Array class. + * + * Array class. Simple std::array like utility that is mostly there to aid + * template code where template expansion would lead to an array with size 0. + */ namespace flecs { @@ -14111,7 +14886,10 @@ struct array> final { } -// String utility that doesn't implicitly allocate memory. +/** + * @file addons/cpp/utils/string.hpp + * @brief String utility that doesn't implicitly allocate memory. + */ namespace flecs { @@ -14252,6 +15030,14 @@ struct string_view : string { } +/** + * @file addons/cpp/utils/enum.hpp + * @brief Compile time enum reflection utilities. + * + * Discover at compile time valid enumeration constants for an enumeration type + * and their names. This is used to automatically register enum constants. + */ + #include #define FLECS_ENUM_MAX(T) _::to_constant::value @@ -14263,7 +15049,12 @@ namespace flecs { namespace _ { template struct to_constant { +#if defined(__clang__) && __clang_major__ >= 16 + // https://reviews.llvm.org/D130058, https://reviews.llvm.org/D131307 + static constexpr E value = __builtin_bit_cast(E, Value); +#else static constexpr E value = static_cast(Value); +#endif }; template @@ -14315,8 +15106,8 @@ constexpr size_t enum_type_len() { * This function leverages that when a valid value is provided, * __PRETTY_FUNCTION__ contains the enumeration name, whereas if a value is * invalid, the string contains a number. */ -#if defined(__clang__) -#if __clang_major__ < 13 || (defined(__APPLE__) && __clang_minor__ < 1) +#if defined(ECS_TARGET_CLANG) +#if ECS_CLANG_VERSION < 13 template constexpr bool enum_constant_is_valid() { return !( @@ -14332,7 +15123,7 @@ constexpr bool enum_constant_is_valid() { enum_type_len() + 6 /* ', E C = ' */] != '('); } #endif -#elif defined(__GNUC__) +#elif defined(ECS_TARGET_GNU) template constexpr bool enum_constant_is_valid() { return (ECS_FUNC_NAME[ECS_FUNC_NAME_FRONT(constepxr bool, enum_constant_is_valid) + @@ -14501,7 +15292,10 @@ enum_data enum_type(flecs::world_t *world) { } // namespace flecs -// Wrapper around ecs_strbuf_t that provides a simple stringstream like API. +/** + * @file addons/cpp/utils/stringstream.hpp + * @brief Wrapper around ecs_strbuf_t that provides a simple stringstream like API. + */ namespace flecs { @@ -14545,9 +15339,12 @@ private: } - -// Neat utility to inspect arguments & returntype of a function type -// Code from: https://stackoverflow.com/questions/27024238/c-template-mechanism-to-get-the-number-of-function-arguments-which-would-work +/** + * @file addons/cpp/utils/function_traits.hpp + * @brief Compile time utilities to inspect properties of functions. + * + * Code from: https://stackoverflow.com/questions/27024238/c-template-mechanism-to-get-the-number-of-function-arguments-which-would-work + */ namespace flecs { namespace _ { @@ -14678,6 +15475,11 @@ using first_arg_t = typename first_arg::type; // Mixin forward declarations +/** + * @file addons/cpp/mixins/id/decl.hpp + * @brief Id class. + */ + #pragma once namespace flecs { @@ -14685,9 +15487,19 @@ namespace flecs { struct id; struct entity; -/** Class that stores a flecs id. - * A flecs id is an identifier that can store an entity id, an first-second - * pair, or role annotated id (such as SWITCH | Movement). +/** + * @defgroup cpp_ids Ids + * @brief Class for working with entity, component, tag and pair ids. + * + * \ingroup cpp_core + * @{ + */ + +/** Class that wraps around a flecs::id_t. + * A flecs id is an identifier that can be added to entities. Ids can be: + * - entities (including components, tags) + * - pair ids + * - entities with id flags set (like flecs::Override, flecs::Toggle) */ struct id { id() @@ -14719,47 +15531,48 @@ struct id { return (m_id & ECS_ID_FLAGS_MASK) == flecs::Pair; } - /* Test if id is a wildcard */ + /** Test if id is a wildcard */ bool is_wildcard() const { return ecs_id_is_wildcard(m_id); } - /* Test if id is entity */ + /** Test if id is entity */ bool is_entity() const { return !(m_id & ECS_ID_FLAGS_MASK); } - /* Return id as entity (only allowed when id is valid entity) */ + /** Return id as entity (only allowed when id is valid entity) */ flecs::entity entity() const; - /* Return id with role added */ + /** Return id with role added */ flecs::entity add_flags(flecs::id_t flags) const; - /* Return id with role removed */ + /** Return id with role removed */ flecs::entity remove_flags(flecs::id_t flags) const; - /* Return id without role */ + /** Return id without role */ flecs::entity remove_flags() const; - /* Return id without role */ + /** Return id without role */ flecs::entity remove_generation() const; - /* Return component type of id */ + /** Return component type of id */ flecs::entity type_id() const; - /* Test if id has specified role */ + /** Test if id has specified role */ bool has_flags(flecs::id_t flags) const { return ((m_id & flags) == flags); } - /* Test if id has any role */ + /** Test if id has any role */ bool has_flags() const { return (m_id & ECS_ID_FLAGS_MASK) != 0; } + /** Return id flags set on id */ flecs::entity flags() const; - /* Test if id has specified first */ + /** Test if id has specified first */ bool has_relation(flecs::id_t first) const { if (!is_pair()) { return false; @@ -14789,6 +15602,7 @@ struct id { return flecs::string_view( ecs_id_flag_str(m_id & ECS_ID_FLAGS_MASK)); } + /** Return flecs::id_t value */ flecs::id_t raw_id() const { return m_id; } @@ -14806,21 +15620,47 @@ protected: flecs::id_t m_id; }; +/** @} */ + } +/** + * @file addons/cpp/mixins/term/decl.hpp + * @brief Term declarations. + */ + #pragma once namespace flecs { +/** + * \ingroup cpp_core_filters + */ + struct term; struct term_builder; +/** @} */ + } +/** + * @file addons/cpp/mixins/filter/decl.hpp + * @brief Filter declarations. + */ + #pragma once namespace flecs { +/** + * @defgroup cpp_core_filters Filters + * @brief Filters are are cheaper to create, but slower to iterate than flecs::query. + * + * \ingroup cpp_core + * @{ + */ + struct filter_base; template @@ -14829,17 +15669,34 @@ struct filter; template struct filter_builder; +/** @} */ + } +/** + * @file addons/cpp/mixins/event/decl.hpp + * @brief Event declarations. + */ + #pragma once +/** + * @file addons/cpp/mixins/event/builder.hpp + * @brief Event builder. + */ + #pragma once #define ECS_EVENT_DESC_ID_COUNT_MAX (8) namespace flecs { -// Event builder interface +/** + * \ingroup cpp_addons_event + * @{ + */ + +/** Event builder interface */ template struct event_builder_base { event_builder_base(flecs::world_t *world, flecs::entity_t event) @@ -14967,13 +15824,28 @@ public: } }; +/** @} */ + } +/** + * @file addons/cpp/mixins/query/decl.hpp + * @brief Query declarations. + */ + #pragma once namespace flecs { +/** + * @defgroup cpp_core_queries Queries + * @brief Cached query implementation. Fast to iterate, but slower to create than flecs::filter. + * + * \ingroup cpp_core + * @{ + */ + struct query_base; template @@ -14982,24 +15854,54 @@ struct query; template struct query_builder; +/** @} */ + } +/** + * @file addons/cpp/mixins/observer/decl.hpp + * @brief Observer declarations. + */ + #pragma once namespace flecs { +/** + * @defgroup cpp_observers Observers + * @brief Observers let applications register callbacks for ECS events. + * + * \ingroup cpp_core + * @{ + */ + struct observer; template struct observer_builder; +/** @} */ + } #ifdef FLECS_SYSTEM +/** + * @file addons/cpp/mixins/system/decl.hpp + * @brief System module declarations. + */ + #pragma once namespace flecs { +/** + * @defgroup cpp_addons_systems Systems + * @brief Systems are a query + function that can be ran manually or by a pipeline. + * + * \ingroup cpp_addons + * @{ + */ + using TickSource = EcsTickSource; struct system; @@ -15011,15 +15913,30 @@ namespace _ { void system_init(flecs::world& world); +/** @} */ + } // namespace _ } // namespace flecs #endif #ifdef FLECS_PIPELINE +/** + * @file addons/cpp/mixins/pipeline/decl.hpp + * @brief Pipeline module declarations. + */ + #pragma once namespace flecs { +/** + * @defgroup cpp_pipelines Pipelines + * @brief Pipelines order and schedule systems for execution. + * + * \ingroup cpp_addons + * @{ + */ + template struct pipeline; @@ -15027,6 +15944,7 @@ template struct pipeline_builder; /* Builtin pipeline tags */ +static const flecs::entity_t OnStart = EcsOnStart; static const flecs::entity_t PreFrame = EcsPreFrame; static const flecs::entity_t OnLoad = EcsOnLoad; static const flecs::entity_t PostLoad = EcsPostLoad; @@ -15038,19 +15956,36 @@ static const flecs::entity_t PreStore = EcsPreStore; static const flecs::entity_t OnStore = EcsOnStore; static const flecs::entity_t PostFrame = EcsPostFrame; +/** @} */ + } #endif #ifdef FLECS_TIMER +/** + * @file addons/cpp/mixins/timer/decl.hpp + * @brief Timer module declarations. + */ + #pragma once namespace flecs { +/** + * @defgroup cpp_addons_timer Timer + * @brief Run systems at a time interval. + * + * \ingroup cpp_addons + * @{ + */ + using Timer = EcsTimer; using RateFilter = EcsRateFilter; struct timer; +/** @} */ + namespace _ { void timer_init(flecs::world& world); @@ -15060,42 +15995,94 @@ void timer_init(flecs::world& world); #endif #ifdef FLECS_SNAPSHOT +/** + * @file addons/cpp/mixins/snapshot/decl.hpp + * @brief Snapshot module declarations. + */ + #pragma once namespace flecs { +/** + * @defgroup cpp_addons_snapshots Snapshots + * @brief Save & restore world. + * + * \ingroup cpp_addons + * @{ + */ + using snapshot_t = ecs_snapshot_t; struct snapshot; +/** @} */ + } #endif #ifdef FLECS_DOC +/** + * @file addons/cpp/mixins/doc/decl.hpp + * @brief Doc mixin declarations. + */ + #pragma once namespace flecs { namespace doc { +/** + * @defgroup cpp_addons_doc Doc + * @brief Utilities for documenting entities, components and systems. + * + * \ingroup cpp_addons + * @{ + */ + +/** flecs.doc.Description component */ using Description = EcsDocDescription; + +/** flecs.doc.Brief component */ static const flecs::entity_t Brief = EcsDocBrief; + +/** flecs.doc.Detail component */ static const flecs::entity_t Detail = EcsDocDetail; + +/** flecs.doc.Link component */ static const flecs::entity_t Link = EcsDocLink; + +/** flecs.doc.Color component */ static const flecs::entity_t Color = EcsDocColor; namespace _ { void init(flecs::world& world); } +/** @} */ + } } #endif #ifdef FLECS_REST +/** + * @file addons/cpp/mixins/rest/decl.hpp + * @brief Rest module declarations. + */ + #pragma once namespace flecs { +/** + * @defgroup cpp_addons_rest Rest + * @brief REST API for querying and mutating entities. + * + * \ingroup cpp_addons + * @{ + */ + using Rest = EcsRest; namespace rest { @@ -15106,14 +16093,30 @@ void init(flecs::world& world); } } + +/** @} */ + } #endif #ifdef FLECS_RULES +/** + * @file addons/cpp/mixins/rule/decl.hpp + * @brief Rule declarations. + */ + #pragma once namespace flecs { +/** + * @defgroup cpp_addons_rules Rules + * @brief Rules are an advanced query engine for matching against entity graphs. + * + * \ingroup cpp_addons + * @{ + */ + struct rule_base; template @@ -15122,14 +16125,29 @@ struct rule; template struct rule_builder; +/** @} */ + } #endif #ifdef FLECS_META +/** + * @file addons/cpp/mixins/meta/decl.hpp + * @brief Meta declarations. + */ + #pragma once namespace flecs { +/** + * @defgroup cpp_addons_meta Meta + * @brief Flecs reflection framework. + * + * \ingroup cpp_addons + * @{ + */ + using bool_t = ecs_bool_t; using char_t = ecs_char_t; using u8_t = ecs_u8_t; @@ -15162,6 +16180,11 @@ using Array = EcsArray; using Vector = EcsVector; using Unit = EcsUnit; +/** Base type for bitmasks */ +struct bitmask { + uint32_t value; +}; + static const flecs::entity_t Bool = ecs_id(ecs_bool_t); static const flecs::entity_t Char = ecs_id(ecs_char_t); static const flecs::entity_t Byte = ecs_id(ecs_byte_t); @@ -15185,113 +16208,145 @@ static const flecs::entity_t Quantity = EcsQuantity; namespace meta { +/** Class for reading/writing dynamic values. + * + * \ingroup cpp_addons_meta + */ struct cursor { cursor(flecs::world_t *world, flecs::entity_t type_id, void *ptr) { m_cursor = ecs_meta_cursor(world, type_id, ptr); } + /** Push value scope (such as a nested struct) */ int push() { return ecs_meta_push(&m_cursor); } + /** Pop value scope */ int pop() { return ecs_meta_pop(&m_cursor); } + /** Move to next member/element */ int next() { return ecs_meta_next(&m_cursor); } + /** Move to member by name */ int member(const char *name) { return ecs_meta_member(&m_cursor, name); } + /** Move to element by index */ int elem(int32_t elem) { return ecs_meta_elem(&m_cursor, elem); } + /** Test if current scope is a collection type */ bool is_collection() { return ecs_meta_is_collection(&m_cursor); } + /** Get member name */ flecs::string_view get_member() const { return flecs::string_view(ecs_meta_get_member(&m_cursor)); } + /** Get type of value */ flecs::entity get_type() const; + /** Get unit of value */ flecs::entity get_unit() const; + /** Get untyped pointer to value */ void* get_ptr() { return ecs_meta_get_ptr(&m_cursor); } + /** Set boolean value */ int set_bool(bool value) { return ecs_meta_set_bool(&m_cursor, value); } + /** Set char value */ int set_char(char value) { return ecs_meta_set_char(&m_cursor, value); } + /** Set signed int value */ int set_int(int64_t value) { return ecs_meta_set_int(&m_cursor, value); } + /** Set unsigned int value */ int set_uint(uint64_t value) { return ecs_meta_set_uint(&m_cursor, value); } + /** Set float value */ int set_float(double value) { return ecs_meta_set_float(&m_cursor, value); } + /** Set string value */ int set_string(const char *value) { return ecs_meta_set_string(&m_cursor, value); } + /** Set string literal value */ int set_string_literal(const char *value) { return ecs_meta_set_string_literal(&m_cursor, value); } + /** Set entity value */ int set_entity(flecs::entity_t value) { return ecs_meta_set_entity(&m_cursor, value); } + /** Set null value */ int set_null() { return ecs_meta_set_null(&m_cursor); } - + /** Get boolean value */ bool get_bool() const { return ecs_meta_get_bool(&m_cursor); } + /** Get char value */ char get_char() const { return ecs_meta_get_char(&m_cursor); } + /** Get signed int value */ int64_t get_int() const { return ecs_meta_get_int(&m_cursor); } + /** Get unsigned int value */ uint64_t get_uint() const { return ecs_meta_get_uint(&m_cursor); } + /** Get float value */ double get_float() const { return ecs_meta_get_float(&m_cursor); } + /** Get string value */ const char *get_string() const { return ecs_meta_get_string(&m_cursor); } + /** Get entity value */ flecs::entity get_entity() const; + /** Cursor object */ ecs_meta_cursor_t m_cursor; }; +/** @} */ + namespace _ { void init(flecs::world& world); @@ -15302,17 +16357,34 @@ void init(flecs::world& world); #endif #ifdef FLECS_UNITS +/** + * @file addons/cpp/mixins/units/decl.hpp + * @brief Units module declarations. + */ + #pragma once namespace flecs { struct units { -//////////////////////////////////////////////////////////////////////////////// -//// Unit prefixes -//////////////////////////////////////////////////////////////////////////////// +/** + * @defgroup cpp_addons_units Units + * @brief Common unit annotations for reflection framework. + * + * \ingroup cpp_addons + * @{ + */ struct Prefixes { }; +/** + * @defgroup cpp_addons_units_prefixes Prefixes + * @brief Prefixes to indicate unit count (e.g. Kilo, Mega) + * + * \ingroup cpp_addons_units + * @{ + */ + struct Yocto { }; struct Zepto { }; struct Atto { }; @@ -15342,10 +16414,15 @@ struct Exbi { }; struct Zebi { }; struct Yobi { }; +/** @} */ -//////////////////////////////////////////////////////////////////////////////// -//// Quantities -//////////////////////////////////////////////////////////////////////////////// +/** + * @defgroup cpp_addons_units_quantities Quantities + * @brief Quantities that group units (e.g. Length) + * + * \ingroup cpp_addons_units + * @{ + */ struct Duration { }; struct Time { }; @@ -15361,13 +16438,19 @@ struct Temperature { }; struct Data { }; struct DataRate { }; struct Angle { }; +struct Frequency { }; +struct Uri { }; - -//////////////////////////////////////////////////////////////////////////////// -//// Units -//////////////////////////////////////////////////////////////////////////////// +/** @} */ struct duration { +/** + * @defgroup cpp_addons_units_duration Duration + * + * \ingroup cpp_addons_units + * @{ + */ + struct PicoSeconds { }; struct NanoSeconds { }; struct MicroSeconds { }; @@ -15376,39 +16459,118 @@ struct Seconds { }; struct Minutes { }; struct Hours { }; struct Days { }; + +/** @} */ }; struct angle { +/** + * @defgroup cpp_addons_units_angle Angle + * + * \ingroup cpp_addons_units + * @{ + */ + struct Radians { }; struct Degrees { }; + +/** @} */ }; + struct time { +/** + * @defgroup cpp_addons_units_time Time + * + * \ingroup cpp_addons_units + * @{ + */ + struct Date { }; + +/** @} */ }; + struct mass { +/** + * @defgroup cpp_addons_units_mass Mass + * + * \ingroup cpp_addons_units + * @{ + */ + struct Grams { }; struct KiloGrams { }; + +/** @} */ }; + struct electric_current { +/** + * @defgroup cpp_addons_units_electric_current Electric Current + * + * \ingroup cpp_addons_units + * @{ + */ + struct Ampere { }; + +/** @} */ }; + struct amount { +/** + * @defgroup cpp_addons_units_amount Amount + * + * \ingroup cpp_addons_units + * @{ + */ + struct Mole { }; + +/** @} */ }; + struct luminous_intensity { +/** + * @defgroup cpp_addons_units_luminous_intensity Luminous Intensity + * + * \ingroup cpp_addons_units + * @{ + */ + struct Candela { }; + +/** @} */ }; + struct force { +/** + * @defgroup cpp_addons_units_force Force + * + * \ingroup cpp_addons_units + * @{ + */ + struct Newton { }; + +/** @} */ }; + struct length { +/** + * @defgroup cpp_addons_units_length Length + * + * \ingroup cpp_addons_units + * @{ + */ + struct Meters { }; struct PicoMeters { }; struct NanoMeters { }; @@ -15417,27 +16579,68 @@ struct MilliMeters { }; struct CentiMeters { }; struct KiloMeters { }; struct Miles { }; +struct Pixels { }; + +/** @} */ }; + struct pressure { +/** + * @defgroup cpp_addons_units_pressure Pressure + * + * \ingroup cpp_addons_units + * @{ + */ + struct Pascal { }; struct Bar { }; + +/** @} */ }; + struct speed { +/** + * @defgroup cpp_addons_units_speed Speed + * + * \ingroup cpp_addons_units + * @{ + */ + struct MetersPerSecond { }; struct KiloMetersPerSecond { }; struct KiloMetersPerHour { }; struct MilesPerHour { }; + +/** @} */ }; + struct temperature { +/** + * @defgroup cpp_addons_units_temperature Temperature + * + * \ingroup cpp_addons_units + * @{ + */ + struct Kelvin { }; struct Celsius { }; struct Fahrenheit { }; + +/** @} */ }; + struct data { +/** + * @defgroup cpp_addons_units_data Data + * + * \ingroup cpp_addons_units + * @{ + */ + struct Bits { }; struct KiloBits { }; struct MegaBits { }; @@ -15449,9 +16652,18 @@ struct GigaBytes { }; struct KibiBytes { }; struct MebiBytes { }; struct GibiBytes { }; + +/** @} */ }; struct datarate { +/** + * @defgroup cpp_addons_units_datarate Data Rate + * + * \ingroup cpp_addons_units + * @{ + */ + struct BitsPerSecond { }; struct KiloBitsPerSecond { }; struct MegaBitsPerSecond { }; @@ -15460,51 +16672,139 @@ struct BytesPerSecond { }; struct KiloBytesPerSecond { }; struct MegaBytesPerSecond { }; struct GigaBytesPerSecond { }; + +/** @} */ }; + +struct frequency { +/** + * @defgroup cpp_addons_units_frequency Frequency + * + * \ingroup cpp_addons_units + * @{ + */ + +struct Hertz { }; +struct KiloHertz { }; +struct MegaHertz { }; +struct GigaHertz { }; + +/** @} */ +}; + + +struct uri { +/** + * @defgroup cpp_addons_units_uri Uri + * + * \ingroup cpp_addons_units + * @{ + */ + +struct Hyperlink { }; +struct Image { }; +struct File { }; + +/** @} */ +}; + + struct Percentage { }; struct Bel { }; struct DeciBel { }; units(flecs::world& world); +/** @} */ + }; } #endif #ifdef FLECS_MONITOR +/** + * @file addons/cpp/mixins/monitor/decl.hpp + * @brief Monitor module declarations. + */ + #pragma once namespace flecs { +/** + * @defgroup cpp_addons_monitor Monitor + * @brief The monitor addon periodically tracks statistics for the world and systems. + * + * \ingroup cpp_addons + * @{ + */ + +/** Component that stores world statistics */ using WorldStats = EcsWorldStats; + +/** Component that stores system/pipeline statistics */ using PipelineStats = EcsPipelineStats; struct monitor { monitor(flecs::world& world); }; +/** @} */ + } #endif #ifdef FLECS_JSON +/** + * @file addons/cpp/mixins/json/decl.hpp + * @brief JSON addon declarations. + */ + #pragma once namespace flecs { +/** + * @defgroup cpp_addons_json Json + * @brief Functions for serializing to/from JSON. + * + * \ingroup cpp_addons + * @{ + */ + using entity_to_json_desc_t = ecs_entity_to_json_desc_t; using iter_to_json_desc_t = ecs_iter_to_json_desc_t; +/** @} */ + } #endif #ifdef FLECS_APP +/** + * @file addons/cpp/mixins/app/decl.hpp + * @brief App addon declarations. + */ + #pragma once +/** + * @file addons/cpp/mixins/app/builder.hpp + * @brief App builder. + */ + #pragma once namespace flecs { -// App builder interface +/** + * @defgroup cpp_addons_app App + * @brief Optional addon for running the main application loop. + * \ingroup cpp_addons + * @{ + */ + +/** App builder interface */ struct app_builder { app_builder(flecs::world_t *world) : m_world(world) @@ -15559,7 +16859,12 @@ struct app_builder { int run() { int result = ecs_app_run(m_world, &m_desc); - ecs_fini(m_world); // app takes ownership of world + if (ecs_should_quit(m_world)) { + // Only free world if quit flag is set. This ensures that we won't + // try to cleanup the world if the app is used in an environment + // that takes over the main loop, like with emscripten. + ecs_fini(m_world); + } return result; } @@ -15568,32 +16873,52 @@ private: ecs_app_desc_t m_desc; }; -} +/** @} */ +} #endif +/** + * @file addons/cpp/log.hpp + * @brief Logging functions. + */ + +#pragma once namespace flecs { namespace log { +/** + * @defgroup cpp_log Logging + * @brief Logging functions. + * + * \ingroup cpp_addons + * @{ + */ + +/** Set log level */ inline void set_level(int level) { ecs_log_set_level(level); } +/** Enable colors in logging */ inline void enable_colors(bool enabled = true) { ecs_log_enable_colors(enabled); } +/** Enable timestamps in logging */ inline void enable_timestamp(bool enabled = true) { ecs_log_enable_timestamp(enabled); } +/** Enable time delta in logging */ inline void enable_timedelta(bool enabled = true) { ecs_log_enable_timedelta(enabled); } +/** Debug trace (level 1) */ inline void dbg(const char *fmt, ...) { va_list args; va_start(args, fmt); @@ -15601,6 +16926,7 @@ inline void dbg(const char *fmt, ...) { va_end(args); } +/** Trace (level 0) */ inline void trace(const char *fmt, ...) { va_list args; va_start(args, fmt); @@ -15608,6 +16934,7 @@ inline void trace(const char *fmt, ...) { va_end(args); } +/** Trace (level -2) */ inline void warn(const char *fmt, ...) { va_list args; va_start(args, fmt); @@ -15615,6 +16942,7 @@ inline void warn(const char *fmt, ...) { va_end(args); } +/** Trace (level -3) */ inline void err(const char *fmt, ...) { va_list args; va_start(args, fmt); @@ -15622,6 +16950,7 @@ inline void err(const char *fmt, ...) { va_end(args); } +/** Increase log indentation */ inline void push(const char *fmt, ...) { va_list args; va_start(args, fmt); @@ -15630,17 +16959,27 @@ inline void push(const char *fmt, ...) { ecs_log_push(); } +/** Increase log indentation */ inline void push() { ecs_log_push(); } +/** Increase log indentation */ inline void pop() { ecs_log_pop(); } +/** @} */ + } } +/** + * @file addons/cpp/pair.hpp + * @brief Utilities for working with compile time pairs. + */ + +#pragma once namespace flecs { @@ -15649,14 +16988,28 @@ namespace _ { } // _ -// Type that represents a pair and can encapsulate a temporary value +/** + * @defgroup cpp_pair_type Pair type + * @brief Compile time utilities for working with relationship pairs. + * + * \ingroup cpp_core + * @{ + */ + +/** Type that represents a pair. + * The pair type can be used to represent a pair at compile time, and is able + * to automatically derive the storage type associated with the pair, accessible + * through pair::type. + * + * The storage type is derived using the following rules: + * - if pair::first is non-empty, the storage type is pair::first + * - if pair::first is empty and pair::second is non-empty, the storage type is pair::second + * + * The pair type can hold a temporary value so that it can be used in the + * signatures of queries + */ template struct pair : _::pair_base { - // Traits used to deconstruct the pair - - // The actual type of the pair is determined by which type of the pair is - // empty. If both types are empty or not empty, the pair assumes the type - // of the first element. using type = conditional_t::value || is_empty::value, First, Second>; using first = First; using second = Second; @@ -15698,25 +17051,26 @@ template ::value> = 0> using pair_object = pair; -// Utilities to test if type is a pair +/** Test if type is a pair. */ template struct is_pair { static constexpr bool value = is_base_of<_::pair_base, remove_reference_t >::value; }; -// Get actual type, relationship or object from pair while preserving cv qualifiers. +/** Get pair::first from pair while preserving cv qualifiers. */ template using pair_first_t = transcribe_cv_t, typename remove_reference_t

::first>; +/** Get pair::second from pair while preserving cv qualifiers. */ template using pair_second_t = transcribe_cv_t, typename remove_reference_t

::second>; +/** Get pair::type type from pair while preserving cv qualifiers. */ template using pair_type_t = transcribe_cv_t, typename remove_reference_t

::type>; - -// Get actual type from a regular type or pair +/** Get actual type from a regular type or pair. */ template struct actual_type; @@ -15763,6 +17117,13 @@ struct is_actual { } // flecs +/** + * @file addons/cpp/lifecycle_traits.hpp + * @brief Utilities for discovering and registering component lifecycle hooks. + */ + +#pragma once + namespace flecs { @@ -16139,15 +17500,82 @@ ecs_move_t move_dtor() { } // _ } // flecs +/** + * @file addons/cpp/ref.hpp + * @brief Class that caches data to speedup get operations. + */ + +#pragma once namespace flecs { -/** Static helper functions to assign a component value */ +/** + * @defgroup cpp_ref Refs + * @brief Refs are a fast mechanism for referring to a specific entity/component. + * + * \ingroup cpp_core + * @{ + */ + +/** Component reference. + * Reference to a component from a specific entity. + */ +template +struct ref { + ref(world_t *world, entity_t entity, flecs::id_t id = 0) + : m_world( world ) + , m_ref() + { + if (!id) { + id = _::cpp_type::id(world); + } + + ecs_assert(_::cpp_type::size() != 0, ECS_INVALID_PARAMETER, NULL); + + m_ref = ecs_ref_init_id(m_world, entity, id); + } + + T* operator->() { + T* result = static_cast(ecs_ref_get_id( + m_world, &m_ref, this->m_ref.id)); + + ecs_assert(result != NULL, ECS_INVALID_PARAMETER, NULL); + + return result; + } + + T* get() { + return static_cast(ecs_ref_get_id( + m_world, &m_ref, this->m_ref.id)); + } + + flecs::entity entity() const; + +private: + world_t *m_world; + flecs::ref_t m_ref; +}; + +/** @} */ + +} + +/** + * @file addons/cpp/world.hpp + * @brief World class. + */ + +#pragma once + +namespace flecs +{ + +/* Static helper functions to assign a component value */ // set(T&&), T = constructible template ::value > = 0> -inline void set(world_t *world, entity_t entity, T&& value, ecs_id_t id) { +inline void set(world_t *world, flecs::entity_t entity, T&& value, flecs::id_t id) { ecs_assert(_::cpp_type::size() != 0, ECS_INVALID_PARAMETER, NULL); T& dst = *static_cast(ecs_get_mut_id(world, entity, id)); @@ -16158,7 +17586,7 @@ inline void set(world_t *world, entity_t entity, T&& value, ecs_id_t id) { // set(const T&), T = constructible template ::value > = 0> -inline void set(world_t *world, entity_t entity, const T& value, ecs_id_t id) { +inline void set(world_t *world, flecs::entity_t entity, const T& value, flecs::id_t id) { ecs_assert(_::cpp_type::size() != 0, ECS_INVALID_PARAMETER, NULL); T& dst = *static_cast(ecs_get_mut_id(world, entity, id)); @@ -16169,7 +17597,7 @@ inline void set(world_t *world, entity_t entity, const T& value, ecs_id_t id) { // set(T&&), T = not constructible template ::value > = 0> -inline void set(world_t *world, entity_t entity, T&& value, ecs_id_t id) { +inline void set(world_t *world, flecs::entity_t entity, T&& value, flecs::id_t id) { ecs_assert(_::cpp_type::size() != 0, ECS_INVALID_PARAMETER, NULL); T& dst = *static_cast(ecs_get_mut_id(world, entity, id)); @@ -16181,7 +17609,7 @@ inline void set(world_t *world, entity_t entity, T&& value, ecs_id_t id) { // set(const T&), T = not constructible template ::value > = 0> -inline void set(world_t *world, id_t entity, const T& value, id_t id) { +inline void set(world_t *world, flecs::entity_t entity, const T& value, flecs::id_t id) { ecs_assert(_::cpp_type::size() != 0, ECS_INVALID_PARAMETER, NULL); T& dst = *static_cast(ecs_get_mut_id(world, entity, id)); @@ -16194,9 +17622,7 @@ inline void set(world_t *world, id_t entity, const T& value, id_t id) { template , Args...>::value || std::is_default_constructible>::value > = 0> -inline void emplace(world_t *world, id_t entity, Args&&... args) { - id_t id = _::cpp_type::id(world); - +inline void emplace(world_t *world, flecs::entity_t entity, flecs::id_t id, Args&&... args) { ecs_assert(_::cpp_type::size() != 0, ECS_INVALID_PARAMETER, NULL); T& dst = *static_cast(ecs_emplace_id(world, entity, id)); @@ -16205,11 +17631,6 @@ inline void emplace(world_t *world, id_t entity, Args&&... args) { ecs_modified_id(world, entity, id); } -// emplace for T(flecs::entity, Args...) -template , flecs::entity, Args...>::value > = 0> -inline void emplace(world_t *world, id_t entity, Args&&... args); - // set(T&&) template inline void set(world_t *world, entity_t entity, A&& value) { @@ -16224,8 +17645,30 @@ inline void set(world_t *world, entity_t entity, const A& value) { flecs::set(world, entity, value, id); } +/** Return id without generation. + * + * @see ecs_strip_generation + */ +inline flecs::id_t strip_generation(flecs::entity_t e) { + return ecs_strip_generation(e); +} + +/** Return entity generation. + */ +inline uint32_t get_generation(flecs::entity_t e) { + return ECS_GENERATION(e); +} + struct scoped_world; +/** + * @defgroup cpp_world World + * @brief World operations. + * + * \ingroup cpp_core + * @{ + */ + /** The world. * The world is the container of all ECS data and systems. If the world is * deleted, all data in the world will be deleted as well. @@ -16318,7 +17761,7 @@ struct world { /** Signal application should quit. * After calling this operation, the next call to progress() returns false. */ - void quit() { + void quit() const { ecs_quit(m_world); } @@ -16330,7 +17773,7 @@ struct world { /** Test if quit() has been called. */ - bool should_quit() { + bool should_quit() const { return ecs_should_quit(m_world); } @@ -16352,7 +17795,7 @@ struct world { * @param delta_time Time elapsed since the last frame. * @return The provided delta_time, or measured time if 0 was provided. */ - ecs_ftime_t frame_begin(float delta_time = 0) { + ecs_ftime_t frame_begin(float delta_time = 0) const { return ecs_frame_begin(m_world, delta_time); } @@ -16362,7 +17805,7 @@ struct world { * * This function should only be ran from the main thread. */ - void frame_end() { + void frame_end() const { ecs_frame_end(m_world); } @@ -16384,7 +17827,7 @@ struct world { * * @return Whether world is currently staged. */ - bool readonly_begin() { + bool readonly_begin() const { return ecs_readonly_begin(m_world); } @@ -16395,7 +17838,7 @@ struct world { * * This function should only be ran from the main thread. */ - void readonly_end() { + void readonly_end() const { ecs_readonly_end(m_world); } @@ -16405,7 +17848,7 @@ struct world { * * This operation is thread safe. */ - bool defer_begin() { + bool defer_begin() const { return ecs_defer_begin(m_world); } @@ -16414,13 +17857,13 @@ struct world { * * This operation is thread safe. */ - bool defer_end() { + bool defer_end() const { return ecs_defer_end(m_world); } /** Test whether deferring is enabled. */ - bool is_deferred() { + bool is_deferred() const { return ecs_is_deferred(m_world); } @@ -16489,7 +17932,7 @@ struct world { * * @param automerge Whether to enable or disable automerging. */ - void set_automerge(bool automerge) { + void set_automerge(bool automerge) const { ecs_set_automerge(m_world, automerge); } @@ -16501,7 +17944,7 @@ struct world { * * This operation may be called on an already merged stage or world. */ - void merge() { + void merge() const { ecs_merge(m_world); } @@ -16640,7 +18083,7 @@ struct world { /** Set search path. */ - flecs::entity_t* set_lookup_path(const flecs::entity_t *search_path) { + flecs::entity_t* set_lookup_path(const flecs::entity_t *search_path) const { return ecs_set_lookup_path(m_world, search_path); } @@ -16666,11 +18109,12 @@ struct world { /** Set singleton component inside a callback. */ template ::value > = 0 > - void set(const Func& func); + void set(const Func& func) const; template void emplace(Args&&... args) const { - flecs::emplace(m_world, _::cpp_type::id(m_world), + flecs::id_t component_id = _::cpp_type::id(m_world); + flecs::emplace(m_world, component_id, component_id, FLECS_FWD(args)...); } @@ -16684,6 +18128,11 @@ struct world { template void modified() const; + /** Get ref singleton component. + */ + template + ref get_ref() const; + /** Get singleton component. */ template @@ -16692,7 +18141,7 @@ struct world { /** Get singleton component inside a callback. */ template ::value > = 0 > - void get(const Func& func); + void get(const Func& func) const; /** Test if world has singleton component. */ @@ -16712,7 +18161,7 @@ struct world { /** Get singleton entity for type. */ template - flecs::entity singleton(); + flecs::entity singleton() const; /** Create alias for component. * @@ -16721,21 +18170,21 @@ struct world { * @return Entity representing the component. */ template - flecs::entity use(const char *alias = nullptr); + flecs::entity use(const char *alias = nullptr) const; /** Create alias for entity. * * @param name Name of the entity. * @param alias Alias for the entity. */ - flecs::entity use(const char *name, const char *alias = nullptr); + flecs::entity use(const char *name, const char *alias = nullptr) const; /** Create alias for entity. * * @param entity Entity for which to create the alias. * @param alias Alias for the entity. */ - void use(flecs::entity entity, const char *alias = nullptr); + void use(flecs::entity entity, const char *alias = nullptr) const; /** Count entities matching a component. * @@ -16843,10 +18292,10 @@ struct world { /** Use provided scope for operations ran on returned world. * Operations need to be ran in a single statement. */ - flecs::scoped_world scope(id_t parent); + flecs::scoped_world scope(id_t parent) const; template - flecs::scoped_world scope(); + flecs::scoped_world scope() const; /** Delete all entities with specified id. */ void delete_with(id_t the_id) const { @@ -16963,79 +18412,140 @@ struct world { #endif /* Run callback after completing frame */ - void run_post_frame(ecs_fini_action_t action, void *ctx) { + void run_post_frame(ecs_fini_action_t action, void *ctx) const { ecs_run_post_frame(m_world, action, ctx); } +/** + * @file addons/cpp/mixins/id/mixin.inl + * @brief Id world mixin. + */ /** Get id from a type. + * + * \memberof flecs::world */ template flecs::id id() const; /** Id factory. + * + * \memberof flecs::world */ template flecs::id id(Args&&... args) const; -/** Get pair id from relationship, object +/** Get pair id from relationship, object. + * + * \memberof flecs::world */ template flecs::id pair() const; -/** Get pair id from relationship, object +/** Get pair id from relationship, object. + * + * \memberof flecs::world */ template flecs::id pair(entity_t o) const; -/** Get pair id from relationship, object +/** Get pair id from relationship, object. + * + * \memberof flecs::world */ flecs::id pair(entity_t r, entity_t o) const; +/** + * @file addons/cpp/mixins/component/mixin.inl + * @brief Component mixin. + */ -/** Register a component. +/** Find or register component. + * + * \ingroup cpp_components + * \memberof flecs::world */ template flecs::component component(Args &&... args) const; -/** Register a component. +/** Find or register untyped component. + * Method available on flecs::world class. + * + * \ingroup cpp_components + * \memberof flecs::world */ template flecs::untyped_component component(Args &&... args) const; +/** + * @file addons/cpp/mixins/entity/mixin.inl + * @brief Entity world mixin. + */ /** Create an entity. + * + * \memberof flecs::world + * \ingroup cpp_entities */ template flecs::entity entity(Args &&... args) const; -/** Get id from an enum constant. +/** Convert enum constant to entity. + * + * \memberof flecs::world + * \ingroup cpp_entities */ template ::value > = 0> flecs::entity id(E value) const; -/** Get id from an enum constant. +/** Convert enum constant to entity. + * + * \memberof flecs::world + * \ingroup cpp_entities */ template ::value > = 0> flecs::entity entity(E value) const; /** Create a prefab. + * + * \memberof flecs::world + * \ingroup cpp_entities */ template flecs::entity prefab(Args &&... args) const; /** Create an entity that's associated with a type. + * + * \memberof flecs::world + * \ingroup cpp_entities */ template flecs::entity entity(const char *name = nullptr) const; /** Create a prefab that's associated with a type. + * + * \memberof flecs::world + * \ingroup cpp_entities */ template flecs::entity prefab(const char *name = nullptr) const; +/** + * @file addons/cpp/mixins/event/mixin.inl + * @brief Event world mixin. + */ + +/** + * @defgroup cpp_addons_event Events + * @brief API for emitting events. + * + * \ingroup cpp_addons + * @{ + */ /** Create a new event. + * + * \memberof flecs::world * * @param evt The event id. * @return Event builder. @@ -17043,6 +18553,8 @@ flecs::entity prefab(const char *name = nullptr) const; flecs::event_builder event(flecs::entity_t evt) const; /** Create a new event. + * + * \memberof flecs::world * * @tparam E The event type. * @return Event builder. @@ -17050,6 +18562,18 @@ flecs::event_builder event(flecs::entity_t evt) const; template flecs::event_builder_typed event() const; +/** @} */ + +/** + * @file addons/cpp/mixins/term/mixin.inl + * @brief Term world mixin. + */ + +/** + * \memberof flecs::world + * \ingroup cpp_core_filters + */ + /** Create a term. * */ @@ -17066,14 +18590,27 @@ flecs::term term() const; template flecs::term term() const; +/** @} */ + +/** + * @file addons/cpp/mixins/filter/mixin.inl + * @brief Filter world mixin. + */ + +/** + * \memberof flecs::world + * \ingroup cpp_core_filters + */ /** Create a filter. + * * @see ecs_filter_init */ template flecs::filter filter(Args &&... args) const; /** Create a filter builder. + * * @see ecs_filter_init */ template @@ -17083,6 +18620,7 @@ flecs::filter_builder filter_builder(Args &&... args) const; * The function parameter must match the following signature: * void(*)(T&, U&, ...) or * void(*)(flecs::entity, T&, U&, ...) + * */ template void each(Func&& func) const; @@ -17091,15 +18629,27 @@ void each(Func&& func) const; * The function parameter must match the following signature: * void(*)(T&) or * void(*)(flecs::entity, T&) + * */ template void each(Func&& func) const; -/** Iterate over all entities with provided (component) id. - */ +/** Iterate over all entities with provided (component) id. */ template void each(flecs::id_t term_id, Func&& func) const; +/** @} */ + +/** + * @file addons/cpp/mixins/observer/mixin.inl + * @brief Observer world mixin. + */ + +/** Observer builder. + * + * \memberof flecs::world + * \ingroup cpp_observers + */ /** Upcast entity to an observer. * The provided entity must be an observer. @@ -17118,6 +18668,17 @@ flecs::observer observer(flecs::entity e) const; template flecs::observer_builder observer(Args &&... args) const; +/** @} */ + +/** + * @file addons/cpp/mixins/query/mixin.inl + * @brief Query world mixin. + */ + +/** + * \memberof flecs::world + * \ingroup cpp_core_queries + */ /** Create a query. * @see ecs_query_init @@ -17137,17 +18698,38 @@ flecs::query query(flecs::query_base& parent, Args &&... args) const; template flecs::query_builder query_builder(Args &&... args) const; +/** @} */ -/** Convert enum constant to entity +/** + * @file addons/cpp/mixins/enum/mixin.inl + * @brief Enum world mixin. + */ + +/** Convert enum constant to entity. + * + * \memberof flecs::world + * \ingroup cpp_entities */ template ::value > = 0> flecs::entity to_entity(E constant) const; - # ifdef FLECS_MODULE +/** + * @file addons/cpp/mixins/module/mixin.inl + * @brief Module world mixin. + */ -/** Create a module. +/** + * \memberof flecs::world + * \ingroup cpp_addons_modules + * + * @{ + */ + +/** Define a module. + * This operation is not mandatory, but can be called inside the module ctor to + * obtain the entity associated with the module, or override the module name. * * @tparam Module module class. * @return Module entity. @@ -17163,8 +18745,19 @@ flecs::entity module(const char *name = nullptr) const; template flecs::entity import(); +/** @} */ + # endif # ifdef FLECS_PIPELINE +/** + * @file addons/cpp/mixins/pipeline/mixin.inl + * @brief Pipeline world mixin. + */ + +/** + * \memberof flecs::world + * \ingroup cpp_pipelines + */ /** Create a new pipeline. * @@ -17204,29 +18797,29 @@ bool progress(ecs_ftime_t delta_time = 0.0) const; /** Run pipeline. * @see ecs_run_pipeline */ -void run_pipeline(const flecs::entity pip, ecs_ftime_t delta_time = 0.0) const; +void run_pipeline(const flecs::entity_t pip, ecs_ftime_t delta_time = 0.0) const; -/** Set timescale +/** Set timescale. * @see ecs_set_time_scale */ void set_time_scale(ecs_ftime_t mul) const; -/** Get timescale +/** Get timescale. * @see ecs_get_time_scale */ ecs_ftime_t get_time_scale() const; -/** Get tick +/** Get tick. * @return Monotonically increasing frame count. */ int64_t get_tick() const; -/** Set target FPS +/** Set target FPS. * @see ecs_set_target_fps */ void set_target_fps(ecs_ftime_t target_fps) const; -/** Get target FPS +/** Get target FPS. * @return Configured frames per second. */ ecs_ftime_t get_target_fps() const; @@ -17246,16 +18839,38 @@ void set_threads(int32_t threads) const; */ int32_t get_threads() const; +/** @} */ + # endif # ifdef FLECS_SNAPSHOT +/** + * @file addons/cpp/mixins/snapshot/mixin.inl + * @brief Snapshot world mixin. + */ + +/** + * \memberof flecs::world + * \ingroup cpp_addons_snapshot + */ /** Create a snapshot. */ template flecs::snapshot snapshot(Args &&... args) const; +/** @} */ + # endif # ifdef FLECS_SYSTEM +/** + * @file addons/cpp/mixins/system/mixin.inl + * @brief System module world mixin. + */ + +/** + * \memberof flecs::world + * \ingroup cpp_addons_system +*/ /** Upcast entity to a system. * The provided entity must be a system. @@ -17274,8 +18889,35 @@ flecs::system system(flecs::entity e) const; template flecs::system_builder system(Args &&... args) const; +/** @} */ + +# endif +# ifdef FLECS_TIMER +/** + * @file addons/cpp/mixins/timer/mixin.inl + * @brief Timer module mixin. + */ + +/** + * \memberof flecs::world + * \ingroup cpp_addons_timer + */ + +/** Find or register a timer. */ +template +flecs::timer timer(Args &&... args) const; + # endif # ifdef FLECS_RULES +/** + * @file addons/cpp/mixins/rule/mixin.inl + * @brief Rule world mixin. + */ + +/** + * \memberof flecs::world + * \ingroup cpp_addons_rules + */ /** Create a rule. * @see ecs_rule_init @@ -17295,8 +18937,22 @@ flecs::rule rule(flecs::rule_base& parent, Args &&... args) const; template flecs::rule_builder rule_builder(Args &&... args) const; +/** @} */ + # endif # ifdef FLECS_PLECS +/** + * @file addons/cpp/mixins/plecs/mixin.inl + * @brief Plecs world mixin. + */ + +/** + * @defgroup cpp_addons_plecs Plecs + * @brief Data definition format for loading entity data. + * + * \ingroup cpp_addons + * @{ + */ /** Load plecs string. * @see ecs_plecs_from_str @@ -17312,8 +18968,21 @@ int plecs_from_file(const char *filename) const { return ecs_plecs_from_file(m_world, filename); } +/** @} */ + # endif # ifdef FLECS_META +/** + * @file addons/cpp/mixins/meta/world.inl + * @brief Meta world mixin. + */ + +/** + * \memberof flecs::world + * \ingroup cpp_addons_meta + * + * @{ + */ flecs::string to_expr(flecs::entity_t tid, const void* value) { char *expr = ecs_ptr_to_expr(m_world, tid, value); @@ -17336,14 +19005,30 @@ flecs::meta::cursor cursor(void *ptr) { return cursor(tid, ptr); } +/** @} */ + # endif # ifdef FLECS_JSON +/** + * @file addons/cpp/mixins/json/world.inl + * @brief JSON world mixin. + */ +/** Serialize untyped value to JSON. + * + * \memberof flecs::world + * \ingroup cpp_addons_json + */ flecs::string to_json(flecs::entity_t tid, const void* value) { char *json = ecs_ptr_to_json(m_world, tid, value); return flecs::string(json); } +/** Serialize value to JSON. + * + * \memberof flecs::world + * \ingroup cpp_addons_json + */ template flecs::string to_json(const T* value) { flecs::entity_t tid = _::cpp_type::id(m_world); @@ -17352,12 +19037,27 @@ flecs::string to_json(const T* value) { # endif # ifdef FLECS_APP +/** + * @file addons/cpp/mixins/app/mixin.inl + * @brief App world addon mixin. + */ +/** Return app builder. + * The app builder is a convenience wrapper around a loop that runs + * world::progress. An app allows for writing platform agnostic code, + * as it provides hooks to modules for overtaking the main loop which is + * required for frameworks like emscripten. + * + * \ingroup cpp_addons_app + * \memberof flecs::world + */ flecs::app_builder app() { m_owned = false; // App takes ownership of world return flecs::app_builder(m_world); } +/** @} */ + # endif public: @@ -17367,6 +19067,9 @@ public: bool m_owned; }; +/** Scoped world. + * Utility class used by the world::scope method to create entities in a scope. + */ struct scoped_world : world { scoped_world( flecs::world_t *w, @@ -17390,22 +19093,24 @@ struct scoped_world : world { flecs::entity_t m_prev_scope; }; -/** Return id without generation. - * - * @see ecs_strip_generation - */ -inline flecs::id_t strip_generation(flecs::entity_t e) { - return ecs_strip_generation(e); -} - -/** Return entity generation. - */ -inline uint32_t get_generation(flecs::entity_t e) { - return ECS_GENERATION(e); -} +/** @} */ } // namespace flecs +/** + * @file addons/cpp/iter.hpp + * @brief Wrapper classes for ecs_iter_t and component arrays. + */ + +#pragma once + +/** + * @defgroup cpp_iterator Iterators + * @brief Iterator operations. + * + * \ingroup cpp_core + * @{ + */ namespace flecs { @@ -17413,9 +19118,11 @@ namespace flecs /** Unsafe wrapper class around a column. * This class can be used when a system does not know the type of a column at * compile time. + * + * \ingroup cpp_iterator */ -struct unchecked_column { - unchecked_column(void* array, size_t size, size_t count, bool is_shared = false) +struct untyped_column { + untyped_column(void* array, size_t size, size_t count, bool is_shared = false) : m_array(array) , m_size(size) , m_count(count) @@ -17443,6 +19150,8 @@ protected: /** Wrapper class around a column. * * @tparam T component type of the column. + * + * \ingroup cpp_iterator */ template struct column { @@ -17552,7 +19261,9 @@ namespace flecs //////////////////////////////////////////////////////////////////////////////// -/** Class that enables iterating over table columns. +/** Class for iterating over query results. + * + * \ingroup cpp_iterator */ struct iter { private: @@ -17605,6 +19316,8 @@ public: flecs::table table() const; + flecs::table_range range() const; + /** Is current type a module or does it contain module contents? */ bool has_module() const { return ecs_table_has_module(m_iter->table); @@ -17617,6 +19330,14 @@ public: return m_iter->ctx; } + /** Access ctx. + * ctx contains the context pointer assigned to a system. + */ + template + T* ctx() { + return static_cast(m_iter->ctx); + } + /** Access param. * param contains the pointer passed to the param argument of system::run */ @@ -17677,7 +19398,7 @@ public: return ecs_field_size(m_iter, index); } - /** Obtain field source (0 if This) + /** Obtain field source (0 if This). * * @param index The field index. */ @@ -17713,14 +19434,13 @@ public: */ template , typename std::enable_if::value, void>::type* = nullptr> - flecs::column field(int32_t index) const { return get_field(index); } /** Get read/write access to field data. - * If the matched id for the specified field does not match with the provided type or if - * the field is readonly, the function will assert. + * If the matched id for the specified field does not match with the provided + * type or if the field is readonly, the function will assert. * * @tparam T Type of the field. * @param index The field index. @@ -17729,7 +19449,6 @@ public: template , typename std::enable_if< std::is_const::value == false, void>::type* = nullptr> - flecs::column field(int32_t index) const { ecs_assert(!ecs_field_is_readonly(m_iter, index), ECS_ACCESS_VIOLATION, NULL); @@ -17742,50 +19461,15 @@ public: * * @param index The field index. */ - flecs::unchecked_column field(int32_t index) const { + flecs::untyped_column field(int32_t index) const { return get_unchecked_field(index); } - /** Obtain the total number of tables the iterator will iterate over. - */ + /** Obtain the total number of tables the iterator will iterate over. */ int32_t table_count() const { return m_iter->table_count; } - /** Obtain untyped pointer to table column. - * - * @param column Id of table column (corresponds with location in table type). - * @return Pointer to table column. - */ - void* table_column(int32_t column) const { - return ecs_iter_column_w_size(m_iter, 0, column); - } - - /** Obtain typed pointer to table column. - * If the table does not contain a column with the specified type, the - * function will assert. - * - * @tparam T Type of the table column. - */ - template > - flecs::column table_column() const { - auto col = ecs_iter_find_column(m_iter, _::cpp_type::id()); - ecs_assert(col != -1, ECS_INVALID_PARAMETER, NULL); - - return flecs::column(static_cast(ecs_iter_column_w_size(m_iter, - sizeof(A), col)), static_cast(m_iter->count), false); - } - - template - flecs::column table_column(flecs::id_t obj) const { - auto col = ecs_iter_find_column(m_iter, - ecs_pair(_::cpp_type::id(), obj)); - ecs_assert(col != -1, ECS_INVALID_PARAMETER, NULL); - - return flecs::column(static_cast(ecs_iter_column_w_size(m_iter, - sizeof(T), col)), static_cast(m_iter->count), false); - } - /** Check if the current table has changed since the last iteration. * Can only be used when iterating queries and/or systems. */ bool changed() { @@ -17851,7 +19535,7 @@ private: count, is_shared); } - flecs::unchecked_column get_unchecked_field(int32_t index) const { + flecs::untyped_column get_unchecked_field(int32_t index) const { size_t count; size_t size = ecs_field_size(m_iter, index); bool is_shared = !ecs_field_is_self(m_iter, index); @@ -17867,7 +19551,7 @@ private: count = static_cast(m_iter->count); } - return flecs::unchecked_column( + return flecs::untyped_column( ecs_field_w_size(m_iter, 0, index), size, count, is_shared); } @@ -17878,58 +19562,20 @@ private: } // namespace flecs -namespace flecs -{ - -template -struct ref { - ref() - : m_world( nullptr ) - , m_ref() { } - - ref(world_t *world, entity_t entity) - : m_world( world ) - , m_ref() - { - auto comp_id = _::cpp_type::id(world); - - ecs_assert(_::cpp_type::size() != 0, - ECS_INVALID_PARAMETER, NULL); - - m_ref = ecs_ref_init_id(m_world, entity, comp_id); - } - - T* operator->() { - T* result = static_cast(ecs_ref_get_id( - m_world, &m_ref, _::cpp_type::id(m_world))); - - ecs_assert(result != NULL, ECS_INVALID_PARAMETER, NULL); - - return result; - } - - T* get() { - return static_cast(ecs_ref_get_id( - m_world, &m_ref, _::cpp_type::id(m_world))); - } - - flecs::entity entity() const; - -private: - world_t *m_world; - flecs::ref_t m_ref; -}; - -} +/** @} */ +/** + * @file addons/cpp/entity.hpp + * @brief Entity class. + * + * This class provides read/write access to entities. + */ #pragma once -#pragma once - -namespace flecs -{ - -/** Entity view class +/** + * @file addons/cpp/entity_view.hpp + * @brief Entity class with only readonly operations. + * * This class provides readonly access to entities. Using this class to store * entities in components ensures valid handles, as this class will always store * the actual world vs. a stage. The constructors of this class will never @@ -17937,6 +19583,22 @@ namespace flecs * * To obtain a mutable handle to the entity, use the "mut" function. */ + +#pragma once + +/** + * \ingroup cpp_entities + * @{ + */ + +namespace flecs +{ + +/** Entity view. + * Class with read operations for entities. Base for flecs::entity. + * + * \ingroup cpp_entities + */ struct entity_view : public id { entity_view() : flecs::id() { } @@ -18008,7 +19670,7 @@ struct entity_view : public id { return flecs::string(path); } - bool enabled() { + bool enabled() const { return !ecs_has_id(m_world, m_id, flecs::Disabled); } @@ -18024,6 +19686,15 @@ struct entity_view : public id { */ flecs::table table() const; + /** Get table range for the entity. + * Returns a range with the entity's row as offset and count set to 1. If + * the entity is not stored in a table, the function returns a range with + * count 0. + * + * @return Returns the entity's table range. + */ + flecs::table_range range() const; + /** Iterate (component) ids of an entity. * The function parameter must match the following signature: * void(*)(flecs::id id) @@ -18324,6 +19995,25 @@ struct entity_view : public id { template flecs::entity target_for(flecs::entity_t relationship) const; + /** Get depth for given relationship. + * + * @param rel The relationship. + * @return The depth. + */ + int32_t depth(flecs::entity_t rel) const { + return ecs_get_depth(m_world, m_id, rel); + } + + /** Get depth for given relationship. + * + * @tparam Rel The relationship. + * @return The depth. + */ + template + int32_t depth() const { + return this->depth(_::cpp_type::id(m_world)); + } + /** Get parent of entity. * Short for target(flecs::ChildOf). * @@ -18501,7 +20191,7 @@ struct entity_view : public id { * @param id The id to test. * @return True if enabled, false if not. */ - bool enabled(flecs::id_t id) { + bool enabled(flecs::id_t id) const { return ecs_is_enabled_id(m_world, m_id, id); } @@ -18511,7 +20201,7 @@ struct entity_view : public id { * @return True if enabled, false if not. */ template - bool enabled() { + bool enabled() const { return this->enabled(_::cpp_type::id(m_world)); } @@ -18521,7 +20211,7 @@ struct entity_view : public id { * @param second The second element of the pair. * @return True if enabled, false if not. */ - bool enabled(flecs::id_t first, flecs::id_t second) { + bool enabled(flecs::id_t first, flecs::id_t second) const { return this->enabled(ecs_pair(first, second)); } @@ -18532,7 +20222,7 @@ struct entity_view : public id { * @return True if enabled, false if not. */ template - bool enabled(flecs::id_t second) { + bool enabled(flecs::id_t second) const { return this->enabled(_::cpp_type::id(m_world), second); } @@ -18543,7 +20233,7 @@ struct entity_view : public id { * @return True if enabled, false if not. */ template - bool enabled() { + bool enabled() const { return this->enabled(_::cpp_type::id(m_world)); } @@ -18601,7 +20291,16 @@ struct entity_view : public id { flecs::entity mut(const flecs::entity_view& e) const; # ifdef FLECS_JSON +/** + * @file addons/cpp/mixins/json/entity_view.inl + * @brief JSON entity mixin. + */ +/** Serialize entity to JSON. + * + * \memberof flecs::entity_view + * \ingroup cpp_addons_json + */ flecs::string to_json(const flecs::entity_to_json_desc_t *desc = nullptr) { char *json = ecs_entity_to_json(m_world, m_id, desc); return flecs::string(json); @@ -18609,6 +20308,10 @@ flecs::string to_json(const flecs::entity_to_json_desc_t *desc = nullptr) { # endif # ifdef FLECS_DOC +/** + * @file addons/cpp/mixins/doc/entity_view.inl + * @brief Doc entity view mixin. + */ const char* doc_name() { return ecs_doc_get_name(m_world, m_id); @@ -18632,8 +20335,15 @@ const char* doc_color() { # endif +/** + * @file addons/cpp/mixins/enum/entity_view.inl + * @brief Enum entity view mixin. + */ -/** Convert entity to enum cosnstant +/** Convert entity to enum constant. + * + * \memberof flecs::entity_view + * \ingroup cpp_entities */ template E to_constant() const; @@ -18646,11 +20356,21 @@ private: } +/** @} */ + +/** + * @file addons/cpp/mixins/entity/builder.hpp + * @brief Entity builder. + */ + #pragma once namespace flecs { +/** Entity builder. + * \ingroup cpp_entities + */ template struct entity_builder : entity_view { @@ -19040,11 +20760,22 @@ struct entity_builder : entity_view { * @tparam T The component to set and for which to add the OVERRIDE flag */ template - Self& set_override(T val) { + Self& set_override(const T& val) { this->override(); return this->set(val); } + /** Set component, mark component for auto-overriding. + * @see override(flecs::id_t id) + * + * @tparam T The component to set and for which to add the OVERRIDE flag + */ + template + Self& set_override(T&& val) { + this->override(); + return this->set(FLECS_FWD(val)); + } + /** Set pair, mark component for auto-overriding. * @see override(flecs::id_t id) * @@ -19052,11 +20783,23 @@ struct entity_builder : entity_view { * @param second The second element of the pair. */ template - Self& set_override(flecs::entity_t second, First val) { + Self& set_override(flecs::entity_t second, const First& val) { this->override(second); return this->set(second, val); } + /** Set pair, mark component for auto-overriding. + * @see override(flecs::id_t id) + * + * @tparam First The first element of the pair. + * @param second The second element of the pair. + */ + template + Self& set_override(flecs::entity_t second, First&& val) { + this->override(second); + return this->set(second, FLECS_FWD(val)); + } + /** Set component, mark component for auto-overriding. * @see override(flecs::id_t id) * @@ -19065,22 +20808,55 @@ struct entity_builder : entity_view { */ template , typename A = actual_type_t

, if_not_t< flecs::is_pair::value> = 0> - Self& set_override(A val) { + Self& set_override(const A& val) { this->override(); return this->set(val); } + /** Set component, mark component for auto-overriding. + * @see override(flecs::id_t id) + * + * @tparam First The first element of the pair. + * @tparam Second The second element of the pair. + */ + template , + typename A = actual_type_t

, if_not_t< flecs::is_pair::value> = 0> + Self& set_override(A&& val) { + this->override(); + return this->set(FLECS_FWD(val)); + } + /** Emplace component, mark component for auto-overriding. * @see override(flecs::id_t id) * - * @tparam T The component to set and for which to add the OVERRIDE flag + * @tparam T The component to emplace and override. */ template Self& emplace_override(Args&&... args) { this->override(); flecs::emplace(this->m_world, this->m_id, - FLECS_FWD(args)...); + _::cpp_type::id(this->m_world), FLECS_FWD(args)...); + + return to_base(); + } + + /** Emplace pair, mark pair for auto-overriding. + * @see override(flecs::id_t id) + * + * @tparam First The first element of the pair to emplace and override. + * @tparam Second The second element of the pair to emplace and override. + */ + template , + typename A = actual_type_t

, if_not_t< flecs::is_pair::value> = 0, + typename ... Args> + Self& emplace_override(Args&&... args) { + this->override(); + + flecs::emplace(this->m_world, this->m_id, + ecs_pair(_::cpp_type::id(this->m_world), + _::cpp_type::id(this->m_world)), + FLECS_FWD(args)...); return to_base(); } @@ -19280,6 +21056,21 @@ struct entity_builder : entity_view { return to_base(); } + /** Set a pair for an entity. + * This operation sets the pair value, and uses First as type. If the + * entity did not yet have the pair, it will be added. + * + * @tparam First The first element of the pair. + * @tparam Second The second element of the pair + * @param value The value to set. + */ + template , + typename A = actual_type_t

, if_not_t< flecs::is_pair::value> = 0> + Self& set(A&& value) { + flecs::set

(this->m_world, this->m_id, FLECS_FWD(value)); + return to_base(); + } + /** Set a pair for an entity. * This operation sets the pair value, and uses First as type. If the * entity did not yet have the pair, it will be added. @@ -19311,6 +21102,22 @@ struct entity_builder : entity_view { return to_base(); } + /** Set a pair for an entity. + * This operation sets the pair value, and uses First as type. If the + * entity did not yet have the pair, it will be added. + * + * @tparam First The first element of the pair. + * @param second The second element of the pair. + * @param value The value to set. + */ + template ::value > = 0> + Self& set(Second second, First&& value) { + auto first = _::cpp_type::id(this->m_world); + flecs::set(this->m_world, this->m_id, FLECS_FWD(value), + ecs_pair(first, second)); + return to_base(); + } + /** Set a pair for an entity. * This operation sets the pair value, and uses First as type. If the * entity did not yet have the pair, it will be added. @@ -19342,6 +21149,22 @@ struct entity_builder : entity_view { return to_base(); } + /** Set a pair for an entity. + * This operation sets the pair value, and uses Second as type. If the + * entity did not yet have the pair, it will be added. + * + * @tparam Second The second element of the pair + * @param first The first element of the pair. + * @param value The value to set. + */ + template + Self& set_second(entity_t first, Second&& value) { + auto second = _::cpp_type::id(this->m_world); + flecs::set(this->m_world, this->m_id, FLECS_FWD(value), + ecs_pair(first, second)); + return to_base(); + } + template Self& set_second(const Second& value) { flecs::set>(this->m_world, this->m_id, value); @@ -19384,9 +21207,35 @@ struct entity_builder : entity_view { * @tparam T the component to emplace * @param args The arguments to pass to the constructor of T */ - template + template> Self& emplace(Args&&... args) { - flecs::emplace(this->m_world, this->m_id, + flecs::emplace(this->m_world, this->m_id, + _::cpp_type::id(this->m_world), FLECS_FWD(args)...); + return to_base(); + } + + template , + typename A = actual_type_t

, if_not_t< flecs::is_pair::value> = 0> + Self& emplace(Args&&... args) { + flecs::emplace(this->m_world, this->m_id, + ecs_pair(_::cpp_type::id(this->m_world), + _::cpp_type::id(this->m_world)), + FLECS_FWD(args)...); + return to_base(); + } + + template + Self& emplace_first(flecs::entity_t second, Args&&... args) { + flecs::emplace(this->m_world, this->m_id, + ecs_pair(_::cpp_type::id(this->m_world), second), + FLECS_FWD(args)...); + return to_base(); + } + + template + Self& emplace_second(flecs::entity_t first, Args&&... args) { + flecs::emplace(this->m_world, this->m_id, + ecs_pair(first, _::cpp_type::id(this->m_world)), FLECS_FWD(args)...); return to_base(); } @@ -19403,7 +21252,7 @@ struct entity_builder : entity_view { return to_base(); } - /** Entities created in function will have (First, this) + /** Entities created in function will have (First, this). * This operation is thread safe. * * @tparam First The first element of the pair @@ -19415,7 +21264,7 @@ struct entity_builder : entity_view { return to_base(); } - /** Entities created in function will have (first, this) + /** Entities created in function will have (first, this). * * @param first The first element of the pair. * @param func The function to call. @@ -19446,27 +21295,61 @@ struct entity_builder : entity_view { } # ifdef FLECS_DOC +/** + * @file addons/cpp/mixins/doc/entity_builder.inl + * @brief Doc entity builder mixin. + */ +/** Set doc name. + * This adds (flecs.doc.Description, flecs.Name) to the entity. + * + * \memberof flecs::entity_builder + * \ingroup cpp_addons_doc + */ Self& set_doc_name(const char *name) { ecs_doc_set_name(m_world, m_id, name); return to_base(); } +/** Set doc brief. + * This adds (flecs.doc.Description, flecs.doc.Brief) to the entity. + * + * \memberof flecs::entity_builder + * \ingroup cpp_addons_doc + */ Self& set_doc_brief(const char *brief) { ecs_doc_set_brief(m_world, m_id, brief); return to_base(); } +/** Set doc detailed description. + * This adds (flecs.doc.Description, flecs.doc.Detail) to the entity. + * + * \memberof flecs::entity_builder + * \ingroup cpp_addons_doc + */ Self& set_doc_detail(const char *detail) { ecs_doc_set_detail(m_world, m_id, detail); return to_base(); } +/** Set doc link. + * This adds (flecs.doc.Description, flecs.doc.Link) to the entity. + * + * \memberof flecs::entity_builder + * \ingroup cpp_addons_doc + */ Self& set_doc_link(const char *link) { ecs_doc_set_link(m_world, m_id, link); return to_base(); } +/** Set doc color. + * This adds (flecs.doc.Description, flecs.doc.Color) to the entity. + * + * \memberof flecs::entity_builder + * \ingroup cpp_addons_doc + */ Self& set_doc_color(const char *link) { ecs_doc_set_color(m_world, m_id, link); return to_base(); @@ -19475,6 +21358,17 @@ Self& set_doc_color(const char *link) { # endif # ifdef FLECS_META +/** + * @file addons/cpp/mixins/meta/entity_builder.inl + * @brief Meta entity builder mixin. + */ + +/** + * \memberof flecs::entity_view + * \ingroup cpp_addons_meta + * + * @{ + */ /** Make entity a unit */ Self& unit( @@ -19552,6 +21446,8 @@ Self& quantity() { return to_base(); } +/** @} */ + # endif protected: @@ -19563,11 +21459,22 @@ protected: } +/** + * @defgroup cpp_entities Entities + * @brief Entity operations. + * + * \ingroup cpp_core + * @{ + */ + namespace flecs { -/** Entity class - * This class provides access to entities. */ +/** Entity. + * Class with read/write operations for entities. + * + * \ingroup cpp_entities +*/ struct entity : entity_builder { entity() : entity_builder() { } @@ -19656,9 +21563,12 @@ struct entity : entity_builder * @tparam First The first part of the pair. * @tparam Second the second part of the pair. */ - template - First* get_mut() const { - return this->get_mut(_::cpp_type::id(m_world)); + template , + typename A = actual_type_t

, if_not_t< flecs::is_pair::value> = 0> + A* get_mut() const { + return static_cast(ecs_get_mut_id(m_world, m_id, ecs_pair( + _::cpp_type::id(m_world), + _::cpp_type::id(m_world)))); } /** Get mutable pointer for a pair. @@ -19764,10 +21674,35 @@ struct entity : entity_builder ref get_ref() const { // Ensure component is registered _::cpp_type::id(m_world); - ecs_assert(_::cpp_type::size() != 0, ECS_INVALID_PARAMETER, NULL); return ref(m_world, m_id); } + template , + typename A = actual_type_t

> + ref get_ref() const { + // Ensure component is registered + _::cpp_type::id(m_world); + return ref(m_world, m_id, + ecs_pair(_::cpp_type::id(m_world), + _::cpp_type::id(m_world))); + } + + template + ref get_ref(flecs::entity_t second) const { + // Ensure component is registered + _::cpp_type::id(m_world); + return ref(m_world, m_id, + ecs_pair(_::cpp_type::id(m_world), second)); + } + + template + ref get_ref_second(flecs::entity_t first) const { + // Ensure component is registered + _::cpp_type::id(m_world); + return ref(m_world, m_id, + ecs_pair(first, _::cpp_type::id(m_world))); + } + /** Clear an entity. * This operation removes all components from an entity without recycling * the entity id. @@ -19805,10 +21740,14 @@ struct entity : entity_builder } // namespace flecs +/** @} */ -//////////////////////////////////////////////////////////////////////////////// -//// Utility class to invoke a system each -//////////////////////////////////////////////////////////////////////////////// +/** + * @file addons/cpp/invoker.hpp + * @brief Utilities for invoking each/iter callbacks. + */ + +#pragma once namespace flecs { @@ -20468,6 +22407,10 @@ struct entity_with_invoker::value > > } // namespace flecs +/** + * @file addons/cpp/utils/iterable.hpp + * @brief Base class for iterable objects, like queries. + */ namespace flecs { @@ -20574,17 +22517,17 @@ struct iterable { */ worker_iterable worker(int32_t index, int32_t count); - /** Return number of entities matched by iteratble. */ + /** Return number of entities matched by iterable. */ int32_t count() const { return this->iter().count(); } - /** Return number of entities matched by iteratble. */ + /** Return whether iterable has any matches. */ bool is_true() const { return this->iter().is_true(); } - /** Return number of entities matched by iteratble. */ + /** Return first entity matched by iterable. */ flecs::entity first() const { return this->iter().first(); } @@ -20623,6 +22566,15 @@ struct iter_iterable final : iterable { } # ifdef FLECS_RULES +/** + * @file addons/cpp/mixins/rule/iterable.inl + * @brief Rule iterable mixin. + */ + +/** + * \memberof flecs::iter + * \ingroup cpp_addons_rules + */ iter_iterable& set_var(int var_id, flecs::entity_t value) { ecs_assert(m_it.next == ecs_rule_next, ECS_INVALID_OPERATION, NULL); @@ -20640,9 +22592,20 @@ iter_iterable& set_var(const char *name, flecs::entity_t value) { return *this; } +/** @} */ + # endif # ifdef FLECS_JSON +/** + * @file addons/cpp/mixins/json/iterable.inl + * @brief JSON iterable mixin. + */ +/** Serialize iterator result to JSON. + * + * \memberof flecs::iter + * \ingroup cpp_addons_json + */ flecs::string to_json(flecs::iter_to_json_desc_t *desc = nullptr) { char *json = ecs_iter_to_json(m_it.real_world, &m_it, desc); return flecs::string(json); @@ -20796,8 +22759,23 @@ worker_iterable iterable::worker( } +/** + * @file addons/cpp/component.hpp + * @brief Registering/obtaining info from components. + */ + #pragma once + #include +#include + +/** + * @defgroup cpp_components Components + * @brief Registering and working with components. + * + * \ingroup cpp_core + * @{ + */ namespace flecs { @@ -21124,10 +23102,26 @@ struct cpp_type::value >> } // namespace _ +/** Untyped component class. + * Generic base class for flecs::component. + * + * \ingroup cpp_components + */ struct untyped_component : entity { using entity::entity; # ifdef FLECS_META +/** + * @file addons/cpp/mixins/meta/component.inl + * @brief Meta component mixin. + */ + +/** + * \memberof flecs::component + * \ingroup cpp_addons_meta + * + * @{ + */ /** Add member. */ untyped_component& member(flecs::entity_t type_id, const char *name, int32_t count = 0, size_t offset = 0) { @@ -21224,9 +23218,15 @@ untyped_component& bit(const char *name, uint32_t value) { return *this; } +/** @} */ # endif }; +/** Component class. + * Class used to register components and component metadata. + * + * \ingroup cpp_components + */ template struct component : untyped_component { /** Register a component. @@ -21263,7 +23263,7 @@ struct component : untyped_component { * this operation does nothing besides returning the existing id */ id = _::cpp_type::id_explicit(world, name, allow_tag, id); - ecs_cpp_component_validate(world, id, n, + ecs_cpp_component_validate(world, id, n, _::symbol_name(), _::cpp_type::size(), _::cpp_type::alignment(), implicit_name); @@ -21271,7 +23271,20 @@ struct component : untyped_component { /* If component is registered from an existing scope, ignore the * namespace in the name of the component. */ if (implicit_name && (ecs_get_scope(world) != 0)) { - const char *last_elem = strrchr(n, ':'); + /* If the type is a template type, make sure to ignore ':' + * inside the template parameter list. */ + const char *start = strchr(n, '<'), *last_elem = NULL; + if (start) { + const char *ptr = start; + while (ptr[0] && (ptr[0] != ':') && (ptr > n)) { + ptr --; + } + if (ptr[0] == ':') { + last_elem = ptr; + } + } else { + last_elem = strrchr(n, ':'); + } if (last_elem) { name = last_elem + 1; } @@ -21381,7 +23394,7 @@ flecs::entity_t type_id() { } } -/** Reset static component variables. +/** Reset static component ids. * When components are registered their component ids are stored in a static * type specific variable. This stored id is passed into component registration * functions to ensure consistent ids across worlds. @@ -21400,6 +23413,8 @@ flecs::entity_t type_id() { * Also note that this operation does not actually change the static component * variables. It only ensures that the next time a component id is requested, a * new id will be generated. + * + * \ingroup cpp_components */ inline void reset() { ecs_cpp_reset_count_inc(); @@ -21407,10 +23422,28 @@ inline void reset() { } +/** @} */ + +/** + * @file addons/cpp/type.hpp + * @brief Utility functions for id vector. + */ + #pragma once namespace flecs { +/** + * @defgroup cpp_types Types + * @brief Type operations. + * + * \ingroup cpp_core + * @{ + */ + +/** Type class. + * A type is a vector of component ids which can be requested from entities or tables. + */ struct type { type() : m_world(nullptr), m_type(nullptr) { } @@ -21418,10 +23451,12 @@ struct type { : m_world(world) , m_type(t) { } + /** Convert type to comma-separated string */ flecs::string str() const { return flecs::string(ecs_type_str(m_world, m_type)); } + /** Return number of ids in type */ int32_t count() const { if (!m_type) { return 0; @@ -21429,6 +23464,7 @@ struct type { return m_type->count; } + /** Return pointer to array. */ flecs::id_t* array() const { if (!m_type) { return nullptr; @@ -21436,6 +23472,7 @@ struct type { return m_type->array; } + /** Get id at specified index in type */ flecs::id get(int32_t index) const { ecs_assert(m_type != NULL, ECS_INVALID_PARAMETER, NULL); ecs_assert(m_type->count > index, ECS_OUT_OF_RANGE, NULL); @@ -21445,7 +23482,7 @@ struct type { return flecs::id(m_world, m_type->array[index]); } - /* Implicit conversion to type_t */ + /** Implicit conversion to type_t */ operator const type_t*() const { return m_type; } @@ -21454,12 +23491,27 @@ private: const type_t *m_type; }; +/** #} */ + } +/** + * @file addons/cpp/table.hpp + * @brief Direct access to table data. + */ + #pragma once namespace flecs { +/** + * @defgroup cpp_tables Tables + * @brief Table operations. + * + * \ingroup cpp_core + * @{ + */ + struct table { table() : m_world(nullptr), m_table(nullptr) { } @@ -21467,50 +23519,272 @@ struct table { : m_world(world) , m_table(t) { } + virtual ~table() { } + + /** Convert table type to string. */ flecs::string str() const { return flecs::string(ecs_table_str(m_world, m_table)); } + /** Get table type. */ flecs::type type() const { return flecs::type(m_world, ecs_table_get_type(m_table)); } - bool has(flecs::id_t id) const { - return ecs_search(m_world, m_table, id, 0) != -1; + /** Get table count. */ + int32_t count() const { + return ecs_table_count(m_table); } + /** Find index for (component) id. + * + * @param id The (component) id. + * @return The index of the id in the table type, -1 if not found/ + */ + int32_t search(flecs::id_t id) const { + return ecs_search(m_world, m_table, id, 0); + } + + /** Find index for type. + * + * @tparam T The type. + * @return True if the table has the type, false if not. + */ + template + int32_t search() const { + return search(_::cpp_type::id(m_world)); + } + + /** Find index for pair. + * @param first First element of pair. + * @param second Second element of pair. + * @return True if the table has the pair, false if not. + */ + int32_t search(flecs::entity_t first, flecs::entity_t second) const { + return search(ecs_pair(first, second)); + } + + /** Find index for pair. + * @tparam First First element of pair. + * @param second Second element of pair. + * @return True if the table has the pair, false if not. + */ + template + int32_t search(flecs::entity_t second) const { + return search(_::cpp_type::id(m_world), second); + } + + /** Find index for pair. + * @tparam First First element of pair. + * @tparam Second Second element of pair. + * @return True if the table has the pair, false if not. + */ + template + int32_t search() const { + return search(_::cpp_type::id(m_world)); + } + + /** Test if table has (component) id. + * + * @param id The (component) id. + * @return True if the table has the id, false if not. + */ + bool has(flecs::id_t id) const { + return search(id) != -1; + } + + /** Test if table has the type. + * + * @tparam T The type. + * @return True if the table has the type, false if not. + */ template bool has() const { - return has(_::cpp_type::id(m_world)); + return search() != -1; } - bool has(flecs::entity_t r, flecs::entity_t o) const { - return has(ecs_pair(r, o)); + /** Test if table has the pair. + * + * @param first First element of pair. + * @param second Second element of pair. + * @return True if the table has the pair, false if not. + */ + bool has(flecs::entity_t first, flecs::entity_t second) const { + return search(first, second) != -1; } + /** Test if table has the pair. + * + * @tparam First First element of pair. + * @param second Second element of pair. + * @return True if the table has the pair, false if not. + */ template - bool has(flecs::entity_t o) const { - return has(_::cpp_type::id(m_world), o); + bool has(flecs::entity_t second) const { + return search(second) != -1; } + /** Test if table has the pair. + * + * @tparam First First element of pair. + * @tparam Second Second element of pair. + * @return True if the table has the pair, false if not. + */ template bool has() const { - return has(_::cpp_type::id(m_world)); + return search() != -1; + } + + /** Get pointer to component array by column index. + * + * @param index The column index. + * @return Pointer to the column, NULL if not a component. + */ + virtual void* get_by_index(int32_t index) const { + return ecs_table_get_column(m_table, index, 0); + } + + /** Get pointer to component array by component. + * + * @param id The component id. + * @return Pointer to the column, NULL if not found. + */ + void* get(flecs::id_t id) const { + int32_t index = search(id); + if (index == -1) { + return NULL; + } + return get_by_index(index); + } + + /** Get pointer to component array by pair. + * + * @param first The first element of the pair. + * @param second The second element of the pair. + * @return Pointer to the column, NULL if not found. + */ + void* get(flecs::entity_t first, flecs::entity_t second) const { + return get(ecs_pair(first, second)); + } + + /** Get pointer to component array by component. + * + * @tparam T The component. + * @return Pointer to the column, NULL if not found. + */ + template ::value > = 0> + T* get() const { + return static_cast(get(_::cpp_type::id(m_world))); + } + + /** Get pointer to component array by component. + * + * @tparam T The component. + * @return Pointer to the column, NULL if not found. + */ + template , + if_t< flecs::is_pair::value > = 0> + A* get() const { + return static_cast(get(_::cpp_type::id(m_world))); + } + + /** Get pointer to component array by pair. + * + * @tparam First The first element of the pair. + * @param second The second element of the pair. + * @return Pointer to the column, NULL if not found. + */ + template + First* get(flecs::entity_t second) const { + return static_cast(get(_::cpp_type::id(m_world), second)); + } + + /** Get pointer to component array by pair. + * + * @tparam First The first element of the pair. + * @tparam Second The second element of the pair. + * @return Pointer to the column, NULL if not found. + */ + template , + typename A = actual_type_t

, if_not_t< flecs::is_pair::value> = 0> + A* get() const { + return static_cast(get(_::cpp_type::id(m_world))); + } + + /** Get depth for given relationship. + * + * @param rel The relationship. + * @return The depth. + */ + int32_t depth(flecs::entity_t rel) { + return ecs_table_get_depth(m_world, m_table, rel); + } + + /** Get depth for given relationship. + * + * @tparam Rel The relationship. + * @return The depth. + */ + template + int32_t depth() { + return depth(_::cpp_type::id(m_world)); } /* Implicit conversion to table_t */ operator table_t*() const { return m_table; } -private: + +protected: world_t *m_world; table_t *m_table; }; +struct table_range : table { + table_range() + : table() + , m_offset(0) + , m_count(0) { } + + table_range(world_t *world, table_t *t, int32_t offset, int32_t count) + : table(world, t) + , m_offset(offset) + , m_count(count) { } + + int32_t offset() const { + return m_offset; + } + + int32_t count() const { + return m_count; + } + +private: + /** Get pointer to component array by column index. + * + * @param index The column index. + * @return Pointer to the column, NULL if not a component. + */ + void* get_by_index(int32_t index) const override { + return ecs_table_get_column(m_table, index, m_offset); + } + + int32_t m_offset = 0; + int32_t m_count = 0; +}; + +/** @} */ + } // Mixin implementations +/** + * @file addons/cpp/mixins/id/impl.hpp + * @brief Id class implementation. + */ + +#pragma once namespace flecs { @@ -21609,6 +23883,11 @@ inline flecs::id world::pair(entity_t r, entity_t o) const { } +/** + * @file addons/cpp/mixins/entity/impl.hpp + * @brief Entity implementation. + */ + #pragma once namespace flecs { @@ -21710,6 +23989,15 @@ inline flecs::table entity_view::table() const { return flecs::table(m_world, ecs_get_table(m_world, m_id)); } +inline flecs::table_range entity_view::range() const { + ecs_record_t *r = ecs_record_find(m_world, m_id); + if (r) { + return flecs::table_range(m_world, r->table, + ECS_RECORD_TO_ROW(r->row), 1); + } + return flecs::table_range(); +} + template inline void entity_view::each(const Func& func) const { const ecs_type_t *type = ecs_get_type(m_world, m_id); @@ -21806,6 +24094,12 @@ inline flecs::entity world::id(E value) const { return flecs::entity(m_world, constant); } +template ::value >> +inline flecs::entity world::entity(E value) const { + flecs::entity_t constant = enum_type(m_world).entity(value); + return flecs::entity(m_world, constant); +} + template inline flecs::entity world::entity(const char *name) const { return flecs::entity(m_world, @@ -21828,11 +24122,15 @@ inline flecs::entity world::prefab(const char *name) const { } +/** + * @file addons/cpp/mixins/component/impl.hpp + * @brief Component mixin implementation + */ + #pragma once namespace flecs { -// Component mixin implementation template inline flecs::component world::component(Args &&... args) const { return flecs::component(m_world, FLECS_FWD(args)...); @@ -21845,10 +24143,25 @@ inline flecs::untyped_component world::component(Args &&... args) const { } // namespace flecs -#pragma once +/** + * @file addons/cpp/mixins/term/impl.hpp + * @brief Term implementation. + */ #pragma once +/** + * @file addons/cpp/mixins/term/builder_i.hpp + * @brief Term builder interface. + */ + +#pragma once + +/** + * @file addons/cpp/utils/signature.hpp + * @brief Compile time utilities for deriving query attributes from param pack. + */ + #pragma once #include @@ -21933,6 +24246,8 @@ namespace flecs * A term identifier describes a single identifier in a term. Identifier * descriptions can reference entities by id, name or by variable, which means * the entity will be resolved when the term is evaluated. + * + * \ingroup cpp_core_filters */ template struct term_id_builder_i { @@ -22049,7 +24364,11 @@ private: } }; -/** Term builder. A term is a single element of a query expression. */ +/** Term builder interface. + * A term is a single element of a query expression. + * + * \ingroup cpp_addons_filter + */ template struct term_builder_i : term_id_builder_i { term_builder_i() : m_term(nullptr) { } @@ -22193,7 +24512,7 @@ struct term_builder_i : term_id_builder_i { this->assert_term(); m_term->inout = static_cast(inout); if (m_term->oper != EcsNot) { - this->entity(0); + this->src().entity(0); } return *this; } @@ -22205,14 +24524,14 @@ struct term_builder_i : term_id_builder_i { return this->inout_stage(flecs::Out); } - /** Short for inout_stage(flecs::In) + /** Short for inout_stage(flecs::In). * Use when system uses get. */ Base& read() { return this->inout_stage(flecs::In); } - /** Short for inout_stage(flecs::InOut) + /** Short for inout_stage(flecs::InOut). * Use when system uses get_mut. */ Base& read_write() { @@ -22333,8 +24652,16 @@ private: namespace flecs { -// Class that describes a term +/** Class that describes a term. + * + * \ingroup cpp_core_filters + */ struct term final : term_builder_i { + term() + : term_builder_i(&value) + , value({}) + , m_world(nullptr) { value.move = true; } + term(flecs::world_t *world_ptr) : term_builder_i(&value) , value({}) @@ -22495,10 +24822,27 @@ inline flecs::term world::term() const { } -#pragma once +/** + * @file addons/cpp/mixins/filter/impl.hpp + * @brief Filter implementation. + */ #pragma once +/** + * @file addons/cpp/mixins/filter/builder.hpp + * @brief Filter builder. + */ + +#pragma once + +/** + * @file addons/cpp/utils/builder.hpp + * @brief Builder base class. + * + * Generic functionality for builder classes. + */ + #pragma once namespace flecs { @@ -22549,13 +24893,21 @@ protected: } // namespace _ } // namespace flecs +/** + * @file addons/cpp/mixins/filter/builder_i.hpp + * @brief Filter builder interface. + */ + #pragma once namespace flecs { -// Filter builder interface +/** Filter builder interface. + * + * \ingroup cpp_filters + */ template struct filter_builder_i : term_builder_i { filter_builder_i(ecs_filter_desc_t *desc, int32_t term_index = 0) @@ -22578,6 +24930,82 @@ struct filter_builder_i : term_builder_i { return *this; } + /* With/without shorthand notation. */ + + template + Base& with(Args&&... args) { + return this->term(FLECS_FWD(args)...); + } + + template + Base& with(Args&&... args) { + return this->term(FLECS_FWD(args)...); + } + + template + Base& with() { + return this->term(); + } + + template + Base& without(Args&&... args) { + return this->term(FLECS_FWD(args)...).not_(); + } + + template + Base& without(Args&&... args) { + return this->term(FLECS_FWD(args)...).not_(); + } + + template + Base& without() { + return this->term().not_(); + } + + /* Write/read shorthand notation */ + + Base& write() { + term_builder_i::write(); + return *this; + } + + template + Base& write(Args&&... args) { + return this->term(FLECS_FWD(args)...).write(); + } + + template + Base& write(Args&&... args) { + return this->term(FLECS_FWD(args)...).write(); + } + + template + Base& write() { + return this->term().write(); + } + + Base& read() { + term_builder_i::read(); + return *this; + } + + template + Base& read(Args&&... args) { + return this->term(FLECS_FWD(args)...).read(); + } + + template + Base& read(Args&&... args) { + return this->term(FLECS_FWD(args)...).read(); + } + + template + Base& read() { + return this->term().read(); + } + + /* Term notation for more complex query features */ + Base& term() { if (this->m_term) { ecs_check(ecs_term_is_initialized(this->m_term), @@ -22641,17 +25069,40 @@ struct filter_builder_i : term_builder_i { return *this; } + Base& term(const char *name) { + this->term(); + *this->m_term = flecs::term().first(name).move(); + return *this; + } + + Base& term(const char *first, const char *second) { + this->term(); + *this->m_term = flecs::term().first(first).second(second).move(); + return *this; + } + Base& term(entity_t r, entity_t o) { this->term(); *this->m_term = flecs::term(r, o).move(); return *this; } + Base& term(entity_t r, const char *o) { + this->term(); + *this->m_term = flecs::term(r).second(o).move(); + return *this; + } + template Base& term(id_t o) { return this->term(_::cpp_type::id(this->world_v()), o); } + template + Base& term(const char *second) { + return this->term(_::cpp_type::id(this->world_v())).second(second); + } + template Base& term() { return this->term(_::cpp_type::id(this->world_v())); @@ -22700,12 +25151,23 @@ namespace _ { filter_builder_i, Components ...>; } +/** Filter builder. + * + * \ingroup cpp_filters + */ template struct filter_builder final : _::filter_builder_base { - filter_builder(flecs::world_t* world) + filter_builder(flecs::world_t* world, const char *name = nullptr) : _::filter_builder_base(world) { _::sig(world).populate(this); + if (name != nullptr) { + ecs_entity_desc_t entity_desc = {}; + entity_desc.name = name; + entity_desc.sep = "::", + entity_desc.root_sep = "::", + this->m_desc.entity = ecs_entity_init(world, &entity_desc); + } } }; @@ -22790,6 +25252,10 @@ struct filter_base { return *this; } + flecs::entity entity() { + return flecs::entity(m_world, ecs_get_entity(m_filter_ptr)); + } + operator const flecs::filter_t*() const { return m_filter_ptr; } @@ -22826,7 +25292,7 @@ struct filter_base { operator filter<>() const; protected: - world_t *m_world; + world_t *m_world = nullptr; filter_t m_filter = ECS_FILTER_INIT; const filter_t *m_filter_ptr; }; @@ -22851,7 +25317,7 @@ public: filter(filter&& obj) : filter_base(FLECS_MOV(obj)) { } filter& operator=(filter&& obj) { - filter_base(FLECS_MOV(obj)); + filter_base::operator=(FLECS_FWD(obj)); return *this; } @@ -22971,6 +25437,11 @@ inline filter_base::operator flecs::filter<> () const { } +/** + * @file addons/cpp/mixins/event/impl.hpp + * @brief Event implementation. + */ + #pragma once @@ -22990,16 +25461,34 @@ inline flecs::event_builder_typed world::event() const { } // namespace flecs -#pragma once +/** + * @file addons/cpp/mixins/query/impl.hpp + * @brief Query implementation. + */ #pragma once +/** + * @file addons/cpp/mixins/query/builder.hpp + * @brief Query builder. + */ + +#pragma once + +/** + * @file addons/cpp/mixins/query/builder_i.hpp + * @brief Query builder interface. + */ + #pragma once namespace flecs { -// Query builder interface +/** Query builder interface. + * + * \ingroup cpp_core_queries + */ template struct query_builder_i : filter_builder_i { private: @@ -23154,6 +25643,10 @@ namespace _ { query_builder_i, Components ...>; } +/** Query builder. + * + * \ingroup cpp_core_queries + */ template struct query_builder final : _::query_builder_base { query_builder(flecs::world_t* world, const char *name = nullptr) @@ -23163,9 +25656,10 @@ struct query_builder final : _::query_builder_base { if (name != nullptr) { ecs_entity_desc_t entity_desc = {}; entity_desc.name = name; - this->m_desc.entity = ecs_entity_init(world, &entity_desc); + entity_desc.sep = "::", + entity_desc.root_sep = "::", + this->m_desc.filter.entity = ecs_entity_init(world, &entity_desc); } - } }; @@ -23214,7 +25708,7 @@ struct query_base { * * @return true if entities changed, otherwise false. */ - bool changed() { + bool changed() const { return ecs_query_changed(m_query, 0); } @@ -23225,7 +25719,7 @@ struct query_base { * * @return true if query is orphaned, otherwise false. */ - bool orphaned() { + bool orphaned() const { return ecs_query_orphaned(m_query); } @@ -23234,7 +25728,7 @@ struct query_base { * @param group_id The group id for which to retrieve the info. * @return The group info. */ - const flecs::query_group_info_t* group_info(uint64_t group_id) { + const flecs::query_group_info_t* group_info(uint64_t group_id) const { return ecs_query_get_group_info(m_query, group_id); } @@ -23243,7 +25737,7 @@ struct query_base { * @param group_id The group id for which to retrieve the context. * @return The group context. */ - void* group_ctx(uint64_t group_id) { + void* group_ctx(uint64_t group_id) const { const flecs::query_group_info_t *gi = group_info(group_id); if (gi) { return gi->ctx; @@ -23261,7 +25755,7 @@ struct query_base { } template - void each_term(const Func& func) { + void each_term(const Func& func) const { const ecs_filter_t *f = ecs_query_get_filter(m_query); ecs_assert(f != NULL, ECS_INVALID_PARAMETER, NULL); @@ -23271,29 +25765,29 @@ struct query_base { } } - filter_base filter() { + filter_base filter() const { return filter_base(m_world, ecs_query_get_filter(m_query)); } - flecs::term term(int32_t index) { + flecs::term term(int32_t index) const { const ecs_filter_t *f = ecs_query_get_filter(m_query); ecs_assert(f != NULL, ECS_INVALID_PARAMETER, NULL); return flecs::term(m_world, f->terms[index]); } - int32_t field_count() { + int32_t field_count() const { const ecs_filter_t *f = ecs_query_get_filter(m_query); return f->term_count; } - flecs::string str() { + flecs::string str() const { const ecs_filter_t *f = ecs_query_get_filter(m_query); char *result = ecs_filter_str(m_world, f); return flecs::string(result); } - flecs::entity entity() { - return flecs::entity(m_world, ecs_query_entity(m_query)); + flecs::entity entity() const { + return flecs::entity(m_world, ecs_get_entity(m_query)); } operator query<>() const; @@ -23358,10 +25852,25 @@ inline query_base::operator query<>() const { } // namespace flecs -#pragma once +/** + * @file addons/cpp/mixins/observer/impl.hpp + * @brief Observer implementation. + */ #pragma once +/** + * @file addons/cpp/mixins/observer/builder.hpp + * @brief Observer builder. + */ + +#pragma once + +/** + * @file addons/cpp/utils/node_builder.hpp + * @brief Base builder class for node objects, like systems, observers. + */ + #pragma once namespace flecs { @@ -23432,12 +25941,20 @@ private: } // namespace _ } // namespace flecs +/** + * @file addons/cpp/mixins/observer/builder_i.hpp + * @brief Observer builder interface. + */ + #pragma once namespace flecs { -// Observer builder interface +/** Observer builder interface. + * + * \ingroup cpp_observers + */ template struct observer_builder_i : filter_builder_i { using BaseClass = filter_builder_i; @@ -23474,11 +25991,17 @@ struct observer_builder_i : filter_builder_i { return *this; } - /** Set system context */ + /** Set observer context */ Base& ctx(void *ptr) { m_desc->ctx = ptr; return *this; - } + } + + /** Set observer run callback */ + Base& run(ecs_iter_action_t action) { + m_desc->run = action; + return *this; + } protected: virtual flecs::world_t* world_v() = 0; @@ -23503,6 +26026,10 @@ namespace _ { observer_builder_i, Components ...>; } +/** Observer builder. + * + * \ingroup cpp_observers + */ template struct observer_builder final : _::observer_builder_base { observer_builder(flecs::world_t* world, const char *name = nullptr) @@ -23548,6 +26075,12 @@ struct observer final : entity void* ctx() const { return ecs_get_observer_ctx(m_world, m_id); } + + flecs::filter<> query() const { + const flecs::Poly *poly = this->get(flecs::Observer); + const ecs_observer_t *ob = static_cast(poly->poly); + return flecs::filter<>(m_world, &ob->filter); + } }; // Mixin implementation @@ -23562,6 +26095,11 @@ inline observer_builder world::observer(Args &&... args) const { } // namespace flecs +/** + * @file addons/cpp/mixins/enum/impl.hpp + * @brief Enum implementation. + */ + #pragma once namespace flecs { @@ -23581,13 +26119,20 @@ inline flecs::entity world::to_entity(E constant) const { } #ifdef FLECS_MODULE +/** + * @file addons/cpp/mixins/module/impl.hpp + * @brief Module implementation. + */ + #pragma once namespace flecs { +namespace _ { + template ecs_entity_t do_import(world& world, const char *symbol) { - ecs_trace("import %s", _::type_name()); + ecs_trace("#[magenta]import#[reset] %s", _::type_name()); ecs_log_push(); ecs_entity_t scope = ecs_set_scope(world, 0); @@ -23625,18 +26170,28 @@ flecs::entity import(world& world) { /* Module is not yet registered, register it now */ } else { - m = do_import(world, symbol); + m = _::do_import(world, symbol); } /* Module has been registered, but could have been for another world. Import * if module hasn't been registered for this world. */ } else if (!m) { - m = do_import(world, symbol); + m = _::do_import(world, symbol); } return flecs::entity(world, m); } +} + +/** + * @defgroup cpp_addons_modules Modules + * @brief Modules organize components, systems and more in reusable units of code. + * + * \ingroup cpp_addons + * @{ + */ + template inline flecs::entity world::module(const char *name) const { flecs::id_t result = _::cpp_type::id(m_world); @@ -23649,23 +26204,44 @@ inline flecs::entity world::module(const char *name) const { template inline flecs::entity world::import() { - return flecs::import(*this); + return flecs::_::import(*this); } +/** @} */ + } #endif #ifdef FLECS_SYSTEM -#pragma once +/** + * @file addons/cpp/mixins/system/impl.hpp + * @brief System module implementation. + */ #pragma once +/** + * @file addons/cpp/mixins/system/builder.hpp + * @brief System builder. + */ + +#pragma once + +/** + * @file addons/cpp/mixins/system/builder_i.hpp + * @brief System builder interface. + */ + #pragma once namespace flecs { -// System builder interface + +/** System builder interface. + * + * \ingroup cpp_addons_systems + */ template struct system_builder_i : query_builder_i { private: @@ -23716,8 +26292,8 @@ public: * * @param value If false system will always run staged. */ - Base& no_staging(bool value = true) { - m_desc->no_staging = value; + Base& no_readonly(bool value = true) { + m_desc->no_readonly = value; return *this; } @@ -23759,12 +26335,28 @@ public: return *this; } + /** Set tick source. + * This operation sets a shared tick source for the system. + * + * @param tick_source The tick source to use for the system. + */ + Base& tick_source(flecs::entity_t tick_source) { + m_desc->tick_source = tick_source; + return *this; + } + /** Set system context */ Base& ctx(void *ptr) { m_desc->ctx = ptr; return *this; } + /** Set system run callback */ + Base& run(ecs_iter_action_t action) { + m_desc->run = action; + return *this; + } + protected: virtual flecs::world_t* world_v() = 0; @@ -23776,6 +26368,8 @@ private: ecs_system_desc_t *m_desc; }; +/** @} */ + } @@ -23787,6 +26381,10 @@ namespace _ { system_builder_i, Components ...>; } +/** System builder. + * + * \ingroup cpp_addons_systems + */ template struct system_builder final : _::system_builder_base { system_builder(flecs::world_t* world, const char *name = nullptr) @@ -23914,6 +26512,15 @@ struct system final : entity } # ifdef FLECS_TIMER +/** + * @file addons/cpp/mixins/timer/system_mixin.inl + * @brief Timer module system mixin. + */ + +/** + * \memberof flecs::system + * \ingroup cpp_addons_timer + */ /** Set interval. * @see ecs_set_interval @@ -23955,6 +26562,8 @@ void stop(); */ void set_tick_source(flecs::entity e); +/** @} */ + # endif }; @@ -23980,16 +26589,34 @@ inline void system_init(flecs::world& world) { #endif #ifdef FLECS_PIPELINE -#pragma once +/** + * @file addons/cpp/mixins/pipeline/impl.hpp + * @brief Pipeline module implementation. + */ #pragma once +/** + * @file addons/cpp/mixins/pipeline/builder.hpp + * @brief Pipeline builder. + */ + +#pragma once + +/** + * @file addons/cpp/mixins/pipeline/builder_i.hpp + * @brief Pipeline builder interface. + */ + #pragma once namespace flecs { -// Query builder interface +/** Pipeline builder interface. + * + * \ingroup cpp_pipelines + */ template struct pipeline_builder_i : query_builder_i { pipeline_builder_i(ecs_pipeline_desc_t *desc, int32_t term_index = 0) @@ -24011,6 +26638,10 @@ namespace _ { pipeline_builder_i, Components ...>; } +/** Pipeline builder. + * + * \ingroup cpp_pipelines + */ template struct pipeline_builder final : _::pipeline_builder_base { pipeline_builder(flecs::world_t* world, flecs::entity_t id = 0) @@ -24069,7 +26700,7 @@ inline bool world::progress(ecs_ftime_t delta_time) const { return ecs_progress(m_world, delta_time); } -inline void world::run_pipeline(const flecs::entity pip, ecs_ftime_t delta_time) const { +inline void world::run_pipeline(const flecs::entity_t pip, ecs_ftime_t delta_time) const { return ecs_run_pipeline(m_world, pip, delta_time); } @@ -24112,16 +26743,56 @@ inline int32_t world::get_threads() const { #endif #ifdef FLECS_TIMER +/** + * @file addons/cpp/mixins/timer/impl.hpp + * @brief Timer module implementation. + */ + #pragma once namespace flecs { // Timer class struct timer final : entity { - template - timer(Args&&... args) : entity(FLECS_FWD(args)...) { } + using entity::entity; + + timer& interval(ecs_ftime_t interval) { + ecs_set_interval(m_world, m_id, interval); + return *this; + } + + ecs_ftime_t interval() { + return ecs_get_interval(m_world, m_id); + } + + timer& timeout(ecs_ftime_t timeout) { + ecs_set_timeout(m_world, m_id, timeout); + return *this; + } + + ecs_ftime_t timeout() { + return ecs_get_timeout(m_world, m_id); + } + + timer& rate(int32_t rate, flecs::entity_t tick_source = 0) { + ecs_set_rate(m_world, m_id, rate, tick_source); + return *this; + } + + void start() { + ecs_start_timer(m_world, m_id); + } + + void stop() { + ecs_stop_timer(m_world, m_id); + } }; +template +inline flecs::timer world::timer(Args &&... args) const { + return flecs::timer(m_world, FLECS_FWD(args)...); +} + inline void system::interval(ecs_ftime_t interval) { ecs_set_interval(m_world, m_id, interval); } @@ -24166,6 +26837,11 @@ inline void timer_init(flecs::world& world) { #endif #ifdef FLECS_SNAPSHOT +/** + * @file addons/cpp/mixins/snapshot/impl.hpp + * @brief Snapshot module implementation. + */ + #pragma once namespace flecs { @@ -24254,6 +26930,11 @@ inline flecs::snapshot world::snapshot(Args &&... args) const { #endif #ifdef FLECS_DOC +/** + * @file addons/cpp/mixins/doc/impl.hpp + * @brief Doc mixin implementation. + */ + #pragma once namespace flecs { @@ -24305,6 +26986,11 @@ inline void init(flecs::world& world) { #ifdef FLECS_DOC #endif #ifdef FLECS_REST +/** + * @file addons/cpp/mixins/rest/impl.hpp + * @brief Rest module implementation. + */ + #pragma once namespace flecs { @@ -24321,8 +27007,18 @@ inline void init(flecs::world& world) { #endif #ifdef FLECS_RULES +/** + * @file addons/cpp/mixins/rule/impl.hpp + * @brief Rule implementation. + */ + #pragma once +/** + * @file addons/cpp/mixins/rule/builder.hpp + * @brief Rule builder. + */ + #pragma once @@ -24334,12 +27030,23 @@ namespace _ { filter_builder_i, Components ...>; } +/** Rule builder. + * + * \ingroup cpp_addons_rules + */ template struct rule_builder final : _::rule_builder_base { - rule_builder(flecs::world_t* world) + rule_builder(flecs::world_t* world, const char *name = nullptr) : _::rule_builder_base(world) { _::sig(world).populate(this); + if (name != nullptr) { + ecs_entity_desc_t entity_desc = {}; + entity_desc.name = name; + entity_desc.sep = "::", + entity_desc.root_sep = "::", + this->m_desc.entity = ecs_entity_init(world, &entity_desc); + } } }; @@ -24379,6 +27086,10 @@ struct rule_base { return m_rule; } + flecs::entity entity() { + return flecs::entity(m_world, ecs_get_entity(m_rule)); + } + /** Free the rule. */ void destruct() { @@ -24449,6 +27160,11 @@ inline rule_base::operator rule<>() const { #endif #ifdef FLECS_META +/** + * @file addons/cpp/mixins/meta/impl.hpp + * @brief Meta implementation. + */ + #pragma once FLECS_ENUM_LAST(flecs::type_kind_t, EcsTypeKindLast) @@ -24498,12 +27214,18 @@ inline void init(flecs::world& world) { flecs::_::cpp_type::init(world, flecs::Iptr, true); ecs_assert(flecs::type_id() == flecs::Iptr, ECS_INTERNAL_ERROR, NULL); + // Remove symbol to prevent validation errors, as it doesn't match with + // the typename + ecs_remove_pair(world, flecs::Iptr, ecs_id(EcsIdentifier), EcsSymbol); } if (!flecs::is_same() && !flecs::is_same()) { flecs::_::cpp_type::init(world, flecs::Uptr, true); ecs_assert(flecs::type_id() == flecs::Uptr, ECS_INTERNAL_ERROR, NULL); + // Remove symbol to prevent validation errors, as it doesn't match with + // the typename + ecs_remove_pair(world, flecs::Uptr, ecs_id(EcsIdentifier), EcsSymbol); } } @@ -24526,6 +27248,11 @@ inline flecs::entity cursor::get_entity() const { #endif #ifdef FLECS_UNITS +/** + * @file addons/cpp/mixins/units/impl.hpp + * @brief Units module implementation. + */ + #pragma once namespace flecs { @@ -24585,6 +27312,8 @@ inline units::units(flecs::world& world) { world.entity("::flecs::units::Data"); world.entity("::flecs::units::DataRate"); world.entity("::flecs::units::Angle"); + world.entity("::flecs::units::Frequency"); + world.entity("::flecs::units::Uri"); // Initialize duration units world.entity( @@ -24634,6 +27363,7 @@ inline units::units(flecs::world& world) { world.entity("::flecs::units::Length::CentiMeters"); world.entity("::flecs::units::Length::KiloMeters"); world.entity("::flecs::units::Length::Miles"); + world.entity("::flecs::units::Length::Pixels"); // Initialize pressure units world.entity("::flecs::units::Pressure::Pascal"); @@ -24699,6 +27429,24 @@ inline units::units(flecs::world& world) { world.entity( "::flecs::units::DataRate::GigaBytesPerSecond"); + // Initialize hertz units + world.entity( + "::flecs::units::Frequency::Hertz"); + world.entity( + "::flecs::units::Frequency::KiloHertz"); + world.entity( + "::flecs::units::Frequency::MegaHertz"); + world.entity( + "::flecs::units::Frequency::GigaHertz"); + + // Initialize uri units + world.entity( + "::flecs::units::Uri::Hyperlink"); + world.entity( + "::flecs::units::Uri::Image"); + world.entity( + "::flecs::units::Uri::File"); + // Initialize angles world.entity( "::flecs::units::Angle::Radians"); @@ -24717,6 +27465,11 @@ inline units::units(flecs::world& world) { #endif #ifdef FLECS_MONITOR +/** + * @file addons/cpp/mixins/monitor/impl.hpp + * @brief Monitor module implementation. + */ + #pragma once namespace flecs { @@ -24730,7 +27483,12 @@ inline monitor::monitor(flecs::world& world) { } #endif +/** + * @file addons/cpp/impl/iter.hpp + * @brief Iterator implementation. + */ +#pragma once namespace flecs { @@ -24786,6 +27544,11 @@ inline flecs::table iter::table() const { return flecs::table(m_iter->world, m_iter->table); } +inline flecs::table_range iter::range() const { + return flecs::table_range(m_iter->world, m_iter->table, + m_iter->offset, m_iter->count); +} + #ifdef FLECS_RULES inline flecs::entity iter::get_var(int var_id) const { ecs_assert(m_iter->next == ecs_rule_next, ECS_INVALID_OPERATION, NULL); @@ -24808,19 +27571,16 @@ inline flecs::entity iter::get_var(const char *name) const { } // namespace flecs +/** + * @file addons/cpp/impl/world.hpp + * @brief World implementation. + */ + #pragma once namespace flecs { -// emplace for T(flecs::entity, Args...) -template , flecs::entity, Args...>::value >> -inline void emplace(world_t *world, id_t entity, Args&&... args) { - flecs::entity self(world, entity); - emplace(world, entity, self, FLECS_FWD(args)...); -} - inline void world::init_builtin_components() { component("flecs::core::Component"); component("flecs::core::Identifier"); @@ -24844,7 +27604,7 @@ inline void world::init_builtin_components() { } template -inline flecs::entity world::use(const char *alias) { +inline flecs::entity world::use(const char *alias) const { entity_t e = _::cpp_type::id(m_world); const char *name = alias; if (!name) { @@ -24855,7 +27615,7 @@ inline flecs::entity world::use(const char *alias) { return flecs::entity(m_world, e); } -inline flecs::entity world::use(const char *name, const char *alias) { +inline flecs::entity world::use(const char *name, const char *alias) const { entity_t e = ecs_lookup_path_w_sep(m_world, 0, name, "::", "::", true); ecs_assert(e != 0, ECS_INVALID_PARAMETER, NULL); @@ -24863,7 +27623,7 @@ inline flecs::entity world::use(const char *name, const char *alias) { return flecs::entity(m_world, e); } -inline void world::use(flecs::entity e, const char *alias) { +inline void world::use(flecs::entity e, const char *alias) const { entity_t eid = e.id(); const char *name = alias; if (!name) { @@ -24903,6 +27663,12 @@ void world::modified() const { return e.modified(); } +template +ref world::get_ref() const { + flecs::entity e(m_world, _::cpp_type::id(m_world)); + return e.get_ref(); +} + template const T* world::get() const { flecs::entity e(m_world, _::cpp_type::id(m_world)); @@ -24928,19 +27694,19 @@ void world::remove() const { } template -inline flecs::entity world::singleton() { +inline flecs::entity world::singleton() const { return flecs::entity(m_world, _::cpp_type::id(m_world)); } template ::value > > -void world::get(const Func& func) { +void world::get(const Func& func) const { static_assert(arity::value == 1, "singleton component must be the only argument"); _::entity_with_invoker::invoke_get( this->m_world, this->singleton>(), func); } template ::value > > -void world::set(const Func& func) { +void world::set(const Func& func) const { static_assert(arity::value == 1, "singleton component must be the only argument"); _::entity_with_invoker::invoke_get_mut( this->m_world, this->singleton>(), func); @@ -24977,12 +27743,12 @@ inline flecs::entity enum_data::entity(E value) const { /** Use provided scope for operations ran on returned world. * Operations need to be ran in a single statement. */ -inline flecs::scoped_world world::scope(id_t parent) { +inline flecs::scoped_world world::scope(id_t parent) const { return scoped_world(m_world, parent); } template -inline flecs::scoped_world world::scope() { +inline flecs::scoped_world world::scope() const { flecs::id_t parent = _::cpp_type::id(m_world); return scoped_world(m_world, parent); } @@ -24990,6 +27756,23 @@ inline flecs::scoped_world world::scope() { } // namespace flecs +/** + * @defgroup cpp_core Core + * @brief Core ECS functionality (entities, storage, queries) + * + * @{ + * @} + */ + +/** + * @defgroup cpp_addons Addons + * @brief C++ APIs for addons. + * + * @{ + * @} + */ + +/** @} */ extern "C" {