diff --git a/distr/flecs.c b/distr/flecs.c index d0caa1c0fe..9b0092f68d 100644 --- a/distr/flecs.c +++ b/distr/flecs.c @@ -1,26 +1,4 @@ -/** - * @file bootstrap.c - * @brief Bootstrap entities in the flecs.core namespace. - * - * Before the ECS storage can be used, core entities first need to be - * initialized. For example, components in Flecs are stored as entities in the - * ECS storage itself with an EcsComponent component, but before this component - * can be stored, the component itself needs to be initialized. - * - * The bootstrap code uses lower-level APIs to initialize the data structures. - * After bootstrap is completed, regular ECS operations can be used to create - * entities and components. - * - * The bootstrap file also includes several lifecycle hooks and observers for - * builtin features, such as relationship properties and hooks for keeping the - * entity name administration in sync with the (Identifier, Name) component. - */ - #include "flecs.h" -/** - * @file private_api.h - * @brief Private functions. - */ #ifndef FLECS_PRIVATE_H #define FLECS_PRIVATE_H @@ -36,15 +14,9 @@ #include #include -/** - * @file datastructures/bitset.h - * @brief Bitset data structure. - */ - #ifndef FLECS_BITSET_H #define FLECS_BITSET_H - #ifdef __cplusplus extern "C" { #endif @@ -152,11 +124,6 @@ void flecs_bitset_swap( #endif -/** - * @file datastructures/name_index.h - * @brief Data structure for resolving 64bit keys by string (name). - */ - #ifndef FLECS_NAME_INDEX_H #define FLECS_NAME_INDEX_H @@ -222,12 +189,6 @@ bool flecs_name_index_update_name( #endif -/** - * @file storage/entity_index.h - * @brief Entity index data structure. - * - * The entity index stores the table and row for an entity id. - */ #ifndef FLECS_ENTITY_INDEX_H #define FLECS_ENTITY_INDEX_H @@ -387,11 +348,6 @@ const uint64_t* flecs_entity_index_ids( #endif -/** - * @file storage/table_cache.h - * @brief Data structure for fast table iteration/lookups. - */ - #ifndef FLECS_TABLE_CACHE_H_ #define FLECS_TABLE_CACHE_H_ @@ -456,11 +412,6 @@ const ecs_table_cache_hdr_t* flecs_table_cache_next_( #endif -/** - * @file storage/component_index.h - * @brief Index for (amongst others) looking up tables by component id. - */ - #ifndef FLECS_COMPONENT_INDEX_H #define FLECS_COMPONENT_INDEX_H @@ -649,19 +600,9 @@ void flecs_component_ordered_children_init( #endif -/** - * @file storage/table.h - * @brief Table storage implementation. - */ - #ifndef FLECS_TABLE_H #define FLECS_TABLE_H -/** - * @file storage/table_graph.h - * @brief Table graph types and functions. - */ - #ifndef FLECS_TABLE_GRAPH_H #define FLECS_TABLE_GRAPH_H @@ -783,7 +724,6 @@ void flecs_table_clear_edges_for_id( #endif - #ifdef FLECS_SANITIZE #define ecs_vec_from_column(arg_column, table, arg_elem_size) {\ .array = (arg_column)->data,\ @@ -885,6 +825,10 @@ typedef struct ecs_table__t { struct ecs_table_record_t *records; /* Array with table records */ + ecs_type_t *extended_type; /* Component ids + inherited base ids, used + * to emit OnTableCreate/OnTableDelete events + * for tables with derived components */ + #ifdef FLECS_DEBUG_INFO /* Fields used for debug visualization */ struct { @@ -1087,11 +1031,6 @@ ecs_hashmap_t* flecs_table_get_name_index( #endif -/** - * @file storage/sparse_storage.h - * @brief Sparse component storage. - */ - #ifndef FLECS_SPARSE_STORAGE_H #define FLECS_SPARSE_STORAGE_H @@ -1129,11 +1068,6 @@ void flecs_component_sparse_remove_all( #endif -/** - * @file storage/ordered_children.h - * @brief Storage for ordered list of entity (child) ids. - */ - #ifndef FLECS_ORDERED_CHILDREN_H #define FLECS_ORDERED_CHILDREN_H @@ -1192,11 +1126,6 @@ void flecs_ordered_entities_remove( #endif -/** - * @file storage/non_fragmenting_childof.h - * @brief Non-fragmenting storage for hierarchies. - */ - #ifndef FLECS_NON_FRAGMENTING_CHILDOF_H #define FLECS_NON_FRAGMENTING_CHILDOF_H @@ -1244,27 +1173,12 @@ int flecs_add_non_fragmenting_child_w_records( #endif -/** - * @file query/query.h - * @brief Query implementation. - */ - #ifndef FLECS_QUERY_H #define FLECS_QUERY_H -/** - * @file query/compiler/compiler.h - * @brief Query compiler functions. - */ - #ifndef FLECS_QUERY_COMPILER_H #define FLECS_QUERY_COMPILER_H -/** - * @file query/types.h - * @brief Internal types and functions for queries. - */ - #ifndef FLECS_QUERY_TYPES_H #define FLECS_QUERY_TYPES_H @@ -1733,7 +1647,6 @@ struct ecs_query_impl_t { #endif - /* Compile query to list of operations */ int flecs_query_compile( ecs_world_t *world, @@ -1797,15 +1710,9 @@ ecs_var_id_t flecs_query_find_var_id( #endif -/** - * @file query/cache/cache.h - * @brief Query cache functions. - */ - #ifndef FLECS_QUERY_CACHE_H #define FLECS_QUERY_CACHE_H - /** Table match data. * Each table matched by the query is represented by an ecs_query_cache_match_t * instance. A table may match a query multiple times (due to wildcard queries) @@ -1925,15 +1832,9 @@ bool flecs_query_cache_is_trivial( ecs_size_t flecs_query_cache_elem_size( const ecs_query_cache_t *cache); -/** - * @file query/cache/cache_iter.h - * @brief Cache iterator functions. - */ - #ifndef FLECS_QUERY_CACHE_ITER_H #define FLECS_QUERY_CACHE_ITER_H - void flecs_query_cache_iter_init( ecs_iter_t *it, ecs_query_iter_t *qit, @@ -1966,11 +1867,6 @@ bool flecs_query_is_trivial_cache_test( #endif -/** - * @file query/cache/group.h - * @brief Adding/removing tables to/from query groups. - */ - #ifndef FLECS_QUERY_GROUP_H #define FLECS_QUERY_GROUP_H @@ -2003,11 +1899,6 @@ ecs_query_cache_match_t* flecs_query_cache_match_from_table( #endif -/** - * @file query/cache/match.h - * @brief Match table one or more times with query. - */ - #ifndef FLECS_QUERY_MATCH_H #define FLECS_QUERY_MATCH_H @@ -2021,11 +1912,6 @@ bool flecs_query_cache_match_next( #endif -/** - * @file query/cache/change_detection.h - * @brief Query change detection functions. - */ - #ifndef FLECS_QUERY_CHANGE_DETECTION_H #define FLECS_QUERY_CHANGE_DETECTION_H @@ -2051,26 +1937,14 @@ bool flecs_query_update_fixed_monitor( #endif - #endif -/** - * @file query/engine/engine.h - * @brief Query engine functions. - */ - #ifndef FLECS_QUERY_ENGINE_H #define FLECS_QUERY_ENGINE_H -/** - * @file query/engine/trav_cache.h - * @brief Traversal cache functions. - */ - #ifndef FLECS_QUERY_TRAV_CACHE_H #define FLECS_QUERY_TRAV_CACHE_H - /* Traversal cache for transitive queries. Finds all reachable entities by * following a relationship. */ @@ -2128,15 +2002,9 @@ void flecs_query_up_cache_fini( #endif -/** - * @file query/engine/trivial_iter.h - * @brief Trivial iterator functions. - */ - #ifndef FLECS_QUERY_TRIVIAL_ITER_H #define FLECS_QUERY_TRIVIAL_ITER_H - /* Iterator for queries with trivial terms. */ bool flecs_query_trivial_search( const ecs_query_run_ctx_t *ctx, @@ -2158,7 +2026,6 @@ bool flecs_query_trivial_test( #endif - /* Query evaluation utilities */ void flecs_query_set_iter_this( @@ -2260,6 +2127,7 @@ ecs_id_t flecs_query_op_get_id( const ecs_query_run_ctx_t *ctx); int16_t flecs_query_next_column( + const ecs_world_t *world, ecs_table_t *table, ecs_id_t id, int32_t column); @@ -2300,7 +2168,6 @@ bool flecs_query_run_until( ecs_query_lbl_t cur, int32_t last); - /* And evaluation */ bool flecs_query_and( @@ -2338,7 +2205,6 @@ bool flecs_query_select_w_id( ecs_id_t id, ecs_flags32_t filter_mask); - /* Sparse evaluation */ bool flecs_query_sparse( @@ -2368,7 +2234,6 @@ bool flecs_query_sparse_self_up( bool redo, const ecs_query_run_ctx_t *ctx); - /* Hierarchy evaluation */ const EcsParent* flecs_query_tree_get_parents( @@ -2429,7 +2294,6 @@ bool flecs_query_toggle_option( bool redo, ecs_query_run_ctx_t *ctx); - /* Equality predicate evaluation */ bool flecs_query_pred_eq_match( @@ -2462,7 +2326,6 @@ bool flecs_query_pred_neq_name( bool redo, ecs_query_run_ctx_t *ctx); - /* Component member evaluation */ bool flecs_query_member_eq( @@ -2475,7 +2338,6 @@ bool flecs_query_member_neq( bool redo, ecs_query_run_ctx_t *ctx); - /* Up traversal */ typedef enum ecs_query_up_select_trav_kind_t { @@ -2516,7 +2378,6 @@ bool flecs_query_self_up_with( bool redo, const ecs_query_run_ctx_t *ctx); - /* Transitive relationship traversal */ bool flecs_query_trav( @@ -2526,16 +2387,9 @@ bool flecs_query_trav( #endif - -/** - * @file query/util.h - * @brief Utility functions. - */ - #ifndef FLECS_QUERY_UTIL_H #define FLECS_QUERY_UTIL_H - /* Helper type for passing around context required for error messages */ typedef struct { const ecs_world_t *world; @@ -2629,8 +2483,6 @@ ecs_id_t flecs_query_iter_set_id( #endif - - #ifdef FLECS_DEBUG #define flecs_set_var_label(var, lbl) (var)->label = lbl #else @@ -2681,16 +2533,9 @@ void flecs_query_reclaim( #endif - -/** - * @file component_actions.h - * @brief Logic executed after adding/removing a component. - */ - #ifndef FLECS_COMPONENT_ACTIONS_H #define FLECS_COMPONENT_ACTIONS_H - /* Invoke component hook. */ void flecs_invoke_hook( ecs_world_t *world, @@ -2791,15 +2636,9 @@ void flecs_notify_on_set_ids( #endif -/** - * @file entity_name.h - * @brief Utilities for looking up entities by name. - */ - #ifndef FLECS_ENTITY_NAME_H #define FLECS_ENTITY_NAME_H - /* Called during bootstrap to register entity name observers with world. */ void flecs_bootstrap_entity_name( ecs_world_t *world); @@ -2825,11 +2664,6 @@ void ecs_on_set(EcsIdentifier)( #endif -/** - * @file commands.h - * @brief Command queue implementation. - */ - #ifndef FLECS_COMMANDS_H #define FLECS_COMMANDS_H @@ -3029,11 +2863,6 @@ void flecs_enqueue( #endif -/** - * @file entity.h - * @brief Internal functions for dealing with entities. - */ - #ifndef FLECS_ENTITY_H #define FLECS_ENTITY_H @@ -3200,11 +3029,6 @@ const char* flecs_entity_invalid_reason( #endif -/** - * @file instantiate.h - * @brief Functions for instantiating prefabs (IsA relationship). - */ - #ifndef FLECS_INSTANTIATE_H #define FLECS_INSTANTIATE_H @@ -3242,11 +3066,6 @@ ecs_entity_t flecs_instantiate_alloc_child_id( #endif -/** - * @file observable.h - * @brief Functions for emitting events. - */ - #ifndef FLECS_OBSERVABLE_H #define FLECS_OBSERVABLE_H @@ -3368,11 +3187,6 @@ void flecs_observer_set_disable_bit( #endif -/** - * @file iter.h - * @brief Iterator utilities. - */ - #ifndef FLECS_ITER_H #define FLECS_ITER_H @@ -3413,11 +3227,6 @@ void* flecs_iter_calloc( #endif -/** - * @file poly.h - * @brief Functions for managing poly objects. - */ - #ifndef FLECS_POLY_H #define FLECS_POLY_H @@ -3530,11 +3339,6 @@ flecs_poly_dtor_t* flecs_get_dtor( #endif -/** - * @file tree_spawner.h - * @brief Data structure used to speed up the creation of hierarchies. - */ - #ifndef FLECS_TREE_SPAWNER_H #define FLECS_TREE_SPAWNER_H @@ -3555,11 +3359,6 @@ void flecs_spawner_instantiate( #endif -/** - * @file stage.h - * @brief Stage functions. - */ - #ifndef FLECS_STAGE_H #define FLECS_STAGE_H @@ -3650,11 +3449,6 @@ void ecs_stage_shrink( #endif -/** - * @file world.h - * @brief World functions. - */ - #ifndef FLECS_WORLD_H #define FLECS_WORLD_H @@ -4017,18 +3811,6 @@ bool flecs_component_is_delete_locked( #endif -/** - * @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 @@ -4094,7 +3876,6 @@ void flecs_journal_end(void); #endif // FLECS_JOURNAL - /* Used in id records to keep track of entities used with id flags */ extern const ecs_entity_t EcsFlag; @@ -4122,7 +3903,6 @@ void flecs_bootstrap( flecs_bootstrap_tag(world, name)\ ecs_add_id(world, name, EcsTrait) - //////////////////////////////////////////////////////////////////////////////// //// Safe(r) integer casting //////////////////////////////////////////////////////////////////////////////// @@ -4181,7 +3961,6 @@ uint64_t flecs_ito_( #define flecs_itoi16(value) flecs_ito(int16_t, (value)) #define flecs_itoi32(value) flecs_ito(int32_t, (value)) - //////////////////////////////////////////////////////////////////////////////// //// Utilities //////////////////////////////////////////////////////////////////////////////// @@ -4265,7 +4044,6 @@ const char* flecs_errstr_5( #endif - /* -- Identifier Component -- */ static ECS_DTOR(EcsIdentifier, ptr, { ecs_os_strset(&ptr->value, NULL); @@ -4294,7 +4072,6 @@ static ECS_MOVE(EcsIdentifier, dst, src, { src->length = 0; }) - /* -- Poly component -- */ static ECS_COPY(EcsPoly, dst, src, { @@ -4322,7 +4099,6 @@ static ECS_DTOR(EcsPoly, ptr, { } }) - /* -- Builtin observers -- */ static @@ -4490,6 +4266,38 @@ void flecs_register_flag_for_trait( return; } +#ifndef FLECS_NDEBUG +static +void flecs_assert_isa_change(ecs_iter_t *it) { + ecs_world_t *world = it->real_world; + + if (world->flags & (EcsWorldInit|EcsWorldFini|EcsWorldQuit)) { + return; + } + + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t component = it->entities[i]; + + ecs_component_record_t *cr = flecs_components_get(world, component); + if (cr && (cr->flags & EcsIdMarkedForDelete)) { + continue; + } + + if (ecs_id_in_use(world, component) || + ecs_id_in_use(world, ecs_pair(component, EcsWildcard))) + { + ecs_throw(ECS_INVALID_OPERATION, + "cannot change (IsA) trait for '%s': component is already in use", + flecs_errstr(ecs_get_path(world, component))); + } + + error: + continue; + } +} +#endif + static void flecs_register_final(ecs_iter_t *it) { ecs_world_t *world = it->world; @@ -4601,7 +4409,6 @@ void flecs_on_symmetric_add_remove(ecs_iter_t *it) { ecs_entity_t tgt = ecs_pair_second(world, pair); ecs_entity_t event = it->event; - if (tgt) { int i, count = it->count; for (i = 0; i < count; i ++) { @@ -5323,6 +5130,18 @@ void flecs_bootstrap( .global_observer = true }); +#ifndef FLECS_NDEBUG + ecs_observer(world, { + .query.terms = { + { .id = ecs_pair(EcsIsA, EcsWildcard) } + }, + .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, + .events = {EcsOnAdd, EcsOnRemove}, + .callback = flecs_assert_isa_change, + .global_observer = true + }); +#endif + static ecs_on_trait_ctx_t inheritable_trait = { EcsIdInheritable, 0 }; ecs_observer(world, { .query.terms = {{ .id = EcsInheritable }}, @@ -5593,12 +5412,6 @@ void flecs_bootstrap( ecs_log_pop(); } -/** - * @file commands.c - * @brief Command queue implementation. - */ - - static ecs_table_t* flecs_find_table_remove( ecs_world_t *world, @@ -7054,20 +6867,6 @@ void ecs_defer_resume( return; } -/** - * @file component_actions.c - * @brief Logic executed after adding/removing a component. - * - * After a component is added to an entity there can be additional things that - * need to be done, such as: - * - * - Invoking hooks - * - Notifying observers - * - Updating sparse storage - * - Updating name lookup index - */ - - void flecs_invoke_hook( ecs_world_t *world, ecs_table_t *table, @@ -7694,12 +7493,6 @@ void flecs_notify_on_set( } } -/** - * @file each.c - * @brief Simple iterator for a single component id. - */ - - static bool flecs_each_component_record( ecs_iter_t *it, @@ -7885,26 +7678,11 @@ int32_t ecs_count_id( return 0; } -/** - * @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. - */ - - #ifdef FLECS_QUERY_DSL -/** - * @file addons/query_dsl/query_dsl.h - * @brief Query DSL parser addon. - */ #ifndef FLECS_QUERY_DSL_H #define FLECS_QUERY_DSL_H - int flecs_terms_parse( ecs_world_t *world, const char *name, @@ -8626,7 +8404,6 @@ const char* flecs_entity_invalid_reason( flecs_errstr_1(ecs_get_path(world, entity)), \ flecs_id_invalid_reason(world, component)) - /* -- Public functions -- */ bool ecs_commit( @@ -11159,7 +10936,6 @@ void ecs_set_version( } } - uint32_t ecs_get_version( ecs_entity_t entity) { @@ -11306,12 +11082,6 @@ ecs_table_range_t flecs_range_from_entity( }; } -/** - * @file entity_name.c - * @brief Functions for working with named entities. - */ - - #define ECS_NAME_BUFFER_LENGTH (64) static @@ -12419,12 +12189,6 @@ void ecs_set_alias( flecs_set_identifier(world, NULL, entity, EcsAlias, name); } -/** - * @file id.c - * @brief Id utilities. - */ - - #ifdef FLECS_QUERY_DSL #endif @@ -12780,12 +12544,6 @@ bool ecs_id_in_use( return (flecs_table_cache_count(&cr->cache) != 0); } -/** - * @file instantiate.c - * @brief Functions for instantiating prefabs (IsA relationship). - */ - - static void flecs_instantiate_slot( ecs_world_t *world, @@ -13330,17 +13088,6 @@ void flecs_instantiate( return; } -/** - * @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. - */ - - /* Utility macros to enforce consistency when initializing iterator fields */ /* If term count is smaller than cache size, initialize with inline array, @@ -13461,8 +13208,45 @@ void* ecs_field_w_size( int16_t column = it->columns[index]; if (column >= 0) { - return ECS_ELEM(it->table->data.columns[column].data, - (ecs_size_t)size, it->offset); + ecs_column_t *col = &it->table->data.columns[column]; + ecs_assert((ecs_size_t)size == col->ti->size, + ECS_INVALID_PARAMETER, NULL); + return ECS_ELEM(col->data, (ecs_size_t)size, it->offset); + } + + return flecs_field_shared(it, size, index); +error: + return NULL; +} + +void* ecs_base_field_w_size( + const ecs_iter_t *it, + size_t size, + int8_t index) +{ + ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, + "operation invalid before calling next()"); + ecs_check(index >= 0, ECS_INVALID_PARAMETER, + "invalid field index %d", index); + ecs_check(index < it->field_count, ECS_INVALID_PARAMETER, + "field index %d out of bounds", index); + ecs_check(size != 0, ECS_INVALID_PARAMETER, + "missing size for field %d", index); + ecs_check(ecs_field_size(it, index) == size || + !ecs_field_size(it, index), + ECS_INVALID_PARAMETER, + "mismatching size for field %d (expected '%s')", + index, + flecs_errstr(ecs_id_str(it->world, it->ids[index]))); + + if (it->ptrs) { + return it->ptrs[index]; + } + + int16_t column = it->columns[index]; + if (column >= 0) { + ecs_column_t *col = &it->table->data.columns[column]; + return ECS_ELEM(col->data, col->ti->size, it->offset); } return flecs_field_shared(it, size, index); @@ -13687,9 +13471,9 @@ size_t ecs_field_size( const ecs_iter_t *it, int8_t index) { - ecs_check(index >= 0, ECS_INVALID_PARAMETER, + ecs_check(index >= 0, ECS_INVALID_PARAMETER, "invalid field index %d", index); - ecs_check(index < it->field_count, ECS_INVALID_PARAMETER, + ecs_check(index < it->field_count, ECS_INVALID_PARAMETER, "field index %d out of bounds", index); return (size_t)it->sizes[index]; @@ -13697,6 +13481,25 @@ size_t ecs_field_size( return 0; } +size_t ecs_field_stride( + const ecs_iter_t *it, + int8_t index) +{ + ecs_check(index >= 0, ECS_INVALID_PARAMETER, + "invalid field index %d", index); + ecs_check(index < it->field_count, ECS_INVALID_PARAMETER, + "field index %d out of bounds", index); + + int16_t column = it->columns[index]; + if (column >= 0 && it->table) { + return (size_t)it->table->data.columns[column].ti->size; + } + + return (size_t)it->sizes[index]; +error: + return 0; +} + char* ecs_iter_str( const ecs_iter_t *it) { @@ -14348,15 +14151,10 @@ bool ecs_worker_next( return false; } -/** - * @file misc.c - * @brief Miscellaneous functions. - */ #include #include - #ifndef FLECS_NDEBUG static int64_t flecs_s_min[] = { [1] = INT8_MIN, [2] = INT16_MIN, [4] = INT32_MIN, [8] = INT64_MIN }; @@ -14876,17 +14674,6 @@ const char* flecs_errstr_5( return flecs_errstr_buf_5; } -/** - * @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 speed up event propagation when - * relationships are added/removed to/from entities. - */ - - void flecs_observable_init( ecs_observable_t *observable) { @@ -16529,17 +16316,6 @@ void ecs_enqueue( flecs_enqueue(world, stage, desc); } -/** - * @file observer.c - * @brief Observer implementation. - * - * The observer implementation contains functions for creating, deleting and - * invoking observers. The code is split up into single-term observers and - * multi-term observers. Multi-term observers are created from multiple single- - * term observers. - */ - - static ecs_entity_t flecs_get_observer_event( ecs_term_t *term, @@ -17197,7 +16973,8 @@ void flecs_multi_observer_invoke( user_it.trs[pivot_field] = it->trs[0]; user_it.sources[pivot_field] = it->sources[0]; ECS_CONST_CAST(int16_t*, user_it.columns)[pivot_field] = - it->sources[0] ? -1 : it->columns[0]; + (it->sources[0] || !(user_it.set_fields & (1llu << pivot_field))) + ? -1 : it->columns[0]; user_it.term_index = pivot_term; user_it.ctx = o->ctx; @@ -18060,12 +17837,6 @@ void flecs_observer_set_disable_bit( } } -/** - * @file on_delete.c - * @brief Implementation of OnDelete/OnDeleteTarget traits. - */ - - static void flecs_marked_id_push( ecs_world_t *world, @@ -18837,15 +18608,6 @@ void ecs_remove_all( flecs_journal_end(); } -/** - * @file os_api.c - * @brief Operating system abstraction API. - * - * The OS API implements an overridable interface for implementing functions - * that are operating system specific, in addition to a number of hooks which - * allow for customization by the user, like logging. - */ - #include void ecs_os_api_impl(ecs_os_api_t *api); @@ -19510,21 +19272,6 @@ const char* ecs_os_strerror(int err) { # endif } -/** - * @file poly.c - * @brief Functions for managing poly objects. - * - * The poly framework makes it possible to generalize common functionality for - * different kinds of API objects, as well as improve type safety checks. Poly - * objects have a header that identifies what kind of object it is. This can - * then be used to discover a set of "mixins" implemented by the type. - * - * Mixins are like a vtable, but for members. Each type populates the table with - * offsets to the members that correspond with the mixin. If an entry in the - * mixin table is not set, the type does not support the mixin. - */ - - static const char* mixin_kind_str[] = { [EcsMixinWorld] = "world", [EcsMixinEntity] = "entity", @@ -19751,14 +19498,6 @@ flecs_poly_dtor_t* flecs_get_dtor( return (flecs_poly_dtor_t*)assert_mixin(poly, EcsMixinDtor); } -/** - * @file ref.c - * @brief Ref API. - * - * Refs provide faster access to components than get. - */ - - ecs_ref_t ecs_ref_init_id( const ecs_world_t *world, ecs_entity_t entity, @@ -19873,16 +19612,6 @@ void* ecs_ref_get_id( return NULL; } -/** - * @file search.c - * @brief Search functions to find (component) ids in table types. - * - * Search functions are used to find the column index of a (component) id in a - * table. Additionally, search functions implement the logic for finding a - * component id by following a relationship upwards. - */ - - static int32_t flecs_table_search_relation( const ecs_world_t *world, @@ -19942,6 +19671,108 @@ int32_t flecs_table_offset_search( return -1; } +static +bool flecs_component_inherits_from( + const ecs_world_t *world, + ecs_entity_t component, + ecs_entity_t base, + int32_t depth) +{ + if (depth >= FLECS_DAG_DEPTH_MAX) { + return false; + } + + ecs_record_t *r = flecs_entities_get(world, component); + if (!r) { + return false; + } + + ecs_table_t *table = r->table; + if (!table || !(table->flags & EcsTableHasIsA)) { + return false; + } + + const ecs_table_record_t *tr_isa = flecs_component_get_table( + world->cr_isa_wildcard, table); + if (!tr_isa) { + return false; + } + + ecs_id_t *ids = table->type.array; + int32_t i = tr_isa->index, end = i + tr_isa->count; + for (; i < end; i ++) { + ecs_entity_t b = ecs_pair_second(world, ids[i]); + if (b == base) { + return true; + } + if (flecs_component_inherits_from(world, b, base, depth + 1)) { + return true; + } + } + + return false; +} + +static +bool flecs_id_match_inherited( + const ecs_world_t *world, + ecs_id_t type_id, + ecs_id_t id) +{ + if (ECS_IS_PAIR(id)) { + if (!ECS_IS_PAIR(type_id)) { + return false; + } + ecs_entity_t tgt = ECS_PAIR_SECOND(id); + if ((tgt != EcsWildcard) && (tgt != EcsAny)) { + if (ECS_PAIR_SECOND(type_id) != tgt) { + return false; + } + } + return flecs_component_inherits_from(world, + ecs_pair_first(world, type_id), ecs_pair_first(world, id), 0); + } else { + if (type_id & ECS_ID_FLAGS_MASK) { + return false; + } + return flecs_component_inherits_from(world, type_id, id, 0); + } +} + +static +int32_t flecs_table_offset_search_w_inherited( + const ecs_world_t *world, + const ecs_table_t *table, + int32_t offset, + ecs_id_t id, + ecs_id_t *id_out) +{ + ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL); + + bool check_inherited = world && world->cr_isa_wildcard && + flecs_table_cache_count(&world->cr_isa_wildcard->cache); + + ecs_id_t *ids = table->type.array; + int32_t count = table->type.count; + while (offset < count) { + ecs_id_t type_id = ids[offset ++]; + if (ecs_id_match(type_id, id)) { + if (id_out) { + id_out[0] = type_id; + } + return offset - 1; + } + if (check_inherited && flecs_id_match_inherited(world, type_id, id)) { + if (id_out) { + id_out[0] = type_id; + } + return offset - 1; + } + } + + return -1; +} + bool flecs_type_can_inherit_id( const ecs_world_t *world, const ecs_table_t *table, @@ -20227,7 +20058,7 @@ int32_t ecs_search_offset( return ecs_search(world, table, id, id_out); } - return flecs_table_offset_search(table, offset, id, id_out); + return flecs_table_offset_search_w_inherited(world, table, offset, id, id_out); } static @@ -20318,24 +20149,6 @@ int32_t flecs_relation_depth( return flecs_relation_depth_walk(world, cr, table, table); } -/** - * @file stage.c - * @brief Staging implementation. - * - * A stage is an object that can be used to temporarily store mutations to a - * world while a world is in readonly mode. ECS operations that are invoked on - * a stage are stored in a command buffer, which is flushed during sync points, - * or manually by the user. - * - * Stages contain additional state to enable other API functionality without - * having to mutate the world, such as setting the current scope, and allocators - * that are local to a stage. - * - * In a multithreaded application, each thread has its own stage which allows - * threads to insert mutations without having to lock the administration. - */ - - static void flecs_stage_merge( ecs_world_t *world) @@ -20804,12 +20617,6 @@ bool ecs_is_defer_suspended( return false; } -/** - * @file tree_spawner.c - * @brief Data structure used to speed up the creation of hierarchies. - */ - - static void flecs_tree_spawner_release_tables( ecs_vec_t *v) @@ -21159,12 +20966,6 @@ void flecs_bootstrap_spawner( EcsOnInstantiate, EcsDontInherit); } -/** - * @file type_info.c - * @brief Component metadata and lifecycle hooks. - */ - - #ifdef FLECS_DEBUG static void flecs_type_info_mark_in_use( @@ -21618,7 +21419,6 @@ void ecs_set_hooks_id( "cannot specify both equals hook and illegal flag", flecs_errstr(ecs_get_path(world, component))); - flecs_stage_from_world(&world); bool in_use = ecs_id_in_use(world, component) || @@ -22064,12 +21864,6 @@ const ecs_type_info_t* ecs_get_type_info( return NULL; } -/** - * @file value.c - * @brief Utility functions to work with non-trivial pointers of user types. - */ - - int ecs_value_init_w_type_info( const ecs_world_t *world, const ecs_type_info_t *ti, @@ -22277,11 +22071,6 @@ int ecs_value_move_ctor( return -1; } -/** - * @file world.c - * @brief World-level API. - */ - #include /* Id flags */ @@ -24386,12 +24175,6 @@ bool flecs_component_is_delete_locked( } #endif -/** - * @file addons/alerts.c - * @brief Alerts addon. - */ - - #ifdef FLECS_ALERTS ECS_COMPONENT_DECLARE(FlecsAlerts); @@ -25174,12 +24957,6 @@ void FlecsAlertsImport(ecs_world_t *world) { #endif -/** - * @file addons/app.c - * @brief App addon. - */ - - #ifdef FLECS_APP static @@ -25334,12 +25111,6 @@ int ecs_app_set_frame_action( #endif -/** - * @file addons/doc.c - * @brief Doc addon. - */ - - #ifdef FLECS_DOC static ECS_COPY(EcsDocDescription, dst, src, { @@ -25535,12 +25306,6 @@ void FlecsDocImport( #endif -/** - * @file addons/flecs_cpp.c - * @brief Utilities for C++ addon. - */ - - /* Utilities for C++ API */ #ifdef FLECS_CPP @@ -26225,12 +25990,6 @@ ecs_entity_t ecs_cpp_new( #endif -/** - * @file addons/journal.c - * @brief Journal addon. - */ - - #ifdef FLECS_JOURNAL static @@ -26382,12 +26141,6 @@ int flecs_journal_get_counter(void) { #endif -/** - * @file addons/log.c - * @brief Log addon. - */ - - #ifdef FLECS_LOG static char *flecs_log_last_err = NULL; @@ -26674,7 +26427,6 @@ void ecs_log_( va_end(args); } - void ecs_log_push_( int32_t level) { @@ -26994,7 +26746,6 @@ void ecs_parser_errorv_( (void)args; } - void ecs_parser_warning_( const char *name, const char *expr, @@ -27103,12 +26854,6 @@ int ecs_log_last_error(void) return result; } -/** - * @file addons/metrics.c - * @brief Metrics addon. - */ - - #ifdef FLECS_METRICS /* Public components */ @@ -28057,15 +27802,8 @@ void FlecsMetricsImport(ecs_world_t *world) { #endif -/** - * @file addons/module.c - * @brief Module addon. - */ - - #ifdef FLECS_MODULE - char* flecs_module_path_from_c( const char *c_name) { @@ -28288,21 +28026,9 @@ ecs_entity_t ecs_module_init( #endif -/** - * @file addons/rest.c - * @brief Rest addon. - */ - - -/** - * @file addons/pipeline/pipeline.h - * @brief Internal functions/types for pipeline addon. - */ - #ifndef FLECS_PIPELINE_PRIVATE_H #define FLECS_PIPELINE_PRIVATE_H - /** Instruction data for pipeline. * This type is the element type in the "ops" vector of a pipeline. */ typedef struct ecs_pipeline_op_t { @@ -28376,15 +28102,9 @@ void flecs_wait_for_sync( #endif -/** - * @file addons/json/json.h - * @brief Internal functions for JSON addon. - */ - #ifndef FLECS_JSON_PRIVATE_H #define FLECS_JSON_PRIVATE_H - #ifdef FLECS_JSON /* Deserialize from JSON */ @@ -28690,7 +28410,6 @@ void flecs_json_assemble_output( #endif /* FLECS_JSON_PRIVATE_H */ - #ifdef FLECS_REST /* Retain captured commands for one minute at 60 FPS */ @@ -30960,22 +30679,11 @@ void FlecsRestImport( #endif -/** - * @file addons/timer.c - * @brief Timer addon. - */ - -/** - * @file addons/system/system.h - * @brief Internal types and functions for system addon. - */ - #ifndef FLECS_SYSTEM_PRIVATE_H #define FLECS_SYSTEM_PRIVATE_H #ifdef FLECS_SYSTEM - #define ecs_system_t_magic (0x65637383) #define ecs_system_t_tag EcsSystem @@ -30996,7 +30704,6 @@ ecs_entity_t flecs_run_system( #endif - #ifdef FLECS_TIMER static @@ -31326,12 +31033,6 @@ void FlecsTimerImport( #endif -/** - * @file addons/units.c - * @brief Units addon. - */ - - #ifdef FLECS_UNITS void FlecsUnitsImport( @@ -31519,7 +31220,6 @@ void FlecsUnitsImport( .kind = EcsF32 }); - EcsNanoSeconds = ecs_unit_init(world, &(ecs_unit_desc_t){ .entity = ecs_entity(world, { .name = "NanoSeconds" }), .quantity = EcsDuration, @@ -32313,14 +32013,6 @@ void FlecsUnitsImport( #endif -/** - * @file datastructures/allocator.c - * @brief Allocator for any size. - * - * Allocators create a block allocator for each requested size. - */ - - #ifndef FLECS_USE_OS_ALLOC static ecs_size_t flecs_allocator_size( @@ -32489,15 +32181,6 @@ void flecs_free( #endif -/** - * @file datastructures/bitset.c - * @brief Bitset data structure. - * - * Simple bitset implementation. The bitset allows for storage of arbitrary - * numbers of bits. - */ - - static void flecs_bitset_ensure_size( ecs_bitset_t *bs, @@ -32615,15 +32298,6 @@ void flecs_bitset_swap( return; } -/** - * @file datastructures/block_allocator.c - * @brief Block allocator. - * - * A block allocator is an allocator for a fixed size that allocates blocks of - * memory with N elements of the requested size. - */ - - // #ifdef FLECS_SANITIZE // #define FLECS_MEMSET_UNINITIALIZED // #endif @@ -32978,7 +32652,6 @@ void* flecs_bdup( uint64_t hash=wyhash(s.c_str(), s.size(), 0, wyp_); */ - #ifndef WYHASH_CONDOM //protections that produce different results: //1: normal valid behavior @@ -33117,16 +32790,6 @@ uint64_t flecs_hash( return wyhash(data, flecs_ito(size_t, length), 0, wyp_); } -/** - * @file datastructures/hashmap.c - * @brief Hashmap data structure. - * - * The hashmap data structure is built on top of the map data structure. Where - * the map data structure can only work with 64bit key values, the hashmap can - * hash keys of any size, and handles collisions between hashes. - */ - - static int32_t flecs_hashmap_find_key( const ecs_hashmap_t *map, @@ -33401,14 +33064,6 @@ void* flecs_hashmap_next_( return ecs_vec_get(&bucket->values, value_size, index); } -/** - * @file datastructures/map.c - * @brief Map data structure. - * - * Map data structure for 64-bit keys and 64-bit payload. - */ - - /* The ratio used to determine whether the map should rehash. If * (element_count * ECS_LOAD_FACTOR) > bucket_count, bucket count is increased. */ #define ECS_LOAD_FACTOR (12) @@ -33863,12 +33518,6 @@ void ecs_map_copy( } } -/** - * @file datastructures/name_index.c - * @brief Data structure for resolving 64bit keys by string (name). - */ - - static uint64_t flecs_name_index_hash( const void *ptr) @@ -34090,12 +33739,6 @@ void flecs_name_index_ensure( return; } -/** - * @file datastructures/sparse.c - * @brief Sparse set data structure. - */ - - /* Utility to get a pointer to the payload */ #define DATA(array, size, offset) (ECS_OFFSET(array, size * offset)) @@ -34808,19 +34451,6 @@ void* ecs_sparse_get( return flecs_sparse_get(sparse, elem_size, id); } -/** - * @file datastructures/stack_allocator.c - * @brief Stack allocator. - * - * The stack allocator enables pushing and popping values to a stack, and has - * a lower overhead when compared to block allocators. A stack allocator is a - * good fit for small temporary allocations. - * - * The stack allocator allocates memory in pages. If the requested size of an - * allocation exceeds the page size, a regular allocator is used instead. - */ - - int64_t ecs_stack_allocator_alloc_count = 0; int64_t ecs_stack_allocator_free_count = 0; @@ -35019,20 +34649,6 @@ void flecs_stack_fini( } } -/** - * @file datastructures/strbuf.c - * @brief Utility for constructing strings. - * - * A buffer builds up a string by appending to a contiguous buffer. For small - * strings, a stack-allocated buffer is used. When the string outgrows the small - * buffer, a heap-allocated buffer is used instead, which is grown by doubling - * its size as needed. When an application calls ecs_strbuf_get, the final - * string is returned and the buffer is reset. - * - * The functionality provided by strbuf is similar to std::stringstream. - */ - - #include // isnan, isinf /** @@ -35614,12 +35230,6 @@ int32_t ecs_strbuf_written( return b->length; } -/** - * @file datastructures/vec.c - * @brief Vector with allocator support. - */ - - void ecs_vec_init( ecs_allocator_t *allocator, ecs_vec_t *v, @@ -36124,12 +35734,6 @@ void* ecs_vec_first( return v->array; } -/** - * @file query/api.c - * @brief User facing API for queries. - */ - - /* Placeholder arrays for queries that only have the $this variable */ ecs_query_var_t flecs_this_array = { .kind = EcsVarTable, @@ -36855,12 +36459,6 @@ const ecs_map_t* ecs_query_get_groups( return NULL; } -/** - * @file query/util.c - * @brief Query utilities. - */ - - const char* flecs_query_op_str( uint16_t kind) { @@ -37581,9 +37179,11 @@ void flecs_query_apply_iter_flags( ecs_iter_t *it, const ecs_query_t *query) { - ECS_BIT_COND(it->flags, EcsIterHasCondSet, + ECS_BIT_COND(it->flags, EcsIterHasCondSet, ECS_BIT_IS_SET(query->flags, EcsQueryHasCondSet)); ECS_BIT_COND(it->flags, EcsIterNoData, query->data_fields == 0); + ECS_BIT_COND(it->flags, EcsIterComponentInheritance, + ECS_BIT_IS_SET(query->flags, EcsQueryHasComponentInheritance)); } void flecs_query_reclaim( @@ -37609,12 +37209,6 @@ ecs_id_t flecs_query_iter_set_id( return id; } -/** - * @file query/validator.c - * @brief Validate and finalize queries. - */ - - #ifdef FLECS_QUERY_DSL #endif @@ -38268,7 +37862,6 @@ int flecs_term_finalize( ecs_term_ref_t *src = &term->src; ecs_term_ref_t *first = &term->first; ecs_term_ref_t *second = &term->second; - ecs_flags64_t first_flags = ECS_TERM_REF_FLAGS(first); ecs_flags64_t second_flags = ECS_TERM_REF_FLAGS(second); if (first->name && (first->id & ~EcsTermRefFlags)) { @@ -38447,10 +38040,9 @@ int flecs_term_finalize( } if (first_entity && !ecs_term_match_0(term)) { - bool first_is_self = (first_flags & EcsTraverseFlags) == EcsSelf; ecs_record_t *first_record = flecs_entities_get(world, first_entity); ecs_table_t *first_table = first_record ? first_record->table : NULL; - + bool first_can_isa = false; if (first_table) { first_can_isa = (first_table->flags & EcsTableHasIsA) != 0; @@ -38459,18 +38051,14 @@ int flecs_term_finalize( } } - /* Only enable inheritance for ids which are inherited from at the time - * of query creation. To force component inheritance to be evaluated, - * an application can explicitly set traversal flags. */ - if (flecs_components_get(world, ecs_pair(EcsIsA, first->id)) || + if (flecs_components_get(world, ecs_pair(EcsIsA, first->id)) || (cr_flags & EcsIdInheritable) || first_can_isa) { - if (!first_is_self) { - term->flags_ |= EcsTermIdInherited; - } + term->flags_ |= EcsTermIdInherited; } else { #ifdef FLECS_DEBUG - if (!first_is_self) { + ecs_flags64_t first_flags = ECS_TERM_REF_FLAGS(first); + if ((first_flags & EcsTraverseFlags) != EcsSelf) { ecs_query_impl_t *q = flecs_query_impl(ctx->query); if (q) { ECS_TERMSET_SET(q->final_terms, 1u << ctx->term_index); @@ -38593,7 +38181,6 @@ int flecs_term_finalize( if (term->flags_ & EcsTermIdInherited) { trivial_term = false; - cacheable_term = false; } if (term->flags_ & EcsTermReflexive) { @@ -38767,6 +38354,10 @@ int flecs_query_finalize_terms( return -1; } + if (term->flags_ & EcsTermIdInherited) { + q->flags |= EcsQueryHasComponentInheritance; + } + if (term->flags_ & EcsTermNonFragmentingChildOf) { if (!i) { /* If the first term is a ChildOf pair, the query result should @@ -39325,6 +38916,7 @@ bool flecs_query_finalize_simple( /* Populate terms */ bool has_this = false, has_only_this = true; int8_t cacheable_count = 0, trivial_count = 0, up_count = 0; + int8_t inherited_count = 0; for (i = 0; i < term_count; i ++) { ecs_term_t *term = &q->terms[i]; ecs_id_t id = term->id; @@ -39439,7 +39031,9 @@ bool flecs_query_finalize_simple( if (flecs_components_get(world, ecs_pair(EcsIsA, first)) != NULL) { term->flags_ |= EcsTermIdInherited; - cacheable = false; trivial = false; + q->flags |= EcsQueryHasComponentInheritance; + trivial = false; + inherited_count ++; } if (cacheable) { @@ -39478,8 +39072,14 @@ bool flecs_query_finalize_simple( q->flags |= EcsQueryHasCacheable; } - if (cacheable_count == term_count && trivial_count == term_count) { - q->flags |= EcsQueryIsCacheable|EcsQueryIsTrivial; + if (cacheable_count == term_count && + (trivial_count + inherited_count) == term_count) + { + q->flags |= EcsQueryIsCacheable; + } + + if (trivial_count == term_count) { + q->flags |= EcsQueryIsTrivial; } if (!up_count) { @@ -39617,23 +39217,6 @@ int flecs_query_finalize_query( return -1; } -/** - * @file storage/component_index.c - * @brief Index for looking up tables by component id. - * - * A component record stores the administration for an in-use (component) id, - * that is an id that has been used in tables. - * - * A component 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). - * - * Component records are used in lots of scenarios, like uncached queries, or for - * getting a component array/component for an entity. - */ - - static ecs_id_record_elem_t* flecs_component_elem( ecs_component_record_t *head, @@ -40865,11 +40448,6 @@ void flecs_component_update_childof_depth( flecs_component_update_childof_w_depth(world, cr, new_depth); } -/** - * @file storage/entity_index.c - * @brief Entity index. - */ - #include ecs_entity_index_page_t* flecs_entity_index_ensure_page( @@ -41464,12 +41042,6 @@ const uint64_t* flecs_entity_index_ids( return ecs_vec_get_t(&index->dense, uint64_t, 1); } -/** - * @file storage/non_fragmenting_childof.c - * @brief Non-fragmenting ChildOf storage. - */ - - static void flecs_add_non_fragmenting_child_to_table( ecs_world_t *world, @@ -41919,12 +41491,6 @@ void flecs_bootstrap_parent_component( ecs_add_pair(world, ecs_id(EcsParent), EcsOnInstantiate, EcsDontInherit); } -/** - * @file storage/ordered_children.c - * @brief Ordered children storage. - */ - - void flecs_ordered_children_init( ecs_world_t *world, ecs_component_record_t *cr) @@ -42141,12 +41707,6 @@ void flecs_ordered_children_reorder( return; } -/** - * @file storage/sparse_storage.c - * @brief Sparse component storage. - */ - - bool flecs_component_sparse_has( ecs_component_record_t *cr, ecs_entity_t entity) @@ -42685,26 +42245,6 @@ void* flecs_component_sparse_emplace( return ptr; } -/** - * @file storage/table.c - * @brief Table storage implementation. - * - * Tables are the data structure that store the component data. Tables have - * columns for each component in the table, and rows for each entity stored in - * the table. Once created, the component list for a table doesn't change, but - * entities can move from one table to another. - * - * Each table has a type, which is a vector with the (component) ids in the - * table. The vector is sorted by id, which ensures that there can be only one - * table for each unique combination of components. - * - * Not all ids in a table have to be components. Tags are ids that have no - * data type associated with them, and as a result don't need to be explicitly - * stored beyond an element in the table type. A column_map member maps between - * type indices and column indices. - */ - - /* Table sanity check to detect storage issues. Only enabled in SANITIZE mode as * this can severely slow down many ECS operations. */ #ifdef FLECS_SANITIZE @@ -43216,9 +42756,14 @@ void flecs_table_emit( ecs_table_t *table, ecs_entity_t event) { + ecs_type_t *ids = &table->type; + if (table->_->extended_type) { + ids = table->_->extended_type; + } + ecs_defer_begin(world); flecs_emit(world, world, &(ecs_event_desc_t) { - .ids = &table->type, + .ids = ids, .event = event, .table = table, .flags = EcsEventTableOnly, @@ -43227,6 +42772,225 @@ void flecs_table_emit( ecs_defer_end(world); } +static +bool flecs_table_register_inherited_for_base( + ecs_world_t *world, + ecs_table_t *table, + int32_t type_index, + int16_t column, + ecs_id_t base_id, + ecs_vec_t *inherited, + ecs_vec_t *inherited_wild) +{ + ecs_component_record_t *cr = flecs_components_ensure(world, base_id); + + ecs_table_record_t *existing = ECS_CONST_CAST(ecs_table_record_t*, + flecs_component_get_table(cr, table)); + if (existing) { + if (existing->index == type_index) { + return false; + } + if (ecs_id_match(table->type.array[existing->index], base_id)) { + return false; + } + + existing->count ++; + existing->index = flecs_ito(int16_t, type_index); + existing->column = column; + return true; + } + + ecs_table_record_t *tr = flecs_walloc_t(world, ecs_table_record_t); + tr->index = flecs_ito(int16_t, type_index); + tr->column = column; + tr->count = 1; + + ecs_table_cache_insert(&cr->cache, table, &tr->hdr); + flecs_component_claim(world, cr); + + ecs_vec_t *dst = ecs_id_is_wildcard(base_id) ? inherited_wild : inherited; + *ecs_vec_append_t(&world->allocator, dst, ecs_table_record_t*) = tr; + + if (base_id < FLECS_HI_COMPONENT_ID) { + world->non_trivial_lookup[base_id] |= EcsNonTrivialIdInherit; + } + + return true; +} + +static +void flecs_table_register_inherited_bases( + ecs_world_t *world, + ecs_table_t *table, + int32_t type_index, + int16_t column, + ecs_entity_t rel, + ecs_entity_t tgt, + ecs_vec_t *inherited, + ecs_vec_t *inherited_wild) +{ + ecs_record_t *r = flecs_entities_get(world, rel); + if (!r) { + return; + } + + ecs_table_t *src = r->table; + if (!src || !(src->flags & EcsTableHasIsA)) { + return; + } + + const ecs_table_record_t *tr_isa = flecs_component_get_table( + world->cr_isa_wildcard, src); + if (!tr_isa) { + return; + } + + ecs_id_t *ids = src->type.array; + int32_t i = tr_isa->index, end = i + tr_isa->count; + for (; i < end; i ++) { + ecs_entity_t base = ecs_pair_second(world, ids[i]); + if (!base) { + continue; + } + + ecs_id_t base_id = tgt ? ecs_pair(base, tgt) : base; + bool recurse = flecs_table_register_inherited_for_base( + world, table, type_index, column, base_id, inherited, + inherited_wild); + + if (tgt) { + if (flecs_table_register_inherited_for_base(world, table, + type_index, column, ecs_pair(base, EcsWildcard), inherited, + inherited_wild)) + { + recurse = true; + } + } + + if (recurse) { + flecs_table_register_inherited_bases(world, table, type_index, + column, base, tgt, inherited, inherited_wild); + } + } +} + +static +void flecs_table_register_inherited( + ecs_world_t *world, + ecs_table_t *table) +{ + ecs_component_record_t *cr_isa = world->cr_isa_wildcard; + if (!cr_isa || !flecs_table_cache_count(&cr_isa->cache)) { + return; + } + + ecs_vec_t inherited, inherited_wild; + ecs_vec_init_t(&world->allocator, &inherited, ecs_table_record_t*, 0); + ecs_vec_init_t(&world->allocator, &inherited_wild, ecs_table_record_t*, 0); + + ecs_type_t type = table->type; + int32_t ti, count = type.count; + for (ti = count - 1; ti >= 0; ti --) { + ecs_id_t id = type.array[ti]; + + ecs_entity_t rel, tgt; + if (id & ECS_ID_FLAGS_MASK) { + if (!ECS_IS_PAIR(id)) { + continue; + } + rel = ecs_pair_first(world, id); + tgt = ECS_PAIR_SECOND(id); + } else { + rel = id; + tgt = 0; + } + + int16_t column = table->_->records[ti].column; + flecs_table_register_inherited_bases( + world, table, ti, column, rel, tgt, &inherited, &inherited_wild); + } + + int32_t nw = ecs_vec_count(&inherited); + int32_t ww = ecs_vec_count(&inherited_wild); + int32_t n = nw + ww; + if (n) { + /* Inherited records are inserted right after the table's own id records + * so that the wildcard records remain at the end. Non-wildcard + * inherited records (the base component ids) are placed first, followed + * by the existing wildcard records and the inherited wildcard records. + * This ensures the extended_type array (component ids + base ids) + * mirrors the order of the records array and excludes wildcards. */ + int32_t i, type_count = type.count; + int32_t old = table->_->record_count; + int32_t aux = old - type_count; + ecs_table_record_t *old_records = table->_->records; + ecs_table_record_t *records = flecs_walloc_n( + world, ecs_table_record_t, old + n); + + ecs_table_record_t **nw_records = ecs_vec_first_t( + &inherited, ecs_table_record_t*); + ecs_table_record_t **ww_records = ecs_vec_first_t( + &inherited_wild, ecs_table_record_t*); + + ecs_os_memcpy_n(records, old_records, ecs_table_record_t, type_count); + + for (i = 0; i < nw; i ++) { + records[type_count + i] = *nw_records[i]; + } + + ecs_os_memcpy_n(&records[type_count + nw], &old_records[type_count], + ecs_table_record_t, aux); + + for (i = 0; i < ww; i ++) { + records[type_count + nw + aux + i] = *ww_records[i]; + } + + table->_->records = records; + table->_->record_count = flecs_ito(int16_t, old + n); + + for (i = 0; i < old + n; i ++) { + ecs_table_record_t *tr = &records[i]; + ecs_table_cache_replace(&tr->hdr.cr->cache, table, &tr->hdr); + + /* Propagate table event flags for inherited base components, so + * that OnTableCreate/OnTableDelete events are emitted for tables + * with derived components (used to notify query caches). */ + table->flags |= tr->hdr.cr->flags & + (EcsIdHasOnTableCreate | EcsIdHasOnTableDelete); + } + + /* Build extended type with component ids + non-wildcard base ids */ + if (nw) { + int32_t et_count = type_count + nw; + ecs_type_t *et = flecs_walloc_t(world, ecs_type_t); + et->count = et_count; + et->array = flecs_walloc_n(world, ecs_id_t, et_count); + for (i = 0; i < type_count; i ++) { + et->array[i] = type.array[i]; + } + for (i = 0; i < nw; i ++) { + ecs_id_t base_id = records[type_count + i].hdr.cr->id; + et->array[type_count + i] = base_id; + table->bloom_filter = flecs_table_bloom_filter_add( + table->bloom_filter, base_id); + } + table->_->extended_type = et; + } + + for (i = 0; i < nw; i ++) { + flecs_wfree_t(world, ecs_table_record_t, nw_records[i]); + } + for (i = 0; i < ww; i ++) { + flecs_wfree_t(world, ecs_table_record_t, ww_records[i]); + } + + flecs_wfree_n(world, ecs_table_record_t, old, old_records); + } + + ecs_vec_fini_t(&world->allocator, &inherited, ecs_table_record_t*); + ecs_vec_fini_t(&world->allocator, &inherited_wild, ecs_table_record_t*); +} + /* Main table initialization function */ void flecs_table_init( ecs_world_t *world, @@ -43525,8 +43289,11 @@ void flecs_table_init( } } - /* If table has IsA pairs, create overrides cache */ + flecs_table_register_inherited(world, table); + if (isa_tr) { + isa_tr = ECS_CONST_CAST(ecs_table_record_t*, + flecs_component_get_table(world->cr_isa_wildcard, table)); flecs_table_init_overrides(world, table, isa_tr); } @@ -43998,6 +43765,12 @@ void flecs_table_fini( ecs_os_free(table->component_map); flecs_table_records_unregister(world, table); + if (table->_->extended_type) { + flecs_wfree_n(world, ecs_id_t, table->_->extended_type->count, + table->_->extended_type->array); + flecs_wfree_t(world, ecs_type_t, table->_->extended_type); + } + /* Update counters */ world->info.table_count --; world->info.table_delete_total ++; @@ -45207,7 +44980,6 @@ bool flecs_table_bloom_filter_test( return (table->bloom_filter & filter) == filter; } - ecs_table_records_t flecs_table_records( ecs_table_t* table) { @@ -45331,7 +45103,6 @@ ecs_hashmap_t* flecs_table_get_name_index( return NULL; } - /* -- Public API -- */ void ecs_table_lock( @@ -45659,20 +45430,6 @@ char* ecs_table_str( } } -/** - * @file storage/table_cache.c - * @brief Data structure for fast table iteration/lookups. - * - * A table cache is a data structure that provides constant time operations for - * insertion and removal of tables, and for 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 component record - * administration. Cached queries store a table cache with matched tables. - */ - - static void flecs_table_cache_list_remove( ecs_table_cache_t *cache, @@ -45888,17 +45645,6 @@ const ecs_table_cache_hdr_t* flecs_table_cache_next_( return next; } -/** - * @file storage/table_graph.c - * @brief Data structure to speed up table transitions. - * - * The table graph is used to speed up finding tables in add/remove operations. - * For example, if component C is added to an entity in table [A, B], the entity - * must be moved to table [A, B, C]. The graph speeds this process up with an - * edge for component C that connects [A, B] to [A, B, C]. - */ - - ecs_type_t flecs_type_copy( ecs_world_t *world, const ecs_type_t *src); @@ -47462,51 +47208,10 @@ ecs_table_t* ecs_table_find( return flecs_table_ensure(world, &type, false, NULL); } -/** - * @file addons/http/http.c - * @brief HTTP addon. - * - * This is a heavily modified version of the EmbeddableWebServer (see copyright - * below). This version has been stripped from everything not strictly necessary - * for receiving/replying to simple HTTP requests, and has been modified to use - * the Flecs OS API. - * - * EmbeddableWebServer Copyright (c) 2016, 2019, 2020 Forrest Heller, and - * CONTRIBUTORS (see below) - All rights reserved. - * - * CONTRIBUTORS: - * Martin Pulec - bug fixes, warning fixes, IPv6 support - * Daniel Barry - bug fix (ifa_addr != NULL) - * - * Released under the BSD 2-clause license: - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. THIS SOFTWARE IS - * PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN - * NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - #include #ifdef FLECS_HTTP -/** - * @file addons/http/http.h - * @brief HTTP addon internals. - */ - #ifndef FLECS_HTTP_IMPL_H #define FLECS_HTTP_IMPL_H @@ -47695,7 +47400,6 @@ typedef struct { #endif - /* Global statistics */ int64_t ecs_http_request_received_count = 0; int64_t ecs_http_request_invalid_count = 0; @@ -49279,12 +48983,6 @@ void* ecs_http_server_ctx( #endif -/** - * @file addons/json/deserialize.c - * @brief Deserialize JSON strings into entities and worlds. - */ - - #ifdef FLECS_JSON typedef struct { @@ -49513,7 +49211,6 @@ const char* flecs_json_deser_tags( goto error; } - end: return json; error: @@ -50172,28 +49869,12 @@ const char* ecs_world_from_json_file( #endif -/** - * @file addons/json/deserialize_value.c - * @brief Deserialize JSON strings into (component) values. - */ - -/** - * @file addons/script/script.h - * @brief Flecs script implementation. - */ - #ifndef FLECS_SCRIPT_PRIVATE_H #define FLECS_SCRIPT_PRIVATE_H -/** - * @file addons/parser/parser.h - * @brief Parser addon. - */ - #ifndef FLECS_PARSER_H #define FLECS_PARSER_H - typedef struct ecs_script_impl_t ecs_script_impl_t; typedef struct ecs_script_scope_t ecs_script_scope_t; @@ -50227,11 +49908,6 @@ typedef struct ecs_parser_t { ecs_term_ref_t *extra_args; } ecs_parser_t; -/** - * @file addons/parser/tokenizer.h - * @brief Parser tokenizer. - */ - #ifndef FLECS_PARSER_TOKENIZER_H #define FLECS_PARSER_TOKENIZER_H @@ -50342,10 +50018,8 @@ const char* flecs_tokenizer_identifier( #endif - #endif - #ifdef FLECS_SCRIPT typedef struct ecs_script_entity_t ecs_script_entity_t; @@ -50374,11 +50048,6 @@ typedef struct ecs_function_calldata_t { void *ctx; } ecs_function_calldata_t; -/** - * @file addons/script/ast.h - * @brief Script AST. - */ - #ifndef FLECS_SCRIPT_AST_H #define FLECS_SCRIPT_AST_H @@ -50646,26 +50315,15 @@ ecs_script_function_node_t* flecs_script_insert_function( #endif -/** - * @file addons/script/expr/expr.h - * @brief Script expression support. - */ - #ifndef FLECS_EXPR_SCRIPT_H #define FLECS_EXPR_SCRIPT_H -/** - * @file addons/script/expr/stack.h - * @brief Script expression stack. - */ - #ifndef FLECS_SCRIPT_EXPR_STACK_H #define FLECS_SCRIPT_EXPR_STACK_H #define FLECS_EXPR_STACK_MAX (256) #define FLECS_EXPR_SMALL_DATA_SIZE (24) - typedef union ecs_expr_small_value_t { bool bool_; char char_; @@ -50726,11 +50384,6 @@ void flecs_expr_stack_pop( #endif -/** - * @file addons/script/expr/ast.h - * @brief Script expression AST. - */ - #ifndef FLECS_SCRIPT_EXPR_AST_H #define FLECS_SCRIPT_EXPR_AST_H @@ -50959,11 +50612,6 @@ ecs_expr_cast_t* flecs_expr_cast( #endif -/** - * @file addons/script/expr/visit.h - * @brief Script expression AST visitor utilities. - */ - #ifndef FLECS_EXPR_SCRIPT_VISIT_H #define FLECS_EXPR_SCRIPT_VISIT_H @@ -50995,7 +50643,6 @@ void flecs_expr_visit_free( #endif - int flecs_value_copy_to( ecs_world_t *world, ecs_value_t *dst, @@ -51064,11 +50711,6 @@ int flecs_expr_initializer_validate_assign( #endif -/** - * @file addons/script/visit.h - * @brief Script AST visitor utilities. - */ - #ifndef FLECS_SCRIPT_VISIT_H #define FLECS_SCRIPT_VISIT_H @@ -51165,11 +50807,6 @@ int32_t ecs_script_node_line_number_( #endif -/** - * @file addons/script/visit_eval.h - * @brief Script evaluation visitor. - */ - #ifndef FLECS_SCRIPT_VISIT_EVAL_H #define FLECS_SCRIPT_VISIT_EVAL_H @@ -51270,11 +50907,6 @@ ecs_entity_t flecs_script_find_entity_action( #endif -/** - * @file addons/script/template.h - * @brief Script template implementation. - */ - #ifndef FLECS_SCRIPT_TEMPLATE_H #define FLECS_SCRIPT_TEMPLATE_H @@ -51334,7 +50966,6 @@ void flecs_script_template_import( #endif - struct ecs_script_runtime_t { ecs_allocator_t allocator; ecs_expr_stack_t expr_stack; @@ -51423,7 +51054,6 @@ void FlecsScriptMathPerlinImport( #endif // FLECS_SCRIPT #endif // FLECS_SCRIPT_PRIVATE_H - #ifdef FLECS_JSON const char* ecs_ptr_from_json( @@ -51601,12 +51231,6 @@ const char* ecs_ptr_from_json( #endif -/** - * @file addons/json/json.c - * @brief JSON serializer utilities. - */ - - #ifdef FLECS_JSON static @@ -52347,12 +51971,6 @@ void flecs_json_assemble_output( } #endif -/** - * @file addons/json/serialize_entity.c - * @brief Serialize single entity. - */ - - #ifdef FLECS_JSON int flecs_entity_to_json_buf( @@ -52473,12 +52091,6 @@ char* ecs_entity_to_json( #endif -/** - * @file addons/json/serialize_field_info.c - * @brief Serialize query field information to JSON. - */ - - #ifdef FLECS_JSON static @@ -52501,7 +52113,6 @@ bool flecs_json_serialize_get_field_ctx( } } - void flecs_json_serialize_field( const ecs_world_t *world, const ecs_iter_t *it, @@ -52575,12 +52186,6 @@ void flecs_json_serialize_field( #endif -/** - * @file addons/json/serialize_iter.c - * @brief Serialize iterator to JSON. - */ - - #ifdef FLECS_JSON static @@ -52929,12 +52534,6 @@ char* ecs_iter_to_json( #endif -/** - * @file addons/json/serialize_iter_result.c - * @brief Serialize iterator result to JSON. - */ - - #ifdef FLECS_JSON static @@ -53504,12 +53103,6 @@ int flecs_json_serialize_iter_result( #endif -/** - * @file addons/json/serialize_iter_result_query.c - * @brief Serialize matched query data of result. - */ - - #ifdef FLECS_JSON static @@ -53684,7 +53277,7 @@ int flecs_json_serialize_iter_result_field_values( ptr = ecs_field_at_w_size(it, 0, f, i); } else { ecs_size_t size = it->sizes[f]; - ptr = ecs_field_w_size(it, flecs_itosize(size), f); + ptr = ecs_base_field_w_size(it, flecs_itosize(size), f); if (!ptr) { ecs_strbuf_list_appendlit(buf, "0"); @@ -53692,7 +53285,8 @@ int flecs_json_serialize_iter_result_field_values( } if (!it->sources[f]) { - ptr = ECS_ELEM(ptr, size, i); + ecs_size_t stride = flecs_uto(ecs_size_t, ecs_field_stride(it, f)); + ptr = ECS_ELEM(ptr, stride, i); } } @@ -53785,12 +53379,6 @@ int flecs_json_serialize_iter_result_query( #endif -/** - * @file addons/json/serialize_iter_result_table.c - * @brief Serialize all components of matched entity. - */ - - #ifdef FLECS_JSON #define FLECS_JSON_MAX_TABLE_COMPONENTS (256) @@ -54405,12 +53993,6 @@ int flecs_json_serialize_iter_result_table( #endif -/** - * @file addons/json/serialize_query_info.c - * @brief Serialize query information to JSON. - */ - - #ifdef FLECS_JSON static @@ -54603,18 +54185,11 @@ void flecs_json_serialize_query( } flecs_json_array_pop(buf); - flecs_json_object_pop(buf); } #endif -/** - * @file addons/json/serialize_type_info.c - * @brief Serialize type (reflection) information to JSON. - */ - - #ifdef FLECS_JSON static @@ -54825,12 +54400,13 @@ int flecs_json_typeinfo_ser_type_slice( int32_t op_count, ecs_strbuf_t *str) { - const EcsStruct *st = NULL; + ecs_entity_t struct_type = 0; if (ops[0].name) { /* If a member, previous operation is PushStruct */ const ecs_meta_op_t *push = &ops[-1]; ecs_assert(push->kind == EcsOpPushStruct, ECS_INTERNAL_ERROR, NULL); - st = ecs_get(world, push->type, EcsStruct); - ecs_assert(st != NULL, ECS_INTERNAL_ERROR, NULL); + struct_type = push->type; + ecs_assert(ecs_has(world, struct_type, EcsStruct), + ECS_INTERNAL_ERROR, NULL); } for (int i = 0; i < op_count; i ++) { @@ -54924,9 +54500,10 @@ int flecs_json_typeinfo_ser_type_slice( } if (op->name) { - ecs_assert(st != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_member_t *m = ecs_vec_get_t( - &st->members, ecs_member_t, op->member_index); + ecs_assert(struct_type != 0, ECS_INTERNAL_ERROR, NULL); + ecs_member_t *m = ecs_struct_get_nth_member( + ECS_CONST_CAST(ecs_world_t*, world), struct_type, + op->member_index); ecs_assert(m != NULL, ECS_INTERNAL_ERROR, NULL); bool value_range = ECS_NEQ(m->range.min, m->range.max); @@ -55019,20 +54596,9 @@ char* ecs_type_info_to_json( #endif -/** - * @file addons/json/serialize_value.c - * @brief Serialize value to JSON. - */ - -/** - * @file addons/meta/meta.h - * @brief Private functions for meta addon. - */ - #ifndef FLECS_META_PRIVATE_H #define FLECS_META_PRIVATE_H - #ifdef FLECS_META void flecs_meta_type_serializer_init( @@ -55060,10 +54626,13 @@ void flecs_rtt_init_default_hooks( const char* flecs_meta_op_kind_str( ecs_meta_op_kind_t kind); -#endif +int32_t flecs_struct_member_count( + ecs_world_t *world, + ecs_entity_t type); #endif +#endif #ifdef FLECS_JSON @@ -55549,12 +55118,6 @@ char* ecs_ptr_to_json( #endif -/** - * @file addons/json/serialize_world.c - * @brief Serialize world to JSON. - */ - - #ifdef FLECS_JSON int ecs_world_to_json_buf( @@ -55621,13 +55184,6 @@ char* ecs_world_to_json( #endif - -/** - * @file addons/meta/c_utils.c - * @brief C utilities for meta addon. - */ - - #ifdef FLECS_META #define ECS_META_IDENTIFIER_LENGTH (256) @@ -56492,11 +56048,6 @@ int ecs_meta_from_desc( #endif -/** - * @file addons/meta/cursor.c - * @brief API for reading and assigning values of runtime types with reflection. - */ - #include #include @@ -57290,17 +56841,12 @@ ecs_entity_t ecs_meta_get_unit( const ecs_meta_cursor_t *cursor) { ecs_meta_scope_t *scope = flecs_cursor_get_scope(cursor); - ecs_entity_t type = scope->type; - const EcsStruct *st = ecs_get(cursor->world, type, EcsStruct); - if (!st) { - return 0; - } - ecs_meta_op_t *op = flecs_cursor_get_op(scope); - ecs_member_t *m = ecs_vec_get_t( - &st->members, ecs_member_t, op->member_index); + ecs_member_t *m = ecs_struct_get_nth_member( + ECS_CONST_CAST(ecs_world_t*, cursor->world), scope->type, + op->member_index); - return m->unit; + return m ? m->unit : 0; } const char* ecs_meta_get_member( @@ -57315,17 +56861,12 @@ ecs_entity_t ecs_meta_get_member_id( const ecs_meta_cursor_t *cursor) { ecs_meta_scope_t *scope = flecs_cursor_get_scope(cursor); - ecs_entity_t type = scope->type; - const EcsStruct *st = ecs_get(cursor->world, type, EcsStruct); - if (!st) { - return 0; - } - ecs_meta_op_t *op = flecs_cursor_get_op(scope); - ecs_member_t *m = ecs_vec_get_t( - &st->members, ecs_member_t, op->member_index); + ecs_member_t *m = ecs_struct_get_nth_member( + ECS_CONST_CAST(ecs_world_t*, cursor->world), scope->type, + op->member_index); - return m->member; + return m ? m->member : 0; } /* Utilities for type conversions and bounds checking */ @@ -58918,20 +58459,9 @@ double ecs_meta_ptr_to_float( #endif -/** - * @file addons/meta/definitions.c - * @brief Reflection definitions for builtin types. - */ - -/** - * @file addons/meta/type_support/type_support.h - * @brief Type support for meta addon. - */ - #ifndef FLECS_META_TYPE_SUPPORT_H #define FLECS_META_TYPE_SUPPORT_H - #ifdef FLECS_META int flecs_init_type( @@ -58981,7 +58511,6 @@ void flecs_meta_units_init( #endif - #ifdef FLECS_META /* Opaque type serializer for addon vector */ @@ -59274,12 +58803,6 @@ void flecs_meta_import_definitions( #endif -/** - * @file addons/meta/meta.c - * @brief Meta addon. - */ - - #ifdef FLECS_META void flecs_type_serializer_dtor( @@ -59344,7 +58867,6 @@ const char* flecs_type_kind_str( } } - #ifdef FLECS_DEBUG static bool flecs_meta_detect_cycles_w_stack( @@ -59585,12 +59107,6 @@ void FlecsMetaImport( #endif -/** - * @file addons/meta/rtt_lifecycle.c - * @brief Runtime component lifecycle management. - */ - - #ifdef FLECS_META /* Stores all the information necessary to forward a hook call to a @@ -59929,9 +59445,7 @@ void flecs_rtt_init_default_hooks_struct( ecs_entity_t component, const ecs_type_info_t *ti) { - /* Obtain struct information to figure out what members it contains: */ - const EcsStruct *struct_info = ecs_get(world, component, EcsStruct); - ecs_assert(struct_info != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ecs_has(world, component, EcsStruct), ECS_INTERNAL_ERROR, NULL); /* These flags will be set to true if we determine we need to generate a * hook of a particular type: */ @@ -59944,11 +59458,10 @@ void flecs_rtt_init_default_hooks_struct( /* Iterate all struct members and see if any member type has hooks. If so, * the struct itself will need to have that hook: */ - int i, member_count = ecs_vec_count(&struct_info->members); - ecs_member_t *members = ecs_vec_first(&struct_info->members); + int i, member_count = flecs_struct_member_count(world, component); ecs_flags32_t flags = 0; for (i = 0; i < member_count; i++) { - ecs_member_t *m = &members[i]; + ecs_member_t *m = ecs_struct_get_nth_member(world, component, i); const ecs_type_info_t *member_ti = ecs_get_type_info(world, m->type); if (!member_ti || member_ti == ti) { continue; @@ -59986,7 +59499,7 @@ void flecs_rtt_init_default_hooks_struct( * build the vector of calls that will then be executed by the generic hook * handler: */ for (i = 0; i < member_count; i++) { - ecs_member_t *m = &members[i]; + ecs_member_t *m = ecs_struct_get_nth_member(world, component, i); const ecs_type_info_t *member_ti = ecs_get_type_info(world, m->type); if (!member_ti || member_ti == ti) { continue; @@ -60237,7 +59750,6 @@ void flecs_rtt_init_default_hooks_array( hooks.equals = valid_equals && !(flags & ECS_TYPE_HOOK_EQUALS_ILLEGAL) ? flecs_rtt_array_equals : NULL; - if (hooks.lifecycle_ctx_free) { hooks.lifecycle_ctx_free(hooks.lifecycle_ctx); hooks.lifecycle_ctx_free = flecs_rtt_free_lifecycle_nop; @@ -60518,7 +60030,6 @@ void flecs_rtt_init_default_hooks_vector( hooks.equals = NULL; } - /* propagate only the compare/equals hook illegal flag, if set */ hooks.flags |= flags & (ECS_TYPE_HOOK_CMP_ILLEGAL|ECS_TYPE_HOOK_EQUALS_ILLEGAL); @@ -60590,12 +60101,6 @@ void flecs_rtt_init_default_hooks( #endif -/** - * @file addons/meta/serializer.c - * @brief Build instruction list for serializing/deserializing types. - */ - - #ifdef FLECS_META static @@ -61039,8 +60544,7 @@ int flecs_meta_serialize_struct( ecs_size_t offset, ecs_vec_t *ops) { - const EcsStruct *ptr = ecs_get(world, type, EcsStruct); - ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ecs_has(world, type, EcsStruct), ECS_INTERNAL_ERROR, NULL); int32_t cur, first = ecs_vec_count(ops); ecs_meta_op_t *op = flecs_meta_ops_add(ops, EcsOpPushStruct); @@ -61049,16 +60553,15 @@ int flecs_meta_serialize_struct( op->type_info = ecs_get_type_info(world, type); ecs_assert(op->type_info != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_member_t *members = ecs_vec_first(&ptr->members); - int32_t i, count = ecs_vec_count(&ptr->members); + int32_t i, count = flecs_struct_member_count(world, type); ecs_hashmap_t *member_index = NULL; - if (count) { + if (count) { op->is.members = member_index = flecs_name_index_new(&world->allocator); } for (i = 0; i < count; i ++) { - ecs_member_t *member = &members[i]; + ecs_member_t *member = ecs_struct_get_nth_member(world, type, i); cur = ecs_vec_count(ops); @@ -61235,18 +60738,8 @@ char* ecs_meta_serializer_to_str( #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 @@ -61561,10 +61054,6 @@ 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" #include @@ -61919,15 +61408,8 @@ void ecs_set_os_api_impl(void) { #endif #endif -/** - * @file addons/parser/tokenizer.c - * @brief Parser tokenizer. - */ - - #ifdef FLECS_PARSER - static bool flecs_is_comment( const char *pos) @@ -62842,12 +62324,6 @@ const char* flecs_token( #endif -/** - * @file addons/pipeline/frame.c - * @brief Functions for frame begin/end. - */ - - #ifdef FLECS_PIPELINE static @@ -63024,12 +62500,6 @@ void ecs_frame_end( #endif -/** - * @file addons/pipeline/pipeline.c - * @brief Functions for building and running pipelines. - */ - - #ifdef FLECS_PIPELINE /* Free state-owned resources without touching the query. Used when the @@ -64051,12 +63521,6 @@ void FlecsPipelineImport( #endif -/** - * @file addons/pipeline/worker.c - * @brief Functions for running pipelines on one or more threads. - */ - - #ifdef FLECS_PIPELINE /* Synchronize workers */ @@ -64360,22 +63824,9 @@ bool ecs_using_task_threads( #endif -/** - * @file addons/query_dsl/parser.c - * @brief Query DSL parser. - */ - -/** - * @file addons/parser/grammar.h - * @brief Grammar parser. - * - * Macro utilities that facilitate a simple recursive descent parser. - */ - #ifndef FLECS_PARSER_GRAMMAR_H #define FLECS_PARSER_GRAMMAR_H - #if defined(ECS_TARGET_CLANG) /* Ignore unused enum constants in switch as it would blow up the parser code */ #pragma clang diagnostic ignored "-Wswitch-enum" @@ -64656,10 +64107,8 @@ bool ecs_using_task_threads( #endif - #ifdef FLECS_QUERY_DSL - #define EcsTokTermIdentifier\ EcsTokIdentifier:\ case EcsTokNumber:\ @@ -65480,12 +64929,6 @@ const char* ecs_query_args_parse( #endif -/** - * @file addons/script/ast.c - * @brief Script AST implementation. - */ - - #ifdef FLECS_SCRIPT #define flecs_ast_new(parser, T, kind)\ @@ -65876,12 +65319,6 @@ ecs_script_function_node_t* flecs_script_insert_function( #endif -/** - * @file addons/script/function.c - * @brief Script function API. - */ - - #ifdef FLECS_SCRIPT static @@ -66292,12 +65729,6 @@ void flecs_function_import( #endif -/** - * @file addons/script/functions_builtin.c - * @brief Built-in functions for flecs script. - */ - - #ifdef FLECS_SCRIPT static @@ -66524,12 +65955,6 @@ void flecs_script_register_builtin_functions( #endif -/** - * @file addons/script/functions_math.c - * @brief Math functions for flecs script. - */ - - #ifdef FLECS_SCRIPT_MATH #include @@ -67267,15 +66692,6 @@ void FlecsScriptMathImport( #endif -/** - * @file addons/script/functions_math_perlin.c - * @brief Perlin noise functions for flecs script. - * - * - flecs_perlin2(x, y): returns noise in ~[-1, 1] - * - flecs_perlin_seed(seed): initializes permutation table for repeatable noise - */ - - #ifdef FLECS_SCRIPT_MATH #include @@ -67408,13 +66824,6 @@ void FlecsScriptMathPerlinImport( #endif -/** - * @file addons/script/parser.c - * @brief Script grammar parser. - */ - - - #ifdef FLECS_SCRIPT #define EcsTokEndOfStatement\ @@ -68724,12 +68133,6 @@ ecs_script_t* ecs_script_parse( #endif -/** - * @file addons/script/script.c - * @brief Script API. - */ - - #ifdef FLECS_SCRIPT ECS_COMPONENT_DECLARE(EcsScript); @@ -69170,11 +68573,6 @@ void FlecsScriptImport( #endif -/** - * @file addons/script/serialize.c - * @brief Serialize values to string. - */ - #include #ifdef FLECS_SCRIPT @@ -69648,12 +69046,6 @@ char* ecs_ptr_to_str( #endif -/** - * @file addons/script/template.c - * @brief Script template implementation. - */ - - #ifdef FLECS_SCRIPT ECS_COMPONENT_DECLARE(EcsScriptTemplateSetEvent); @@ -70301,12 +69693,6 @@ void flecs_script_template_import( #endif -/** - * @file addons/script/vars.c - * @brief Script variables. - */ - - #ifdef FLECS_SCRIPT ecs_script_vars_t* flecs_script_vars_push( @@ -70652,12 +70038,6 @@ void ecs_script_vars_from_iter( #endif -/** - * @file addons/script/visit.c - * @brief Script AST visitor utilities. - */ - - #ifdef FLECS_SCRIPT static @@ -70837,12 +70217,6 @@ int ecs_script_visit_( #endif -/** - * @file addons/script/visit_check.c - * @brief Script AST validation. - */ - - #ifdef FLECS_SCRIPT static @@ -71397,12 +70771,6 @@ int flecs_script_check_node( #endif -/** - * @file addons/script/visit_eval.c - * @brief Script evaluation visitor. - */ - - #ifdef FLECS_SCRIPT static @@ -72724,7 +72092,6 @@ int flecs_script_eval_module( ecs_entity_t old_scope = ecs_set_scope(v->world, 0); v->parent = 0; - ecs_entity_t m = flecs_script_create_entity(v, node->name); if (!m) { return -1; @@ -73666,12 +73033,6 @@ int ecs_script_eval( #endif -/** - * @file addons/script/visit_free.c - * @brief Script free visitor (frees AST resources). - */ - - #ifdef FLECS_SCRIPT static @@ -73927,12 +73288,6 @@ int flecs_script_visit_free( #endif -/** - * @file addons/script/visit_to_str.c - * @brief Script AST to string visitor. - */ - - #ifdef FLECS_SCRIPT typedef struct ecs_script_str_visitor_t { @@ -74467,20 +73822,9 @@ char* ecs_script_ast_to_str( #endif -/** - * @file addons/stats/memory.c - * @brief Memory usage statistics. - */ - -/** - * @file addons/stats/stats.h - * @brief Internal functions/types for stats addon. - */ - #ifndef FLECS_STATS_PRIVATE_H #define FLECS_STATS_PRIVATE_H - typedef struct { /* Statistics API interface */ void (*copy_last)(void *stats, void *src); @@ -74525,7 +73869,6 @@ void flecs_stats_memory_register_reflection( #endif - #ifdef FLECS_REST #endif @@ -75946,12 +75289,6 @@ ecs_size_t ecs_memory_get( #endif -/** - * @file addons/stats/monitor.c - * @brief Stats addon module. - */ - - #ifdef FLECS_STATS ECS_COMPONENT_DECLARE(FlecsStats); @@ -76366,12 +75703,6 @@ void FlecsStatsImport( #endif -/** - * @file addons/stats/pipeline_monitor.c - * @brief Stats addon pipeline monitor. - */ - - #ifdef FLECS_STATS ECS_COMPONENT_DECLARE(EcsPipelineStats); @@ -76417,7 +75748,6 @@ void flecs_pipeline_stats_set_t( ((ecs_pipeline_stats_t*)stats)->t = t; } - static void flecs_pipeline_stats_copy_last( void *stats, @@ -76496,13 +75826,6 @@ void FlecsPipelineMonitorImport( #endif -/** - * @file addons/stats/stats.c - * @brief Stats addon. - */ - - - #ifdef FLECS_STATS #define ECS_GAUGE_RECORD(m, t, value)\ @@ -77258,12 +76581,6 @@ void ecs_world_stats_log( #endif -/** - * @file addons/stats/system_monitor.c - * @brief Stats addon system monitor. - */ - - #ifdef FLECS_STATS ECS_COMPONENT_DECLARE(EcsSystemStats); @@ -77378,12 +76695,6 @@ void FlecsSystemMonitorImport( #endif -/** - * @file addons/stats/world_monitor.c - * @brief Stats addon world monitor. - */ - - #ifdef FLECS_STATS ECS_COMPONENT_DECLARE(EcsWorldStats); @@ -77494,12 +76805,6 @@ void FlecsWorldMonitorImport( #endif -/** - * @file addons/stats/world_summary.c - * @brief World summary addon. - */ - - #ifdef FLECS_STATS ECS_COMPONENT_DECLARE(EcsWorldSummary); @@ -77667,15 +76972,8 @@ void FlecsWorldSummaryImport( #endif -/** - * @file addons/system/system.c - * @brief System addon. - */ - - #ifdef FLECS_SYSTEM - ecs_mixins_t ecs_system_t_mixins = { .type_name = "ecs_system_t", .elems = { @@ -77821,7 +77119,6 @@ ecs_entity_t flecs_run_system( return it->interrupted_by; } - /* -- Public API -- */ ecs_entity_t ecs_run_worker( @@ -78162,138 +77459,6 @@ void FlecsSystemImport( #endif -/** - * @file query/cache/cache.c - * @brief Cached query implementation. - * - * Implements a cache that stores a list of tables that match the query. - * Cached queries outperform uncached queries in many scenarios since they don't - * have to search for tables that match a query, but just iterate a list. - * - * A cache has a "cache query" which is derived from the actual query the - * application provides. This cache query differs in two significant ways from - * the application-provided query: - * - It does not contain terms that aren't cacheable - * - It always matches empty tables - * - * If the number of terms in the actual query differs from the cache query, the - * query will create a field_map array that maps from cached field indices to - * actual query indices. - * - * Cached queries use an observer to get notified of new/deleted tables. In most - * cases this is sufficient to keep the cache up to date, but for queries that - * match components outside of a table (for example, from a parent entity) the - * cache will have to be revalidated after a parent changes a matched component. - * This is called rematching, the implementation can be found in match.c. - * - * A cache can be trivial or non-trivial. A trivial cache is a cache for a query - * that meets the following criteria: - * - The query doesn't have any wildcards - * - The query doesn't use relationship traversal - * - The query doesn't use operators other than And, Not or Optional - * - The cached query and actual query have the same terms - * - * A trivial cache has to store much less data for each cached element, and uses - * an iterator implementation that is much faster. Because of this difference - * the code often needs to lookup the size of cache elements before doing work. - * - * The following types are important for the cache: - * - ecs_query_cache_match_t: Stores a single match for a table. - * - ecs_query_cache_table_t: Element in the cache->tables map, which allows for - * looking up a match by table id. - * - ecs_query_cache_group_t: Stores an array of matched tables. A query has a - * single group by default, but can have more (see - * group_by). - * - * There are three cache features that significantly alter how elements - * are stored in the cache, which are group_by, order_by and wildcards. - * - * Group_by - * ======== - * Group_by assigns a group id (unsigned 64-bit integer) to each table. This - * number is computed by a group_by function that can be provided by the - * application. A group can only be computed from which components are stored in - * a table (the table type). - * - * By default a query only has a single group which contains all matched tables - * stored in an array. When a query uses group_by, the matched tables are split - * up across multiple groups depending on the computed group id of the table. - * When a group becomes empty (there are no more tables with the group id) it is - * deleted from the cache. - * - * The query cache contains a "groups" map and a "tables" map. The groups map - * allows for group id -> group lookups. The tables map allows for table id -> - * table element lookups. Because tables are stored in arrays that can be - * reallocated, rather than a pointer to the table element, the tables map - * stores a pointer to the group the table is part of, and an index into the - * group's table array. - * - * Queries can use group_by for two main reasons: - * - It allows for quickly iterating subsets of the matched query results - * - Groups are iterated in order based on group id - * - * An application can iterate a single group by calling ecs_iter_set_group on an - * iterator created from a query that uses group_by. This will cause the - * iterator to only iterate the table array for that specific group. To find the - * group's table array, the ecs_iter_set_group function uses the groups map. - * - * Groups are stored in a linked list that's ordered by the group id. This can - * be in ascending or descending order, depending on the query. Because of this - * ordering, group insertion and group removal are O(N) operations where N is - * the number of groups in the query. The head of the list is stored in the - * first_group member of the query. - * - * The cascade query feature is built on top of group_by. It provides a callback - * that computes a hierarchy depth for the table with a specified relationship. - * Because groups are stored in ascending or descending order, this effectively - * means that tables will be iterated in a breadth-first order, which can be - * useful for hierarchy traversal code or transform systems. - * - * Order_by - * ======== - * Order_by will cause the query to return results in an order that is defined - * by either a component or entity id. To accomplish this, the query has to find - * the order across different tables. The code will first sort the elements in - * each matched table, and then build a list of (offset, count) slices across - * the matched tables that represents the correct iteration order. The algorithm - * used for sorting is qsort. - * - * Resorting is a very expensive operation. Queries use change detection, which - * at a table level can detect if any changes occurred to the entities or the - * ordered-by component. Only if a change has been detected will resorting - * occur. Even then, this remains an expensive feature and should only be used - * for data that doesn't change often. Flecs uses the query sorting feature to - * ensure that pipeline queries return systems in a well-defined order. - * - * The sorted list of slices is stored in the table_slices member of the cache, - * and is only populated for sorted queries. - * - * When group_by and order_by are combined in a single query, the group order - * takes precedence over order_by. - * - * It is currently not possible to iterate a single group when order_by is used - * together with group_by. This is a TODO that has to be addressed by adding a - * table_slices array to each group instead of as a member of the cache object. - * - * Wildcards - * ========= - * Wildcards are a query feature that can cause a single table to be matched - * multiple times. A typical example would be a (Likes, *) query. If an entity - * has both (Likes, Bob) and (Likes, Alice), it (and all entities in the same - * table) will get matched twice by the query. - * - * When a table matches multiple times (through one or more wildcard terms), any - * match after the first is stored in a separate wildcard_matches member on the - * cache element. This separate array ensures that tables can be removed from - * groups without leaving arbitrarily sized holes in the group->tables array. - * - * When iterating a wildcard query, the iterator will alternate between - * iterating the group->tables array and the wildcard_matches array on each - * matched table, in a way that all matches for the same table are iterated - * together. - */ - - /* Is cache trivial? */ bool flecs_query_cache_is_trivial( const ecs_query_cache_t *cache) @@ -78788,6 +77953,7 @@ ecs_query_cache_t* flecs_query_cache_init( if ((t == count) && (q->flags & EcsQueryMatchOnlySelf) && !(q->flags & EcsQueryMatchWildcards) && + !(q->flags & EcsQueryHasComponentInheritance) && !(q->flags & EcsQueryCacheWithFilter)) { if (!const_desc->order_by && !const_desc->group_by && @@ -78938,12 +78104,6 @@ ecs_query_cache_t* flecs_query_cache_init( return NULL; } -/** - * @file query/cache/cache_iter.c - * @brief Cache iterator functions. - */ - - /* Initialize cached query iterator. */ void flecs_query_cache_iter_init( ecs_iter_t *it, @@ -79278,12 +78438,6 @@ bool flecs_query_is_trivial_cache_test( return false; } -/** - * @file query/cache/change_detection.c - * @brief Query change detection implementation. - */ - - typedef struct { ecs_table_t *table; int32_t column; @@ -79998,12 +79152,6 @@ void ecs_iter_skip( it->flags |= EcsIterSkip; } -/** - * @file query/cache/group.c - * @brief Adding/removing tables to/from query groups. - */ - - /* Check if group is currently linked in the cache group list. */ static bool flecs_query_cache_group_is_linked( @@ -80134,7 +79282,6 @@ ecs_query_cache_group_t* flecs_query_cache_ensure_group( flecs_query_cache_group_insert(cache, group); - if (cache->on_group_create) { group->info.ctx = cache->on_group_create( cache->query->world, group_id, cache->group_by_ctx); @@ -80448,12 +79595,6 @@ void flecs_query_cache_remove_all_tables( ecs_assert(ecs_map_count(&cache->groups) == 0, ECS_INTERNAL_ERROR, NULL); } -/** - * @file query/cache/match.c - * @brief Match table one or more times with query. - */ - - /* Free cache entry element. */ static void flecs_query_cache_match_elem_fini( @@ -80804,12 +79945,6 @@ void flecs_query_rematch( ecs_os_perf_trace_pop("flecs.query.rematch"); } -/** - * @file query/cache/order_by.c - * @brief Query sorting (order_by) implementation. - */ - - ECS_SORT_TABLE_WITH_COMPARE(_, flecs_query_cache_sort_table_generic, order_by, static) static @@ -81121,12 +80256,6 @@ void flecs_query_cache_sort_tables( } } -/** - * @file query/compiler/compiler.c - * @brief Compile query program from query. - */ - - static bool flecs_query_var_is_anonymous( const ecs_query_impl_t *query, @@ -81295,12 +80424,6 @@ int flecs_query_discover_vars( ecs_var_id_t first_var_id = flecs_query_add_var_for_term_id( query, first, vars, EcsVarEntity); if (first_var_id == EcsVarNone) { - /* If first is not a variable, check if we need to insert anonymous - * variable for resolving component inheritance */ - if (term->flags_ & EcsTermIdInherited) { - anonymous_count += 2; /* table & entity variable */ - } - /* If first is a wildcard, insert anonymous variable */ if (flecs_term_ref_is_wildcard(first)) { anonymous_count ++; @@ -82296,12 +81419,6 @@ int flecs_query_compile( return 0; } -/** - * @file query/compiler/compiler_term.c - * @brief Compile query term. - */ - - #define FlecsRuleOrMarker ((int16_t)-2) /* Marks instruction in OR chain */ ecs_var_id_t flecs_query_find_var_id( @@ -82735,46 +81852,6 @@ void flecs_query_insert_unconstrained_transitive( flecs_query_op_insert(&and_op, ctx); } -static -void flecs_query_insert_inheritance( - ecs_query_impl_t *query, - ecs_term_t *term, - ecs_query_op_t *op, - ecs_query_compile_ctx_t *ctx, - bool cond_write) -{ - /* Anonymous variable to store the resolved component ids */ - ecs_var_id_t tvar = flecs_query_add_var(query, NULL, NULL, EcsVarTable); - ecs_var_id_t evar = flecs_query_add_var(query, NULL, NULL, EcsVarEntity); - - flecs_set_var_label(&query->vars[tvar], ecs_get_name(query->pub.world, - ECS_TERM_REF_ID(&term->first))); - flecs_set_var_label(&query->vars[evar], ecs_get_name(query->pub.world, - ECS_TERM_REF_ID(&term->first))); - - ecs_query_op_t trav_op = {0}; - trav_op.kind = EcsQueryTrav; - trav_op.field_index = -1; - trav_op.first.entity = EcsIsA; - trav_op.second.entity = ECS_TERM_REF_ID(&term->first); - trav_op.src.var = tvar; - trav_op.flags = EcsQueryIsSelf; - trav_op.flags |= (EcsQueryIsEntity << EcsQueryFirst); - trav_op.flags |= (EcsQueryIsEntity << EcsQuerySecond); - trav_op.flags |= (EcsQueryIsVar << EcsQuerySrc); - trav_op.written |= (1ull << tvar); - if (term->first.id & EcsSelf) { - trav_op.match_flags |= EcsTermReflexive; - } - flecs_query_op_insert(&trav_op, ctx); - flecs_query_insert_each(tvar, evar, ctx, cond_write); - - ecs_query_ref_t r = { .var = evar }; - op->first = r; - op->flags &= (ecs_flags8_t)~(EcsQueryIsEntity << EcsQueryFirst); - op->flags |= (EcsQueryIsVar << EcsQueryFirst); -} - void flecs_query_compile_term_ref( ecs_world_t *world, ecs_query_impl_t *query, @@ -83721,12 +82798,6 @@ int flecs_query_compile_term( flecs_query_begin_block_or(&op, term, ctx); } - /* If term has component inheritance enabled, insert instruction to walk - * down the relationship tree of the id. */ - if (term->flags_ & EcsTermIdInherited) { - flecs_query_insert_inheritance(query, term, &op, ctx, cond_write); - } - op.match_flags = term->flags_; ecs_write_flags_t write_state = ctx->written; @@ -83853,12 +82924,6 @@ int flecs_query_compile_term( return -1; } -/** - * @file query/engine/eval.c - * @brief Query engine implementation. - */ - - // #define FLECS_QUERY_TRACE #ifdef FLECS_QUERY_TRACE @@ -83917,7 +82982,8 @@ bool flecs_query_select_w_id( tr = (const ecs_table_record_t*)op_ctx->it.cur; ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); table = tr->hdr.table; - op_ctx->column = flecs_query_next_column(table, cr->id, op_ctx->column); + op_ctx->column = flecs_query_next_column( + ctx->world, table, cr->id, op_ctx->column); op_ctx->remaining --; } @@ -83974,14 +83040,15 @@ bool flecs_query_with( op_ctx->remaining = flecs_ito(int16_t, tr->count); op_ctx->it.cur = &tr->hdr; } else { - ecs_assert((op_ctx->remaining + op_ctx->column - 1) < table->type.count, + ecs_assert((op_ctx->remaining + op_ctx->column - 1) < table->type.count, ECS_INTERNAL_ERROR, NULL); ecs_assert(op_ctx->remaining >= 0, ECS_INTERNAL_ERROR, NULL); if (--op_ctx->remaining <= 0) { return false; } - op_ctx->column = flecs_query_next_column(table, cr->id, op_ctx->column); + op_ctx->column = flecs_query_next_column( + ctx->world, table, cr->id, op_ctx->column); ecs_assert(op_ctx->column != -1, ECS_INTERNAL_ERROR, NULL); } @@ -85433,12 +84500,6 @@ bool flecs_query_run_until( return true; } -/** - * @file query/engine/eval_iter.c - * @brief Query iterator. - */ - - static void flecs_query_iter_run_ctx_init( ecs_iter_t *it, @@ -86108,12 +85169,6 @@ ecs_iter_t ecs_query_iter( return flecs_query_iter(world, q); } -/** - * @file query/engine/eval_member.c - * @brief Component member evaluation. - */ - - static bool flecs_query_member_cmp( const ecs_query_op_t *op, @@ -86247,12 +85302,6 @@ bool flecs_query_member_neq( return flecs_query_member_cmp(op, redo, ctx, true); } -/** - * @file query/engine/eval_pred.c - * @brief Equality predicate evaluation. - */ - - static const char* flecs_query_name_arg( const ecs_query_op_t *op, @@ -86581,12 +85630,6 @@ bool flecs_query_pred_neq_name( return flecs_query_pred_neq_w_range(op, redo, ctx, r); } -/** - * @file query/engine/eval_sparse.c - * @brief Sparse component evaluation. - */ - - static bool flecs_query_sparse_init_sparse( ecs_query_sparse_ctx_t *op_ctx, @@ -87224,12 +86267,6 @@ bool flecs_query_sparse( } } -/** - * @file query/engine/eval_toggle.c - * @brief Bitset toggle evaluation. - */ - - typedef struct { ecs_flags64_t mask; bool has_bitset; @@ -87567,13 +86604,6 @@ repeat: {} return result; } - -/** - * @file query/engine/eval_trav.c - * @brief Transitive/reflexive relationship traversal. - */ - - static bool flecs_query_trav_fixed_src_reflexive( const ecs_query_op_t *op, @@ -87857,12 +86887,6 @@ bool flecs_query_trav( } } -/** - * @file query/engine/eval_tree.c - * @brief Hierarchy evaluation (ChildOf pairs/Parent components). - */ - - const EcsParent* flecs_query_tree_get_parents( ecs_table_range_t range) { @@ -88685,12 +87709,6 @@ bool flecs_query_tree_up_post( } } -/** - * @file query/engine/eval_up.c - * @brief Up traversal evaluation. - */ - - /* Find tables with requested component that have traversable entities. */ static bool flecs_query_up_select_table( @@ -89165,12 +88183,6 @@ bool flecs_query_self_up( } } -/** - * @file query/engine/eval_utils.c - * @brief Query engine evaluation utilities. - */ - - void flecs_query_set_iter_this( ecs_iter_t *it, const ecs_query_run_ctx_t *ctx) @@ -89496,17 +88508,14 @@ ecs_id_t flecs_query_op_get_id( } int16_t flecs_query_next_column( + const ecs_world_t *world, ecs_table_t *table, ecs_id_t id, int32_t column) { - if (!ECS_IS_PAIR(id) || (ECS_PAIR_FIRST(id) != EcsWildcard)) { - column = column + 1; - } else { - ecs_assert(column >= 0, ECS_INTERNAL_ERROR, NULL); - column = ecs_search_offset(NULL, table, column + 1, id, NULL); - ecs_assert(column != -1, ECS_INTERNAL_ERROR, NULL); - } + ecs_assert(column >= 0, ECS_INTERNAL_ERROR, NULL); + column = ecs_search_offset(world, table, column + 1, id, NULL); + ecs_assert(column != -1, ECS_INTERNAL_ERROR, NULL); return flecs_ito(int16_t, column); } @@ -89583,12 +88592,6 @@ bool flecs_query_table_filter( return (table->flags & filter_mask & filter) != 0; } -/** - * @file query/engine/trav_cache.c - * @brief Cache that stores the result of graph traversal. - */ - - static void flecs_query_build_down_cache( ecs_world_t *world, @@ -89731,12 +88734,6 @@ void flecs_query_get_trav_up_cache( } } -/** - * @file query/engine/trav_down_cache.c - * @brief Down traversal cache. - */ - - static void flecs_trav_entity_down_isa( ecs_world_t *world, @@ -90070,12 +89067,6 @@ void flecs_query_down_cache_fini( ecs_vec_fini_t(a, &cache->down.elems, ecs_trav_down_elem_t); } -/** - * @file query/engine/trav_up_cache.c - * @brief Up traversal cache. - */ - - static ecs_trav_up_t* flecs_trav_up_ensure( const ecs_query_run_ctx_t *ctx, @@ -90372,12 +89363,6 @@ void flecs_query_up_cache_fini( ecs_map_fini(&cache->src); } -/** - * @file query/engine/trivial_iter.c - * @brief Iterator for trivial queries. - */ - - static bool flecs_query_trivial_search_init( const ecs_query_run_ctx_t *ctx, @@ -90611,12 +89596,6 @@ bool flecs_query_trivial_test( } } -/** - * @file addons/meta/type_support/array_ts.c - * @brief Array type support. - */ - - #ifdef FLECS_META static @@ -90783,12 +89762,6 @@ void flecs_meta_array_init( #endif -/** - * @file addons/meta/type_support/enum_ts.c - * @brief Enum type support. - */ - - #ifdef FLECS_META /* EcsConstants lifecycle */ @@ -91444,12 +90417,6 @@ void flecs_meta_enum_init( #endif -/** - * @file addons/meta/type_support/opaque_ts.c - * @brief Opaque type support. - */ - - #ifdef FLECS_META static @@ -91534,12 +90501,6 @@ void flecs_meta_opaque_init( #endif -/** - * @file addons/meta/type_support/primitive_ts.c - * @brief Primitive type support. - */ - - #ifdef FLECS_META /* ecs_string_t lifecycle */ @@ -92098,12 +91059,6 @@ void flecs_meta_primitives_init( #endif -/** - * @file addons/meta/type_support/struct_ts.c - * @brief Struct type support. - */ - - #ifdef FLECS_META static void flecs_struct_dtor( @@ -92158,6 +91113,202 @@ void flecs_set_struct_member( member->warning_range = m->warning_range; } +static +ecs_entity_t flecs_struct_base( + ecs_world_t *world, + ecs_entity_t type, + ecs_size_t *base_size, + ecs_size_t *base_align) +{ + if (base_size) *base_size = 0; + if (base_align) *base_align = 0; + + ecs_entity_t base; + int32_t i = 0; + while ((base = ecs_get_target(world, type, EcsIsA, i ++))) { + const EcsComponent *comp = ecs_get(world, base, EcsComponent); + if (comp) { + if (base_size) *base_size = comp->size; + if (base_align) *base_align = comp->alignment; + return base; + } + } + + return 0; +} + +/* Total number of members in the struct, including inherited base members. */ +int32_t flecs_struct_member_count( + ecs_world_t *world, + ecs_entity_t type) +{ + int32_t count = 0; + ecs_entity_t base = flecs_struct_base(world, type, NULL, NULL); + if (base) { + count = flecs_struct_member_count(world, base); + } + + const EcsStruct *s = ecs_get(world, type, EcsStruct); + if (s) { + count += ecs_vec_count(&s->members); + } + + return count; +} + +static +int flecs_struct_compute_offsets( + ecs_world_t *world, + ecs_member_t *members, + int32_t from, + int32_t count, + ecs_size_t *size_inout, + ecs_size_t *align_inout) +{ + ecs_size_t size = *size_inout; + ecs_size_t alignment = *align_inout; + + int32_t i; + for (i = from; i < count; i ++) { + ecs_member_t *elem = &members[i]; + + ecs_assert(elem->name != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(elem->type != 0, ECS_INTERNAL_ERROR, NULL); + + const EcsComponent *mbr_comp = ecs_get(world, elem->type, EcsComponent); + if (!mbr_comp) { + char *path = ecs_get_path(world, elem->type); + ecs_err("member '%s' is not a type", path); + ecs_os_free(path); + return -1; + } + + ecs_size_t member_size = mbr_comp->size; + ecs_size_t member_alignment = mbr_comp->alignment; + + if (!member_size || !member_alignment) { + char *path = ecs_get_path(world, elem->type); + ecs_err("member '%s' has 0 size/alignment", path); + ecs_os_free(path); + return -1; + } + + member_size *= elem->count ? elem->count : 1; + size = ECS_ALIGN(size, member_alignment); + elem->size = member_size; + elem->offset = size; + + if (elem->member) { + EcsMember *member_data = ecs_ensure(world, elem->member, EcsMember); + member_data->offset = elem->offset; + } + + size += member_size; + + if (member_alignment > alignment) { + alignment = member_alignment; + } + } + + *size_inout = size; + *align_inout = alignment; + + return 0; +} + +static +void flecs_struct_propagate_to_derived( + ecs_world_t *world, + ecs_entity_t base); + +static ecs_entity_t flecs_struct_finalizing = 0; + +static +int flecs_struct_init_layout( + ecs_world_t *world, + ecs_entity_t struct_type, + ecs_size_t size, + ecs_size_t alignment) +{ + if (!size) { + ecs_err("struct '%s' has 0 size", ecs_get_name(world, struct_type)); + return -1; + } + + if (!alignment) { + ecs_err("struct '%s' has 0 alignment", ecs_get_name(world, struct_type)); + return -1; + } + + size = ECS_ALIGN(size, alignment); + + ecs_modified(world, struct_type, EcsStruct); + + /* Do this last as it triggers the update of EcsTypeSerializer */ + if (flecs_init_type(world, struct_type, EcsStructType, size, alignment)) { + return -1; + } + + /* If current struct is also a member, assign to itself */ + if (ecs_has(world, struct_type, EcsMember)) { + EcsMember *type_mbr = ecs_ensure(world, struct_type, EcsMember); + ecs_assert(type_mbr != NULL, ECS_INTERNAL_ERROR, NULL); + + type_mbr->type = struct_type; + type_mbr->count = 0; + + ecs_modified(world, struct_type, EcsMember); + } + + flecs_struct_propagate_to_derived(world, struct_type); + + return 0; +} + +static +int flecs_struct_finalize( + ecs_world_t *world, + ecs_entity_t struct_type, + EcsStruct *s) +{ + ecs_size_t size, alignment; + flecs_struct_base(world, struct_type, &size, &alignment); + + ecs_member_t *members = ecs_vec_first_t(&s->members, ecs_member_t); + int32_t count = ecs_vec_count(&s->members); + + if (flecs_struct_compute_offsets(world, members, 0, count, &size, &alignment)) { + return -1; + } + + ecs_entity_t prev = flecs_struct_finalizing; + flecs_struct_finalizing = struct_type; + int ret = flecs_struct_init_layout(world, struct_type, size, alignment); + flecs_struct_finalizing = prev; + return ret; +} + +static +void flecs_struct_propagate_to_derived( + ecs_world_t *world, + ecs_entity_t base) +{ + if (!ecs_id_in_use(world, ecs_pair(EcsIsA, base))) { + return; + } + + ecs_iter_t it = ecs_each_id(world, ecs_pair(EcsIsA, base)); + while (ecs_each_next(&it)) { + int32_t i; + for (i = 0; i < it.count; i ++) { + if (ecs_has(world, it.entities[i], EcsStruct)) { + flecs_struct_finalize(world, it.entities[i], + ecs_ensure(world, it.entities[i], EcsStruct)); + } + } + } +} + static int flecs_add_member_to_struct( ecs_world_t *world, @@ -92186,18 +91337,18 @@ int flecs_add_member_to_struct( return -1; } - flecs_meta_detect_cycles(world, m.type, struct_type); - if (ecs_get_typeid(world, m.type) == 0) { char *path = ecs_get_path(world, struct_type); char *ent_path = ecs_get_path(world, m.type); - ecs_err("member '%s.%s.type' is '%s' which is not a type", + ecs_err("member '%s.%s.type' is '%s' which is not a type", path, name, ent_path); ecs_os_free(path); ecs_os_free(ent_path); return -1; } + flecs_meta_detect_cycles(world, m.type, struct_type); + ecs_entity_t unit = m.unit; if (unit) { if (!ecs_has(world, unit, EcsUnit)) { @@ -92232,164 +91383,64 @@ int flecs_add_member_to_struct( EcsStruct *s = ecs_ensure(world, struct_type, EcsStruct); ecs_assert(s != NULL, ECS_INTERNAL_ERROR, NULL); - /* First check if member is already added to struct */ + ecs_entity_t base = flecs_struct_base(world, struct_type, NULL, NULL); + + if (base) { + if (m.offset || m.use_offset) { + ecs_err("member '%s' of struct '%s' has an explicit offset, which is " + "not supported for a struct that inherits from a base", name, + flecs_errstr(ecs_get_path(world, struct_type))); + return -1; + } + + if (ecs_struct_get_member(world, base, name)) { + ecs_err("member '%s' shadows base member of struct '%s'", name, + flecs_errstr(ecs_get_path(world, struct_type))); + return -1; + } + } + ecs_member_t *members = ecs_vec_first_t(&s->members, ecs_member_t); int32_t i, count = ecs_vec_count(&s->members); - bool has_member = false; for (i = 0; i < count; i ++) { - if (member_entity && members[i].member) { - if (members[i].member == member_entity) { - flecs_set_struct_member(&members[i], member_entity, &m, unit); - break; - } - } else { - if (!ecs_os_strcmp(name, members[i].name)) { - flecs_set_struct_member(&members[i], member_entity, &m, unit); - break; - } + bool match = (member_entity && members[i].member) + ? members[i].member == member_entity + : !ecs_os_strcmp(name, members[i].name); + if (match) { + flecs_set_struct_member(&members[i], member_entity, &m, unit); + break; } } - has_member = i != count; - - /* If member wasn't added yet, add a new element to vector */ - if (!has_member) { + if (i == count) { ecs_vec_init_if_t(&s->members, ecs_member_t); ecs_member_t *elem = ecs_vec_append_t(NULL, &s->members, ecs_member_t); elem->name = NULL; flecs_set_struct_member(elem, member_entity, &m, unit); - - /* Reobtain members array in case it was reallocated */ - members = ecs_vec_first_t(&s->members, ecs_member_t); - count ++; } - bool explicit_offset = m.offset || m.use_offset; - - /* Compute member offsets and size & alignment of struct */ - ecs_size_t size = 0; - ecs_size_t alignment = 0; - - if (!explicit_offset) { - for (i = 0; i < count; i ++) { - ecs_member_t *elem = &members[i]; - - ecs_assert(elem->name != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(elem->type != 0, ECS_INTERNAL_ERROR, NULL); - - /* Get component of member type to get its size & alignment */ - const EcsComponent *mbr_comp = ecs_get(world, elem->type, EcsComponent); - if (!mbr_comp) { - char *path = ecs_get_path(world, elem->type); - ecs_err("member '%s' is not a type", path); - ecs_os_free(path); - return -1; - } - - ecs_size_t member_size = mbr_comp->size; - ecs_size_t member_alignment = mbr_comp->alignment; - - if (!member_size || !member_alignment) { - char *path = ecs_get_path(world, elem->type); - ecs_err("member '%s' has 0 size/alignment", path); - ecs_os_free(path); - return -1; - } - - member_size *= elem->count ? elem->count : 1; - size = ECS_ALIGN(size, member_alignment); - elem->size = member_size; - elem->offset = size; - - /* Synchronize offset with Member component */ - if (elem->member) { - EcsMember *member_data = ecs_ensure( - world, elem->member, EcsMember); - member_data->offset = elem->offset; - } - - size += member_size; - - if (member_alignment > alignment) { - alignment = member_alignment; - } - } - } else { - /* If members have explicit offsets, we can't rely on computed - * size/alignment values. Calculate size as if this is the last member - * instead, since this will validate if the member fits in the struct. - * It doesn't matter if the size is smaller than the actual struct size - * because flecs_init_type function compares computed size with actual - * (component) size to determine if the type is partial. */ + if (m.offset || m.use_offset) { + members = ecs_vec_first_t(&s->members, ecs_member_t); ecs_member_t *elem = &members[i]; - ecs_assert(elem->name != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(elem->type != 0, ECS_INTERNAL_ERROR, NULL); - - /* Get component of member type to get its size & alignment */ const EcsComponent *mbr_comp = ecs_get(world, elem->type, EcsComponent); - if (!mbr_comp) { + if (!mbr_comp || !mbr_comp->size || !mbr_comp->alignment) { char *path = ecs_get_path(world, elem->type); - ecs_err("member '%s' is not a type", path); + ecs_err("member '%s' is not a type or has 0 size/alignment", path); ecs_os_free(path); return -1; } - ecs_size_t member_size = mbr_comp->size; - ecs_size_t member_alignment = mbr_comp->alignment; - - if (!member_size || !member_alignment) { - char *path = ecs_get_path(world, elem->type); - ecs_err("member '%s' has 0 size/alignment", path); - ecs_os_free(path); - return -1; - } - - member_size *= elem->count ? elem->count : 1; - elem->size = member_size; - size = elem->offset + member_size; - - const EcsComponent* comp = ecs_get(world, struct_type, EcsComponent); - if (comp) { - alignment = comp->alignment; - } else { - alignment = member_alignment; - } - } - - if (size == 0) { - ecs_err("struct '%s' has 0 size", ecs_get_name(world, struct_type)); - return -1; - } - - if (alignment == 0) { - ecs_err("struct '%s' has 0 alignment", ecs_get_name(world, struct_type)); - return -1; - } - - /* Align struct size to struct alignment */ - size = ECS_ALIGN(size, alignment); - - ecs_modified(world, struct_type, EcsStruct); - - /* Do this last as it triggers the update of EcsTypeSerializer */ - if (flecs_init_type(world, struct_type, EcsStructType, size, alignment)) { - return -1; - } - - /* If current struct is also a member, assign to itself */ - if (ecs_has(world, struct_type, EcsMember)) { - EcsMember *type_mbr = ecs_ensure(world, struct_type, EcsMember); - ecs_assert(type_mbr != NULL, ECS_INTERNAL_ERROR, NULL); - - type_mbr->type = struct_type; - type_mbr->count = 0; + elem->size = mbr_comp->size * (elem->count ? elem->count : 1); - ecs_modified(world, struct_type, EcsMember); + const EcsComponent *comp = ecs_get(world, struct_type, EcsComponent); + return flecs_struct_init_layout(world, struct_type, + elem->offset + elem->size, + comp ? comp->alignment : mbr_comp->alignment); } - return 0; + return flecs_struct_finalize(world, struct_type, s); } static @@ -92448,11 +91499,28 @@ void flecs_set_member_ranges(ecs_iter_t *it) { int i, count = it->count; for (i = 0; i < count; i ++) { - flecs_set_member_from_component(world, it->entities[i], + flecs_set_member_from_component(world, it->entities[i], &member[i], &ranges[i]); } } +static +void flecs_struct_on_set(ecs_iter_t *it) { + ecs_world_t *world = it->world; + EcsStruct *s = ecs_field(it, EcsStruct, 0); + + int32_t i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; + if (e != flecs_struct_finalizing && + flecs_struct_base(world, e, NULL, NULL) && + !ecs_has(world, e, EcsType)) + { + flecs_struct_finalize(world, e, &s[i]); + } + } +} + static bool flecs_member_range_overlaps( const ecs_member_value_range_t *range, @@ -92625,6 +91693,16 @@ ecs_entity_t ecs_struct_init( ecs_entity_t old_scope = ecs_set_scope(world, type); + if (ecs_has(world, type, EcsStruct)) { + EcsStruct *s = ecs_ensure(world, type, EcsStruct); + ecs_member_t *members = ecs_vec_first_t(&s->members, ecs_member_t); + int32_t m, mcount = ecs_vec_count(&s->members); + for (m = 0; m < mcount; m ++) { + ecs_os_free(ECS_CONST_CAST(char*, members[m].name)); + } + ecs_vec_clear(&s->members); + } + int i; for (i = 0; i < ECS_MEMBER_DESC_CACHE_SIZE; i ++) { const ecs_member_t *m_desc = &desc->members[i]; @@ -92664,7 +91742,7 @@ ecs_entity_t ecs_struct_init( if (i == 0) { EcsStruct *s = ecs_ensure(world, type, EcsStruct); ecs_assert(s != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_vec_init_t(NULL, &s->members, ecs_member_t, 0); + ecs_vec_init_if_t(&s->members, ecs_member_t); ecs_modified(world, type, EcsStruct); } else if (!ecs_has(world, type, EcsStruct)) { goto error; @@ -92710,17 +91788,19 @@ ecs_member_t* ecs_struct_get_member( const char *name) { const EcsStruct *s = ecs_get(world, type, EcsStruct); - if (!s) { - return NULL; + if (s) { + ecs_member_t *members = ecs_vec_first(&s->members); + int32_t i, count = ecs_vec_count(&s->members); + for (i = 0; i < count; i ++) { + if (!ecs_os_strcmp(members[i].name, name)) { + return &members[i]; + } + } } - ecs_member_t *members = ecs_vec_first(&s->members); - int32_t i, count = ecs_vec_count(&s->members); - - for (i = 0; i < count; i ++) { - if (!ecs_os_strcmp(members[i].name, name)) { - return &members[i]; - } + ecs_entity_t base = flecs_struct_base(world, type, NULL, NULL); + if (base) { + return ecs_struct_get_member(world, base, name); } return NULL; @@ -92731,18 +91811,21 @@ ecs_member_t* ecs_struct_get_nth_member( ecs_entity_t type, int32_t i) { - const EcsStruct *s = ecs_get(world, type, EcsStruct); - if (!s) { - return NULL; + ecs_entity_t base = flecs_struct_base(world, type, NULL, NULL); + if (base) { + int32_t base_count = flecs_struct_member_count(world, base); + if (i < base_count) { + return ecs_struct_get_nth_member(world, base, i); + } + i -= base_count; } - ecs_member_t *members = ecs_vec_first(&s->members); - int32_t count = ecs_vec_count(&s->members); - if (i >= count) { + const EcsStruct *s = ecs_get(world, type, EcsStruct); + if (!s || i >= ecs_vec_count(&s->members)) { return NULL; } - return &members[i]; + return ecs_vec_get_t(&s->members, ecs_member_t, i); } void flecs_meta_struct_init( @@ -92775,11 +91858,12 @@ void flecs_meta_struct_init( .type.alignment = ECS_ALIGNOF(EcsStruct) }); - ecs_set_hooks(world, EcsStruct, { + ecs_set_hooks(world, EcsStruct, { .ctor = flecs_default_ctor, .move = ecs_move(EcsStruct), .copy = ecs_copy(EcsStruct), - .dtor = ecs_dtor(EcsStruct) + .dtor = ecs_dtor(EcsStruct), + .on_set = flecs_struct_on_set }); ecs_set_hooks(world, EcsMember, { @@ -92811,12 +91895,6 @@ void flecs_meta_struct_init( #endif -/** - * @file addons/meta/type_support/units_ts.c - * @brief Units type support. - */ - - #ifdef FLECS_META /* EcsUnit lifecycle */ @@ -92853,7 +91931,6 @@ static ECS_MOVE(EcsUnit, dst, src, { static ECS_DTOR(EcsUnit, ptr, { flecs_unit_dtor(ptr); }) - /* EcsUnitPrefix lifecycle */ static void flecs_unit_prefix_dtor( @@ -93184,17 +92261,10 @@ void flecs_meta_units_init( .global_observer = true }); - } #endif -/** - * @file addons/script/expr/ast.c - * @brief Script expression AST implementation. - */ - - #ifdef FLECS_SCRIPT #define flecs_expr_ast_new(parser, T, kind)\ @@ -93553,12 +92623,6 @@ ecs_expr_cast_t* flecs_expr_cast( #endif -/** - * @file addons/script/expr/parser.c - * @brief Script expression parser. - */ - - #ifdef FLECS_SCRIPT /* From https://en.cppreference.com/w/c/language/operator_precedence */ @@ -94459,12 +93523,6 @@ char* ecs_script_string_interpolate( #endif -/** - * @file addons/script/expr/stack.c - * @brief Script expression stack implementation. - */ - - #ifdef FLECS_SCRIPT ecs_expr_value_t* flecs_expr_stack_alloc( @@ -94583,12 +93641,6 @@ void flecs_expr_stack_pop( #endif -/** - * @file addons/script/expr/util.c - * @brief Script expression utilities. - */ - - #ifdef FLECS_SCRIPT int flecs_value_copy_to( @@ -94648,7 +93700,6 @@ int flecs_value_move_to( return -1; } - int flecs_value_unary( const ecs_script_t *script, const ecs_value_t *expr, @@ -94740,7 +93791,6 @@ int flecs_value_unary( OP(left, right, result, op, ecs_f32_t, ecs_f32_t);\ } - /* Combinations + error checking */ #define ECS_BINARY_INT_OP(left, right, result, op)\ @@ -95029,12 +94079,6 @@ bool flecs_value_is_0( #endif -/** - * @file addons/script/expr/visit_eval.c - * @brief Script expression evaluation visitor. - */ - - #ifdef FLECS_SCRIPT typedef struct ecs_script_eval_ctx_t { @@ -95850,13 +94894,11 @@ int flecs_expr_member_visit_eval( ecs_os_memcpy(ECS_OFFSET(out->value.ptr, i * size), ECS_OFFSET(expr->value.ptr, node->swizzle[i]), size); } - out->value.type = node->node.type; - out->owned = false; } else { out->value.ptr = ECS_OFFSET(expr->value.ptr, node->offset); - out->value.type = node->node.type; - out->owned = false; } + out->value.type = node->node.type; + out->owned = false; flecs_expr_stack_pop(ctx->stack); return 0; @@ -96275,12 +95317,6 @@ int flecs_expr_visit_eval( #endif -/** - * @file addons/script/expr/visit_fold.c - * @brief Script expression constant folding. - */ - - #ifdef FLECS_SCRIPT static @@ -96928,12 +95964,6 @@ int flecs_expr_visit_fold( #endif -/** - * @file addons/script/expr/visit_free.c - * @brief Visitor to free expression AST. - */ - - #ifdef FLECS_SCRIPT static @@ -97161,12 +96191,6 @@ void flecs_expr_visit_free( #endif -/** - * @file addons/script/expr/visit_to_str.c - * @brief Script expression AST to string visitor. - */ - - #ifdef FLECS_SCRIPT typedef struct ecs_expr_str_visitor_t { @@ -97638,12 +96662,6 @@ void flecs_expr_to_str_buf( #endif -/** - * @file addons/script/expr/visit_type.c - * @brief Script expression type visitor. - */ - - #ifdef FLECS_SCRIPT static diff --git a/distr/flecs.h b/distr/flecs.h index dd5e52a07c..56a439bbc8 100644 --- a/distr/flecs.h +++ b/distr/flecs.h @@ -1,11 +1,5 @@ // Comment out this line when using as DLL #define flecs_STATIC -/** - * @file flecs.h - * @brief Flecs public API. - * - * This file contains the public API for Flecs. - */ #ifndef FLECS_H #define FLECS_H @@ -61,7 +55,6 @@ #define ecs_ftime_t ecs_float_t #endif - /** @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 @@ -366,23 +359,9 @@ /** @} */ -/** - * @file private/api_defines.h - * @brief Supporting defines for the public API. - * - * This file contains constants / macros that are typically not used by an - * application but support the public API, and therefore must be exposed. This - * header should not be included by itself. - */ - #ifndef FLECS_API_DEFINES_H #define FLECS_API_DEFINES_H -/** - * @file private/api_flags.h - * @brief Bitset flags used by internals. - */ - #ifndef FLECS_API_FLAGS_H #define FLECS_API_FLAGS_H @@ -390,7 +369,6 @@ extern "C" { #endif - //////////////////////////////////////////////////////////////////////////////// //// World flags //////////////////////////////////////////////////////////////////////////////// @@ -414,7 +392,6 @@ extern "C" { #define EcsOsApiLogWithTimeStamp (1u << 2) #define EcsOsApiLogWithTimeDelta (1u << 3) - //////////////////////////////////////////////////////////////////////////////// //// Entity flags (set in upper bits of ecs_record_t::row) //////////////////////////////////////////////////////////////////////////////// @@ -424,7 +401,6 @@ extern "C" { #define EcsEntityIsTraversable (1u << 29) #define EcsEntityHasDontFragment (1u << 28) - //////////////////////////////////////////////////////////////////////////////// //// ID flags (used by ecs_component_record_t::flags) //////////////////////////////////////////////////////////////////////////////// @@ -489,7 +465,6 @@ extern "C" { [(((flags) & EcsIdOnInstantiateMask) >> 6)]) #define ECS_ID_ON_INSTANTIATE_FLAG(id) (1u << (6 + ((id) - EcsOverride))) - //////////////////////////////////////////////////////////////////////////////// //// Bits set in world->non_trivial array //////////////////////////////////////////////////////////////////////////////// @@ -498,7 +473,6 @@ extern "C" { #define EcsNonTrivialIdNonFragmenting (1u << 1) #define EcsNonTrivialIdInherit (1u << 2) - //////////////////////////////////////////////////////////////////////////////// //// Iterator flags (used by ecs_iter_t::flags) //////////////////////////////////////////////////////////////////////////////// @@ -512,6 +486,7 @@ extern "C" { #define EcsIterHasCondSet (1u << 6u) /* Does the iterator have conditionally set fields. */ #define EcsIterProfile (1u << 7u) /* Profile iterator performance. */ #define EcsIterTrivialSearch (1u << 8u) /* Trivial iterator mode. */ +#define EcsIterComponentInheritance (1u << 9u) /* Query matches via component inheritance. */ #define EcsIterTrivialTest (1u << 11u) /* Trivial test mode (constrained $this). */ #define EcsIterTrivialCached (1u << 14u) /* Trivial search for cached query. */ #define EcsIterCached (1u << 15u) /* Cached query. */ @@ -521,11 +496,9 @@ extern "C" { #define EcsIterCppEach (1u << 19u) /* Uses C++ 'each' iterator. */ #define EcsIterImmutableCacheData (1u << 21u) /* Internally used by the engine to indicate immutable arrays from the cache. */ - /* Same as event flags. */ #define EcsIterTableOnly (1u << 20u) /* Result only populates the table. */ - //////////////////////////////////////////////////////////////////////////////// //// Event flags (used by ecs_event_desc_t::flags) //////////////////////////////////////////////////////////////////////////////// @@ -533,7 +506,6 @@ extern "C" { #define EcsEventTableOnly (1u << 20u) /* Table event (no data, same as iter flags). */ #define EcsEventNoOnSet (1u << 16u) /* Don't emit OnSet for inherited IDs. */ - //////////////////////////////////////////////////////////////////////////////// //// Query flags (used by ecs_query_t::flags) //////////////////////////////////////////////////////////////////////////////// @@ -560,6 +532,7 @@ extern "C" { #define EcsQueryNested (1u << 29u) /* Query created by a query (for observer, cache). */ #define EcsQueryCacheWithFilter (1u << 30u) #define EcsQueryValid (1u << 31u) +#define EcsQueryHasComponentInheritance (1u << 5u) /* Query matches via component inheritance (low free bit; 11..31 exhausted). */ //////////////////////////////////////////////////////////////////////////////// //// Term flags (used by ecs_term_t::flags_) @@ -580,7 +553,6 @@ extern "C" { #define EcsTermDontFragment (1u << 12) #define EcsTermNonFragmentingChildOf (1u << 13) - //////////////////////////////////////////////////////////////////////////////// //// Observer flags (used by ecs_observer_t::flags) //////////////////////////////////////////////////////////////////////////////// @@ -654,7 +626,6 @@ extern "C" { #endif - #if defined(_WIN32) || defined(_MSC_VER) #define ECS_TARGET_WINDOWS #elif defined(__COSMOCC__) @@ -859,8 +830,6 @@ extern "C" { #endif - - #ifdef __cplusplus extern "C" { #endif @@ -877,7 +846,6 @@ extern "C" { #define FLECS_DBG_API #endif - //////////////////////////////////////////////////////////////////////////////// //// Language support defines //////////////////////////////////////////////////////////////////////////////// @@ -976,6 +944,14 @@ typedef struct ecs_allocator_t ecs_allocator_t; #define FLECS_ALWAYS_INLINE #endif +#if defined(ECS_TARGET_CLANG) || defined(ECS_TARGET_GCC) + #define FLECS_NOINLINE __attribute__((noinline)) +#elif defined(ECS_TARGET_MSVC) + #define FLECS_NOINLINE __declspec(noinline) +#else + #define FLECS_NOINLINE +#endif + #ifndef FLECS_NO_DEPRECATED_WARNINGS #if defined(ECS_TARGET_GNU) #define ECS_DEPRECATED(msg) __attribute__((deprecated(msg))) @@ -1039,7 +1015,6 @@ typedef struct ecs_allocator_t ecs_allocator_t; #define ecs_query_t_magic (0x65637375) #define ecs_observer_t_magic (0x65637362) - //////////////////////////////////////////////////////////////////////////////// //// Entity ID macros //////////////////////////////////////////////////////////////////////////////// @@ -1073,7 +1048,6 @@ typedef struct ecs_allocator_t ecs_allocator_t; /** Translate a C type to an ID. */ #define ecs_id(T) FLECS_ID##T##ID_ - //////////////////////////////////////////////////////////////////////////////// //// Utilities for working with pair identifiers //////////////////////////////////////////////////////////////////////////////// @@ -1093,7 +1067,6 @@ typedef struct ecs_allocator_t ecs_allocator_t; #define flecs_poly_id(tag) ecs_pair(ecs_id(EcsPoly), tag) - //////////////////////////////////////////////////////////////////////////////// //// Debug macros //////////////////////////////////////////////////////////////////////////////// @@ -1106,7 +1079,6 @@ typedef struct ecs_allocator_t ecs_allocator_t; #define ECS_TABLE_UNLOCK(world, table) #endif - //////////////////////////////////////////////////////////////////////////////// //// Convenience macros for ctor, dtor, move, and copy //////////////////////////////////////////////////////////////////////////////// @@ -1193,7 +1165,6 @@ typedef struct ecs_allocator_t ecs_allocator_t; #endif - /** * @defgroup core_types Core API Types * Types for core API objects. @@ -1360,15 +1331,9 @@ typedef struct ecs_table_record_t ecs_table_record_t; /** @} */ -/** - * @file datastructures/vec.h - * @brief Vector with allocator support. - */ - #ifndef FLECS_VEC_H #define FLECS_VEC_H - #ifdef __cplusplus extern "C" { #endif @@ -1909,15 +1874,9 @@ void* ecs_vec_last( #endif -/** - * @file datastructures/sparse.h - * @brief Sparse set data structure. - */ - #ifndef FLECS_SPARSE_H #define FLECS_SPARSE_H - #ifdef __cplusplus extern "C" { #endif @@ -2351,15 +2310,9 @@ void* ecs_sparse_get( #endif -/** - * @file datastructures/block_allocator.h - * @brief Block allocator. - */ - #ifndef FLECS_BLOCK_ALLOCATOR_H #define FLECS_BLOCK_ALLOCATOR_H - /** Forward declaration of map type. */ typedef struct ecs_map_t ecs_map_t; @@ -2544,11 +2497,6 @@ void* flecs_bdup( #endif -/** - * @file datastructures/stack_allocator.h - * @brief Stack allocator. - */ - #ifndef FLECS_STACK_ALLOCATOR_H #define FLECS_STACK_ALLOCATOR_H @@ -2691,15 +2639,9 @@ void flecs_stack_restore_cursor( #endif -/** - * @file datastructures/map.h - * @brief Map data structure. - */ - #ifndef FLECS_MAP_H #define FLECS_MAP_H - #ifdef __cplusplus extern "C" { #endif @@ -2978,15 +2920,9 @@ void ecs_map_copy( #endif -/** - * @file datastructures/allocator.h - * @brief Allocator that returns memory objects of any size. - */ - #ifndef FLECS_ALLOCATOR_H #define FLECS_ALLOCATOR_H - FLECS_DBG_API extern int64_t ecs_block_allocator_alloc_count; /**< Block allocator allocation count. */ FLECS_DBG_API extern int64_t ecs_block_allocator_free_count; /**< Block allocator free count. */ FLECS_DBG_API extern int64_t ecs_stack_allocator_alloc_count; /**< Stack allocator allocation count. */ @@ -3211,15 +3147,9 @@ void flecs_free( #endif -/** - * @file datastructures/strbuf.h - * @brief Utility for constructing strings. - */ - #ifndef FLECS_STRBUF_H_ #define FLECS_STRBUF_H_ - #ifdef __cplusplus extern "C" { #endif @@ -3471,18 +3401,6 @@ int32_t ecs_strbuf_written( #endif -/** - * @file os_api.h - * @brief Operating system abstraction API. - * - * This file contains the operating system abstraction API. The flecs core - * library avoids OS/runtime-specific API calls as much as possible. Instead, it - * provides an interface that can be implemented by applications. - * - * Examples of how to implement this interface can be found in the - * examples/os_api folder. - */ - #ifndef FLECS_OS_API_H #define FLECS_OS_API_H @@ -4035,7 +3953,6 @@ void ecs_os_set_api_defaults(void); #define ecs_os_ldec(v) #endif - #ifdef ECS_TARGET_MINGW /* mingw bug: without this, a conversion error is thrown, but isnan/isinf should * accept float, double, and long double. */ @@ -4062,7 +3979,6 @@ void ecs_os_set_api_defaults(void); * \endcond */ - /* Logging */ /** Log at debug level. @@ -4270,7 +4186,6 @@ bool ecs_os_has_modules(void); #endif - #ifdef __cplusplus extern "C" { #endif @@ -4282,7 +4197,6 @@ extern "C" { * @{ */ - /** * @defgroup function_types Function types. * Function callback types. @@ -4651,7 +4565,6 @@ struct ecs_observer_t { #define ECS_TYPE_HOOK_CMP ECS_CAST(ecs_flags32_t, 1 << 8) #define ECS_TYPE_HOOK_EQUALS ECS_CAST(ecs_flags32_t, 1 << 9) - /* Flags that can be used to set/check which hooks of a type are invalid */ #define ECS_TYPE_HOOK_CTOR_ILLEGAL ECS_CAST(ecs_flags32_t, 1 << 10) #define ECS_TYPE_HOOK_DTOR_ILLEGAL ECS_CAST(ecs_flags32_t, 1 << 12) @@ -4667,7 +4580,6 @@ struct ecs_observer_t { /* Internal debug flag that indicates type hooks have been invoked */ #define ECS_TYPE_HOOK_IN_USE ECS_CAST(ecs_flags32_t, 1 << 21) - /* All valid hook flags */ #define ECS_TYPE_HOOKS (ECS_TYPE_HOOK_CTOR|ECS_TYPE_HOOK_DTOR|\ ECS_TYPE_HOOK_COPY|ECS_TYPE_HOOK_MOVE|ECS_TYPE_HOOK_COPY_CTOR|\ @@ -4758,19 +4670,9 @@ struct ecs_type_info_t { const char *name; /**< Type name. */ }; -/** - * @file private/api_types.h - * @brief Supporting types for the public API. - * - * This file contains types that are typically not used by an application but - * support the public API, and therefore must be exposed. This header should not - * be included by itself. - */ - #ifndef FLECS_API_TYPES_H #define FLECS_API_TYPES_H - #ifdef __cplusplus extern "C" { #endif @@ -4925,20 +4827,9 @@ typedef struct ecs_commands_t { #endif -/** - * @file private/api_support.h - * @brief Support functions and constants. - * - * Supporting types and functions that need to be exposed either in support of - * the public API or for unit tests. - * - * Operations may change without warning. - */ - #ifndef FLECS_API_SUPPORT_H #define FLECS_API_SUPPORT_H - #ifdef __cplusplus extern "C" { #endif @@ -5380,15 +5271,9 @@ int flecs_journal_get_counter(void); #endif -/** - * @file datastructures/hashmap.h - * @brief Hashmap data structure. - */ - #ifndef FLECS_HASHMAP_H #define FLECS_HASHMAP_H - #ifdef __cplusplus extern "C" { #endif @@ -5621,17 +5506,9 @@ void* flecs_hashmap_next_( #endif -/** - * @file private/api_internals.h - * @brief Access to internal data structures. - * - * Operations may change without warning. - */ - #ifndef FLECS_API_INTERNALS_H #define FLECS_API_INTERNALS_H - #ifdef __cplusplus extern "C" { #endif @@ -6031,7 +5908,6 @@ FLECS_ALWAYS_INLINE ecs_table_t *flecs_table_traverse_add( #endif - /** Utility to hold a value of a dynamic type. */ typedef struct ecs_value_t { ecs_entity_t type; /**< Type of value. */ @@ -6228,7 +6104,6 @@ struct ecs_iter_t { ecs_iter_t *chain_it; /**< Optional, allows for creating iterator chains. */ }; - /** Query must match prefabs. * Can be combined with other query flags on the ecs_query_desc_t::flags field. * \ingroup queries @@ -6292,7 +6167,6 @@ struct ecs_iter_t { */ #define EcsQueryGroupByDesc (1u << 10u) - /** Used with ecs_query_init(). * * \ingroup queries @@ -6477,7 +6351,6 @@ typedef struct ecs_event_desc_t { ecs_flags32_t flags; } ecs_event_desc_t; - /** * @defgroup misc_types Miscellaneous types * Types used to create entities, observers, queries, and more. @@ -6649,10 +6522,6 @@ typedef struct EcsTreeSpawner { /* Only include deprecated definitions if deprecated addon is required */ #ifdef FLECS_DEPRECATED -/** - * @file addons/deprecated.h - * @brief The deprecated addon contains deprecated operations. - */ #ifdef FLECS_DEPRECATED @@ -8995,7 +8864,6 @@ int32_t ecs_count_id( /** @} */ - /** * @defgroup paths Entity Names * Functions for working with entity names and paths. @@ -10840,6 +10708,24 @@ void* ecs_field_w_size( size_t size, int8_t index); +/** Get data for a field matched through component inheritance. + * This operation is like ecs_field_w_size(), but is used when a field is + * matched on a component that is derived from the queried (base) type. Because + * the stored (derived) component can be larger than the requested (base) type, + * the returned pointer is computed using the stride of the stored component. To + * iterate the values, use the stride returned by ecs_field_stride(). + * + * @param it The iterator. + * @param size The size of the field type. + * @param index The index of the field. + * @return A pointer to the data of the field. + */ +FLECS_API +void* ecs_base_field_w_size( + const ecs_iter_t *it, + size_t size, + int8_t index); + /** Get data for a field at a specified row. * This operation should be used instead of ecs_field_w_size() for sparse * component fields. This operation should be called for each returned row in a @@ -10956,6 +10842,19 @@ size_t ecs_field_size( const ecs_iter_t *it, int8_t index); +/** Return the storage stride of a field. + * Like ecs_field_size(), but for a field matched through component inheritance + * returns the size of the stored (derived) component, i.e. the array stride. + * + * @param it The iterator. + * @param index The index of the field in the iterator. + * @return The storage stride for the field. + */ +FLECS_API +size_t ecs_field_stride( + const ecs_iter_t *it, + int8_t index); + /** Test whether the field is matched on self. * This operation returns whether the field is matched on the currently iterated * entity. This function will return false when the field is owned by another @@ -11339,7 +11238,6 @@ bool ecs_commit( const ecs_type_t *added, const ecs_type_t *removed); - /** Search for a component in a table type. * This operation returns the index of the first occurrence of the component in the * table type. The component may be a pair or a wildcard. @@ -11693,11 +11591,6 @@ int ecs_value_move_ctor( * @} */ -/** - * @file addons/flecs_c.h - * @brief Extends the core API with convenience macros for C applications. - */ - #ifndef FLECS_C_ #define FLECS_C_ @@ -12422,6 +12315,13 @@ int ecs_value_move_ctor( #define ecs_field(it, T, index)\ (ECS_CAST(T*, ecs_field_w_size(it, sizeof(T), index))) +/** Get field data for a component matched through component inheritance. + * Use this instead of ecs_field() when a field is matched on a component that + * is derived from the queried (base) type. The data is iterated with the stride + * returned by ecs_field_stride(). */ +#define ecs_base_field(it, T, index)\ + (ECS_CAST(T*, ecs_base_field_w_size(it, sizeof(T), index))) + /** Get field data for a self-owned component. */ #define ecs_field_self(it, T, index)\ (ECS_CAST(T*, ecs_field_self_w_size(it, sizeof(T), index))) @@ -12635,18 +12535,10 @@ int ecs_value_move_ctor( #endif // FLECS_C_ - #ifdef __cplusplus } #endif -/** - * @file private/addons.h - * @brief Include enabled addons. - * - * This file should only be included by the main flecs.h header. - */ - #ifndef FLECS_ADDONS_H #define FLECS_ADDONS_H @@ -12716,32 +12608,6 @@ int ecs_value_move_ctor( #endif /* Always included; if disabled, functions are replaced with dummy macros. */ -/** - * @file addons/log.h - * @brief Logging addon. - * - * The logging addon provides an API for (debug) tracing and reporting errors - * at various levels. When enabled, the logging addon can provide more detailed - * information about the state of the ECS and any errors that may occur. - * - * The logging addon can be disabled to reduce the footprint of the library, but - * limits information logged to only file, line, and error code. - * - * When enabled, the logging addon can be configured to exclude levels of tracing - * from the build to reduce the impact on performance. By default, all debug - * tracing is enabled for debug builds, tracing is enabled for release builds. - * - * Applications can change the logging level at runtime with ecs_log_set_level(), - * but what is actually logged depends on what is compiled (when compiled - * without debug tracing, setting the runtime level to debug won't have an - * effect). - * - * The logging addon uses the OS API log_() function for all tracing. - * - * Note that even when the logging addon is not enabled, its header and source must - * be included in a build. To prevent unused variable warnings in the code, some - * API functions are included when the addon is disabled, but have empty bodies. - */ #ifndef FLECS_LOG_H #define FLECS_LOG_H @@ -12837,7 +12703,6 @@ const char* ecs_strerror( #endif // FLECS_LOG - //////////////////////////////////////////////////////////////////////////////// //// Logging functions (do nothing when logging is disabled) //////////////////////////////////////////////////////////////////////////////// @@ -12998,7 +12863,6 @@ void ecs_parser_warningv_( const char *fmt, va_list args); - //////////////////////////////////////////////////////////////////////////////// //// Logging macros //////////////////////////////////////////////////////////////////////////////// @@ -13059,7 +12923,6 @@ void ecs_parser_warningv_( #endif // !defined(FLECS_NDEBUG) #endif // !(defined(FLECS_LOG_0) || defined(FLECS_LOG_1) || defined(FLECS_LOG_2) || defined(FLECS_LOG_3)) - /* Define/undefine macros based on compiled-in tracing level. This can optimize * out tracing statements from a build, which improves performance. */ @@ -13222,7 +13085,6 @@ void ecs_parser_warningv_( #define ecs_san_assert(condition, error_code, ...) #endif - /** Silence dead code/unused label warnings when compiling without checks. */ #define ecs_dummy_check\ if ((false)) {\ @@ -13451,7 +13313,6 @@ char* ecs_log_stop_capture(void); /** Invalid from worker error code. */ #define ECS_INVALID_FROM_WORKER (72) - //////////////////////////////////////////////////////////////////////////////// //// Used when logging with colors is enabled //////////////////////////////////////////////////////////////////////////////// @@ -13487,7 +13348,6 @@ char* ecs_log_stop_capture(void); #endif // FLECS_LOG_H - /* Handle addon dependencies that need declarations to be visible in the header. */ #ifdef FLECS_STATS #ifndef FLECS_PIPELINE @@ -13508,15 +13368,6 @@ char* ecs_log_stop_capture(void); #ifdef FLECS_NO_APP #error "FLECS_NO_APP failed: APP is required by other addons" #endif -/** - * @file addons/app.h - * @brief App addon. - * - * The app addon is a wrapper around the application's main loop. Its main - * purpose is to provide a hook to modules that need to take control of the - * main loop, as is for example the case with native applications that use - * Emscripten with WebGL. - */ #ifdef FLECS_APP @@ -13635,24 +13486,6 @@ int ecs_app_set_frame_action( #ifdef FLECS_NO_HTTP #error "FLECS_NO_HTTP failed: HTTP is required by other addons" #endif -/** - * @file addons/http.h - * @brief HTTP addon. - * - * Minimalistic HTTP server that can receive and reply to simple HTTP requests. - * The main goal of this addon is to enable remotely connecting to a running - * Flecs application (for example, with a web-based UI) and request and visualize - * data from the ECS world. - * - * Each server instance creates a single thread used for receiving requests. - * Received requests are enqueued and handled when the application calls - * ecs_http_server_dequeue(). This increases the latency of request handling vs. - * responding directly in the receive thread, but is better suited for - * retrieving data from ECS applications, as requests can be processed by an ECS - * system without having to lock the world. - * - * This server is intended to be used in a development environment. - */ #ifdef FLECS_HTTP @@ -13900,15 +13733,6 @@ const char* ecs_http_get_param( #ifdef FLECS_NO_REST #error "FLECS_NO_REST failed: REST is required by other addons" #endif -/** - * @file addons/rest.h - * @brief REST API addon. - * - * A small REST API that uses the HTTP server and JSON serializer to provide - * access to application data for remote applications. - * - * A description of the API can be found in docs/FlecsRemoteApi.md. - */ #ifdef FLECS_REST @@ -14014,13 +13838,6 @@ void FlecsRestImport( #ifdef FLECS_NO_TIMER #error "FLECS_NO_TIMER failed: TIMER is required by other addons" #endif -/** - * @file addons/timer.h - * @brief Timer module. - * - * Timers can be used to trigger actions at periodic or one-shot intervals. They - * are typically used together with systems and pipelines. - */ #ifdef FLECS_TIMER @@ -14065,7 +13882,6 @@ typedef struct EcsRateFilter { ecs_ftime_t time_elapsed; /**< Time elapsed since last tick. */ } EcsRateFilter; - /** 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 @@ -14251,7 +14067,6 @@ void ecs_set_tick_source( ecs_entity_t system, ecs_entity_t tick_source); - //////////////////////////////////////////////////////////////////////////////// //// Module //////////////////////////////////////////////////////////////////////////////// @@ -14284,20 +14099,6 @@ void FlecsTimerImport( #ifdef FLECS_NO_PIPELINE #error "FLECS_NO_PIPELINE failed: PIPELINE is required by other addons" #endif -/** - * @file addons/pipeline.h - * @brief Pipeline module. - * - * The pipeline module provides support for running systems automatically and - * on multiple threads. A pipeline is a collection of tags that can be added to - * systems. When run, a pipeline will query for all systems that have the tags - * that belong to a pipeline, and run them. - * - * The module defines a number of built-in tags (EcsPreUpdate, EcsOnUpdate, - * EcsPostUpdate, etc.) that are registered with the built-in pipeline. The - * built-in pipeline is run by default when calling ecs_progress(). An - * application can set a custom pipeline with the ecs_set_pipeline() function. - */ #ifdef FLECS_PIPELINE @@ -14516,7 +14317,6 @@ void ecs_run_pipeline( ecs_entity_t pipeline, ecs_ftime_t delta_time); - //////////////////////////////////////////////////////////////////////////////// //// Threading //////////////////////////////////////////////////////////////////////////////// @@ -14599,14 +14399,6 @@ void FlecsPipelineImport( #ifdef FLECS_NO_SYSTEM #error "FLECS_NO_SYSTEM failed: SYSTEM is required by other addons" #endif -/** - * @file addons/system.h - * @brief System module. - * - * The system module allows for creating and running systems. A system is a - * query in combination with a callback function. In addition, systems have - * support for time management and can be monitored by the stats addon. - */ #ifdef FLECS_SYSTEM @@ -14971,19 +14763,6 @@ void FlecsSystemImport( #ifdef FLECS_NO_STATS #error "FLECS_NO_STATS failed: STATS is required by other addons" #endif -/** - * @file addons/stats.h - * @brief Statistics addon. - * - * The stats addon tracks high-resolution statistics for the world, systems, and - * pipelines. The addon can be used as an API where an application calls - * functions to obtain statistics directly and as a module where statistics are - * automatically tracked. The latter is required for statistics tracking in the - * explorer. - * - * When the addon is imported as a module, statistics are tracked for each frame, - * second, minute, hour, day, and week with 60 data points per tier. - */ #ifdef FLECS_STATS @@ -15712,7 +15491,6 @@ FLECS_API ecs_size_t ecs_memory_get( const ecs_world_t *world); - /** Stats module import function. * Usage: * @code @@ -15741,13 +15519,6 @@ void FlecsStatsImport( #ifdef FLECS_NO_METRICS #error "FLECS_NO_METRICS failed: METRICS is required by other addons" #endif -/** - * @file addons/metrics.h - * @brief Metrics module. - * - * The metrics module extracts metrics from components and makes them available - * through a unified component interface. - */ #ifdef FLECS_METRICS @@ -15934,14 +15705,6 @@ void FlecsMetricsImport( #ifdef FLECS_NO_ALERTS #error "FLECS_NO_ALERTS failed: ALERTS is required by other addons" #endif -/** - * @file addons/alerts.h - * @brief Alerts module. - * - * The alerts module enables applications to register alerts for when certain - * conditions are met. Alerts are registered as queries, and automatically - * become active when entities match the alert query. - */ #ifdef FLECS_ALERTS @@ -16165,15 +15928,6 @@ void FlecsAlertsImport( #ifdef FLECS_NO_JSON #error "FLECS_NO_JSON failed: JSON is required by other addons" #endif -/** - * @file addons/json.h - * @brief JSON parser addon. - * - * Parse expression strings into component values. Entity identifiers, - * enumerations, and bitmasks are encoded as strings. - * - * See docs/FlecsRemoteApi.md for a description of the JSON format. - */ #ifdef FLECS_JSON @@ -16626,29 +16380,6 @@ int ecs_world_to_json_buf( #ifdef FLECS_NO_UNITS #error "FLECS_NO_UNITS failed: UNITS is required by other addons" #endif -/** - * @file addons/units.h - * @brief Units module. - * - * Built-in standard units. The units addon is not imported by default, even if - * the addon is included in the build. To import the module, do: - * - * In C: - * - * @code - * ECS_IMPORT(world, FlecsUnits); - * @endcode - * - * In C++: - * - * @code - * world.import(); - * @endcode - * - * As a result this module behaves just like an application-defined module, - * which means that the ids generated for the entities inside the module are not - * fixed, and depend on the order in which the module is imported. - */ #ifdef FLECS_UNITS @@ -16951,7 +16682,6 @@ FLECS_API extern ecs_entity_t EcsColorCss; /**< ColorCss unit. */ /** @} */ - FLECS_API extern ecs_entity_t EcsAcceleration; /**< Acceleration unit. */ FLECS_API extern ecs_entity_t EcsPercentage; /**< Percentage unit. */ FLECS_API extern ecs_entity_t EcsBel; /**< Bel unit. */ @@ -16989,10 +16719,6 @@ void FlecsUnitsImport( #ifdef FLECS_NO_SCRIPT_MATH #error "FLECS_NO_SCRIPT_MATH failed: SCRIPT_MATH is required by other addons" #endif -/** - * @file addons/script_math.h - * @brief Math functions for Flecs script. - */ #ifdef FLECS_SCRIPT_MATH @@ -17066,12 +16792,6 @@ void FlecsScriptMathImport( #ifdef FLECS_NO_SCRIPT #error "FLECS_NO_SCRIPT failed: SCRIPT is required by other addons" #endif -/** - * @file addons/script.h - * @brief Flecs script module. - * - * For script examples, see examples/script. - */ #ifdef FLECS_SCRIPT @@ -17395,7 +17115,6 @@ char* ecs_script_ast_to_str( ecs_script_t *script, bool colors); - /* Managed scripts (script associated with entity that outlives the function) */ /** Used with ecs_script_init(). */ @@ -17451,7 +17170,6 @@ void ecs_script_clear( ecs_entity_t script, ecs_entity_t instance); - /* Script variables */ /** Create new variable scope. @@ -17644,7 +17362,6 @@ void ecs_script_vars_from_iter( ecs_script_vars_t *vars, int offset); - /* Standalone expression evaluation */ /** Used with ecs_expr_run(). */ @@ -17756,7 +17473,6 @@ char* ecs_script_string_interpolate( const char *str, const ecs_script_vars_t *vars); - /* Global const variables */ /** Used with ecs_const_var_init(). */ @@ -17789,7 +17505,6 @@ ecs_entity_t ecs_const_var_init( #define ecs_const_var(world, ...)\ ecs_const_var_init(world, &(ecs_const_var_desc_t)__VA_ARGS__) - /** Return the value for a const variable. * This returns the value for a const variable that is created either with * ecs_const_var_init(), or in a script with "export const v: ...". @@ -17917,7 +17632,6 @@ ecs_entity_t ecs_method_init( #define ecs_method(world, ...)\ ecs_method_init(world, &(ecs_function_desc_t)__VA_ARGS__) - /* Value serialization */ /** Serialize value into expression string. @@ -18014,15 +17728,6 @@ void FlecsScriptImport( #ifdef FLECS_NO_DOC #error "FLECS_NO_DOC failed: DOC is required by other addons" #endif -/** - * @file addons/doc.h - * @brief Doc module. - * - * The doc module allows for documenting entities (and thus components, systems) - * by adding brief and/or detailed descriptions as components. Documentation - * added with the doc module can be retrieved at runtime, and can be used by - * tooling such as UIs or documentation frameworks. - */ #ifdef FLECS_DOC @@ -18315,61 +18020,6 @@ void FlecsDocImport( #ifdef FLECS_NO_META #error "FLECS_NO_META failed: META is required by other addons" #endif -/** - * @file addons/meta.h - * @brief Meta addon. - * - * The meta addon enables reflecting on component data. Types are stored as - * entities, with components that store the reflection data. A type has at least - * two components: - * - * - EcsComponent: core component, contains size and alignment - * - EcsType: component that indicates what kind of type the entity is - * - * Additionally, the type may have an additional component that contains the - * reflection data for the type. For example, structs have these components: - * - * - EcsComponent - * - EcsType - * - EcsStruct - * - * Structs can be populated by adding child entities with the EcsMember - * component. Adding a child with a Member component to an entity will - * automatically add the EcsStruct component to the parent. - * - * Enums and bitmasks can be populated by adding child entities with the Constant - * tag. By default, constants are automatically assigned values when they are - * added to the enum or bitmask. The parent entity must have the EcsEnum or - * EcsBitmask component before adding the constants. - * - * To create enum constants with a manual value, set (Constant, i32) to the - * desired value. To create bitmask constants with a manual value, set - * (Constant, u32) to the desired value. Constants with manual values should not - * conflict with other constants. - * - * The _init APIs are convenience wrappers around creating the entities and - * components for the types. - * - * When a type is created, it automatically receives the EcsComponent and - * EcsType components. The former means that the resulting type can be - * used as a regular component: - * - * @code - * // Create Position type - * ecs_entity_t pos = ecs_struct_init(world, &(ecs_struct_desc_t){ - * .entity.name = "Position", - * .members = { - * {"x", ecs_id(ecs_f32_t)}, - * {"y", ecs_id(ecs_f32_t)} - * } - * }); - * - * // Create entity with Position component - * ecs_entity_t e = ecs_new_w_id(world, pos); - * @endcode - * - * Type entities do not have to be named. - */ #ifdef FLECS_META @@ -18643,7 +18293,6 @@ typedef struct EcsVector { ecs_entity_t type; /**< Element type. */ } EcsVector; - /* Opaque type support */ #if !defined(__cplusplus) || !defined(FLECS_CPP) @@ -18704,7 +18353,6 @@ typedef int (*ecs_meta_serialize_t)( const ecs_serializer_t *ser, const void *src); /**< Pointer to value to serialize. */ - /** Callback invoked to serialize an opaque struct member. */ typedef int (*ecs_meta_serialize_member_t)( const ecs_serializer_t *ser, @@ -18717,7 +18365,6 @@ typedef int (*ecs_meta_serialize_element_t)( const void *src, /**< Pointer to value to serialize. */ size_t elem); /**< Element index to serialize. */ - /** Opaque type reflection data. * An opaque type is a type with an unknown layout that can be mapped to a type * known to the reflection framework. See the opaque type reflection examples. @@ -18804,7 +18451,6 @@ typedef struct EcsOpaque { size_t count); } EcsOpaque; - /* Units */ /** Helper type to describe translation between two units. Note that this @@ -18834,7 +18480,6 @@ typedef struct EcsUnitPrefix { ecs_unit_translation_t translation; /**< Translation of prefix. */ } EcsUnitPrefix; - /* Serializer utilities */ /** Serializer instruction opcodes. @@ -18907,7 +18552,6 @@ typedef struct EcsTypeSerializer { ecs_vec_t ops; /**< vector */ } EcsTypeSerializer; - /* Deserializer utilities */ /** Maximum level of type nesting. @@ -19358,7 +19002,6 @@ ecs_entity_t ecs_primitive_init( ecs_world_t *world, const ecs_primitive_desc_t *desc); - /** Used with ecs_enum_init(). */ typedef struct ecs_enum_desc_t { ecs_entity_t entity; /**< Existing entity to use for type (optional). */ @@ -19377,7 +19020,6 @@ ecs_entity_t ecs_enum_init( ecs_world_t *world, const ecs_enum_desc_t *desc); - /** Used with ecs_bitmask_init(). */ typedef struct ecs_bitmask_desc_t { ecs_entity_t entity; /**< Existing entity to use for type (optional). */ @@ -19395,7 +19037,6 @@ ecs_entity_t ecs_bitmask_init( ecs_world_t *world, const ecs_bitmask_desc_t *desc); - /** Used with ecs_array_init(). */ typedef struct ecs_array_desc_t { ecs_entity_t entity; /**< Existing entity to use for type (optional). */ @@ -19414,7 +19055,6 @@ ecs_entity_t ecs_array_init( ecs_world_t *world, const ecs_array_desc_t *desc); - /** Used with ecs_vector_init(). */ typedef struct ecs_vector_desc_t { ecs_entity_t entity; /**< Existing entity to use for type (optional). */ @@ -19432,7 +19072,6 @@ ecs_entity_t ecs_vector_init( ecs_world_t *world, const ecs_vector_desc_t *desc); - /** Used with ecs_struct_init(). */ typedef struct ecs_struct_desc_t { ecs_entity_t entity; /**< Existing entity to use for type (optional). */ @@ -19524,7 +19163,6 @@ ecs_entity_t ecs_opaque_init( ecs_world_t *world, const ecs_opaque_desc_t *desc); - /** Used with ecs_unit_init(). */ typedef struct ecs_unit_desc_t { /** Existing entity to associate with unit (optional). */ @@ -19565,7 +19203,6 @@ ecs_entity_t ecs_unit_init( ecs_world_t *world, const ecs_unit_desc_t *desc); - /** Used with ecs_unit_prefix_init(). */ typedef struct ecs_unit_prefix_desc_t { /** Existing entity to associate with unit prefix (optional). */ @@ -19589,7 +19226,6 @@ ecs_entity_t ecs_unit_prefix_init( ecs_world_t *world, const ecs_unit_prefix_desc_t *desc); - /** Create a new quantity. * * @param world The world. @@ -19643,7 +19279,6 @@ ecs_entity_t ecs_quantity_init( #define ecs_quantity(world, ...)\ ecs_quantity_init(world, &(ecs_entity_desc_t) __VA_ARGS__ ) - /** Meta module import function. * Usage: * @code @@ -19660,11 +19295,6 @@ void FlecsMetaImport( } #endif -/** - * @file addons/meta_c.h - * @brief Utility macros for populating reflection data in C. - */ - #ifdef FLECS_META /** @@ -19730,7 +19360,6 @@ int ecs_meta_from_desc( ecs_type_kind_t kind, const char *desc); - /** \cond * Private utilities to switch between meta IMPL, DECLARE and EXTERN variants. */ @@ -19760,7 +19389,6 @@ int ecs_meta_from_desc( #define ECS_STRUCT_EXTERN(name, type_desc)\ extern ECS_COMPONENT_DECLARE(name) - /* ECS_ENUM implementation */ #define ECS_ENUM_TYPE(name, ...)\ typedef enum name __VA_ARGS__ name @@ -19780,7 +19408,6 @@ int ecs_meta_from_desc( #define ECS_ENUM_EXTERN(name, type_desc)\ extern ECS_COMPONENT_DECLARE(name) - /* ECS_BITMASK implementation */ #define ECS_BITMASK_TYPE(name, ...)\ typedef enum name __VA_ARGS__ name @@ -19837,7 +19464,6 @@ int ecs_meta_from_desc( #endif // FLECS_META - #endif /** @} */ @@ -19850,10 +19476,6 @@ int ecs_meta_from_desc( #ifdef FLECS_NO_OS_API_IMPL #error "FLECS_NO_OS_API_IMPL failed: OS_API_IMPL is required by other addons" #endif -/** - * @file addons/os_api_impl.h - * @brief Default OS API implementation. - */ #ifdef FLECS_OS_API_IMPL @@ -19895,14 +19517,6 @@ void ecs_set_os_api_impl(void); #ifdef FLECS_NO_MODULE #error "FLECS_NO_MODULE failed: MODULE is required by other addons" #endif -/** - * @file addons/module.h - * @brief Module addon. - * - * The module addon allows for creating and importing modules. Flecs modules - * enable applications to organize components and systems into reusable units of - * code that can easily be used across projects. - */ #ifdef FLECS_MODULE @@ -20037,14 +19651,6 @@ ecs_entity_t ecs_module_init( #ifdef FLECS_NO_CPP #error "FLECS_NO_CPP failed: CPP is required by other addons" #endif -/** - * @file addons/flecs_cpp.h - * @brief C++ utility functions. - * - * This header contains utility functions that are accessible from both C and - * C++ code. These functions are not part of the public API and are not meant - * to be used directly by applications. - */ #ifdef FLECS_CPP @@ -20277,12 +19883,7 @@ ecs_member_t* ecs_cpp_last_member( #endif // FLECS_CPP - #ifdef __cplusplus -/** - * @file addons/cpp/flecs.hpp - * @brief Flecs C++17 API. - */ #pragma once @@ -20326,10 +19927,6 @@ struct each_delegate; } // namespace flecs // Types imported from C API -/** - * @file addons/cpp/c_types.hpp - * @brief Aliases for types/constants from C API. - */ #pragma once @@ -20573,15 +20170,7 @@ static const flecs::entity_t ScopeClose = EcsScopeClose; } - // C++ utilities -/** - * @file addons/cpp/utils/utils.hpp - * @brief 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: @@ -20732,7 +20321,6 @@ using transcribe_pointer_t = conditional_t::value, Dst*, Dst>; template using transcribe_cvp_t = transcribe_cv_t< Src, transcribe_pointer_t< Src, Dst> >; - /** Convenience enable_if alias using int as default type. * This enables writing code that's a bit less cluttered when * the templates are used in a template declaration: @@ -20761,14 +20349,6 @@ struct always_false { } // namespace flecs -/** - * @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 { /** Iterator for flecs::array. */ @@ -20898,11 +20478,6 @@ struct array> final { } -/** - * @file addons/cpp/utils/string.hpp - * @brief String utility that doesn't implicitly allocate memory. - */ - namespace flecs { struct string_view; @@ -21074,14 +20649,6 @@ struct string_view : string { } -/** - * @file addons/cpp/utils/enum.hpp - * @brief Compile-time enum reflection utilities. - * - * Discover at compile time the valid enumeration constants for an enumeration type - * and their names. This is used to automatically register enum constants. - */ - #include // 126, so that FLECS_ENUM_MAX_COUNT is 127, which is the largest value @@ -21697,11 +21264,6 @@ enum_data enum_type(flecs::world_t *world) { } // namespace flecs -/** - * @file addons/cpp/utils/stringstream.hpp - * @brief Wrapper around ecs_strbuf_t that provides a simple stringstream-like API. - */ - namespace flecs { /** Simple stringstream wrapper around ecs_strbuf_t. */ @@ -21752,13 +21314,6 @@ struct stringstream { } -/** - * @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 - */ - #pragma once namespace flecs { @@ -21861,7 +21416,6 @@ struct function_traits } // namespace _ - /** Trait to check if a type is callable. */ template struct is_callable { @@ -21924,7 +21478,6 @@ using second_arg_t = typename second_arg::type; } // namespace flecs - namespace flecs { namespace _ { @@ -21945,11 +21498,6 @@ inline const char* type_name() { } } -/** - * @file addons/cpp/utils/map.hpp - * @brief Minimal C++ wrapper for ecs_map_t iteration. - */ - #pragma once namespace flecs { @@ -22044,12 +21592,7 @@ struct map { } - // Mixin forward declarations -/** - * @file addons/cpp/mixins/id/decl.hpp - * @brief ID class. - */ #pragma once @@ -22201,11 +21744,6 @@ struct id { } -/** - * @file addons/cpp/mixins/term/decl.hpp - * @brief Term declarations. - */ - #pragma once namespace flecs { @@ -22223,11 +21761,6 @@ struct term_builder; } -/** - * @file addons/cpp/mixins/query/decl.hpp - * @brief Query declarations. - */ - #pragma once namespace flecs { @@ -22251,18 +21784,8 @@ struct query_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) @@ -22431,7 +21954,6 @@ struct event_builder_typed : event_builder_base, E> { } - namespace flecs { namespace _ { @@ -22457,11 +21979,6 @@ using event_from_func_t = typename event_from_func::type; } } -/** - * @file addons/cpp/mixins/observer/decl.hpp - * @brief Observer declarations. - */ - #pragma once namespace flecs { @@ -22484,10 +22001,6 @@ struct observer_builder; } #ifdef FLECS_SYSTEM -/** - * @file addons/cpp/mixins/system/decl.hpp - * @brief System module declarations. - */ #pragma once @@ -22522,10 +22035,6 @@ void system_init(flecs::world& world); #endif #ifdef FLECS_PIPELINE -/** - * @file addons/cpp/mixins/pipeline/decl.hpp - * @brief Pipeline module declarations. - */ #pragma once @@ -22576,10 +22085,6 @@ static const flecs::entity_t PostFrame = EcsPostFrame; #endif #ifdef FLECS_TIMER -/** - * @file addons/cpp/mixins/timer/decl.hpp - * @brief Timer module declarations. - */ #pragma once @@ -22612,10 +22117,6 @@ void timer_init(flecs::world& world); #endif #ifdef FLECS_DOC -/** - * @file addons/cpp/mixins/doc/decl.hpp - * @brief Doc mixin declarations. - */ #pragma once @@ -22661,10 +22162,6 @@ void init(flecs::world& world); #endif #ifdef FLECS_REST -/** - * @file addons/cpp/mixins/rest/decl.hpp - * @brief Rest module declarations. - */ #pragma once @@ -22696,10 +22193,6 @@ void init(flecs::world& world); #endif #ifdef FLECS_META -/** - * @file addons/cpp/mixins/meta/decl.hpp - * @brief Meta declarations. - */ #pragma once @@ -22818,11 +22311,6 @@ void init(flecs::world& world); } // namespace meta } // namespace flecs -/** - * @file addons/cpp/mixins/meta/cursor.hpp - * @brief Cursor for reading/writing dynamic values. - */ - #pragma once namespace flecs { @@ -22981,11 +22469,6 @@ struct cursor { } -/** - * @file addons/cpp/mixins/meta/opaque.hpp - * @brief Helpers for opaque type registration. - */ - #pragma once namespace flecs { @@ -23016,7 +22499,6 @@ using serialize_member = int(*)(const serializer *, const T*, const char* name); template using serialize_element = int(*)(const serializer *, const T*, size_t element); - /** Type-safe interface for opaque types. */ template struct opaque { @@ -23187,13 +22669,8 @@ struct opaque { } - #endif #ifdef FLECS_UNITS -/** - * @file addons/cpp/mixins/units/decl.hpp - * @brief Units module declarations. - */ #pragma once @@ -23368,7 +22845,6 @@ struct Degrees { }; /** @} */ }; - /** Time units. */ struct time { /** @@ -23383,7 +22859,6 @@ struct Date { }; /** @} */ }; - /** Mass units. */ struct mass { /** @@ -23400,7 +22875,6 @@ struct KiloGrams { }; /** @} */ }; - /** Electric current units. */ struct electric_current { /** @@ -23415,7 +22889,6 @@ struct Ampere { }; /** @} */ }; - /** Amount units. */ struct amount { /** @@ -23430,7 +22903,6 @@ struct Mole { }; /** @} */ }; - /** Luminous intensity units. */ struct luminous_intensity { /** @@ -23445,7 +22917,6 @@ struct Candela { }; /** @} */ }; - /** Force units. */ struct force { /** @@ -23460,7 +22931,6 @@ struct Newton { }; /** @} */ }; - /** Length units. */ struct length { /** @@ -23491,7 +22961,6 @@ struct Pixels { }; /** @} */ }; - /** Pressure units. */ struct pressure { /** @@ -23508,7 +22977,6 @@ struct Bar { }; /** @} */ }; - /** Speed units. */ struct speed { /** @@ -23529,7 +22997,6 @@ struct MilesPerHour { }; /** @} */ }; - /** Temperature units. */ struct temperature { /** @@ -23548,7 +23015,6 @@ struct Fahrenheit { }; /** @} */ }; - /** Data units. */ struct data { /** @@ -23611,7 +23077,6 @@ struct GigaBytesPerSecond { }; /** @} */ }; - /** Frequency units. */ struct frequency { /** @@ -23632,7 +23097,6 @@ struct GigaHertz { }; /** @} */ }; - /** URI units. */ struct uri { /** @@ -23651,7 +23115,6 @@ struct File { }; /** @} */ }; - /** Color units. */ struct color { /** @@ -23687,10 +23150,6 @@ units(flecs::world& world); #endif #ifdef FLECS_STATS -/** - * @file addons/cpp/mixins/stats/decl.hpp - * @brief Stats module declarations. - */ #pragma once @@ -23725,18 +23184,9 @@ struct stats { #endif #ifdef FLECS_METRICS -/** - * @file addons/cpp/mixins/metrics/decl.hpp - * @brief Metrics declarations. - */ #pragma once -/** - * @file addons/cpp/mixins/metrics/builder.hpp - * @brief Metric builder. - */ - #pragma once namespace flecs { @@ -23853,7 +23303,6 @@ struct metric_builder { } - namespace flecs { /** @@ -23895,10 +23344,6 @@ struct metrics { #endif #ifdef FLECS_ALERTS -/** - * @file addons/cpp/mixins/alerts/decl.hpp - * @brief Alert declarations. - */ #pragma once @@ -23937,10 +23382,6 @@ struct alert_builder; #endif #ifdef FLECS_JSON -/** - * @file addons/cpp/mixins/json/decl.hpp - * @brief JSON addon declarations. - */ #pragma once @@ -23964,18 +23405,9 @@ 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 { @@ -24074,21 +23506,11 @@ struct app_builder { } - #endif #ifdef FLECS_SCRIPT -/** - * @file addons/cpp/mixins/script/decl.hpp - * @brief Script declarations. - */ #pragma once -/** - * @file addons/cpp/mixins/script/builder.hpp - * @brief Script builder. - */ - #pragma once namespace flecs { @@ -24139,7 +23561,6 @@ struct script_builder { } - namespace flecs { /** @@ -24167,11 +23588,6 @@ void init(flecs::world& world); #endif -/** - * @file addons/cpp/log.hpp - * @brief Logging functions. - */ - #pragma once namespace flecs { @@ -24296,11 +23712,6 @@ inline void pop() { } } -/** - * @file addons/cpp/pair.hpp - * @brief Utilities for working with compile-time pairs. - */ - #pragma once namespace flecs { @@ -24309,7 +23720,6 @@ namespace _ { struct pair_base { }; } // namespace _ - /** * @defgroup cpp_pair_type Pair type * @ingroup cpp_core @@ -24430,7 +23840,6 @@ struct actual_type::value >> { template using actual_type_t = typename actual_type::type; - /** Get the type without const, *, and &. */ template struct base_type { @@ -24441,7 +23850,6 @@ struct base_type { template using base_type_t = typename base_type::type; - /** Get the type without * and & (retains const, which is useful for function args). */ template struct base_arg_type { @@ -24452,7 +23860,6 @@ struct base_arg_type { template using base_arg_type_t = typename base_arg_type::type; - /** Test if a type is the same as its actual type. */ template struct is_actual { @@ -24467,11 +23874,6 @@ inline constexpr bool is_actual_v = is_actual::value; } // namespace flecs -/** - * @file addons/cpp/lifecycle_traits.hpp - * @brief Utilities for discovering and registering component lifecycle hooks. - */ - #pragma once namespace flecs @@ -24843,11 +24245,6 @@ ecs_equals_t equals() { } // namespace _ } // namespace flecs -/** - * @file addons/cpp/world.hpp - * @brief World class. - */ - #pragma once namespace flecs @@ -25031,7 +24428,6 @@ inline void assign(world_t *world, entity_t entity, const A& value) { flecs::assign(world, entity, value, id); } - /** Emplace a component value, constructing it in place. * * @tparam T The component type. @@ -25687,7 +25083,6 @@ struct world { template ref get_ref() const; - /* try_get */ /** Get singleton component. @@ -25714,7 +25109,6 @@ struct world { template const First* try_get(Second second) const; - /* get */ /** Get singleton component. @@ -25744,7 +25138,6 @@ struct world { template ::value > = 0 > void get(const Func& func) const; - /* try_get_mut */ /** Get mutable singleton component. @@ -25769,7 +25162,6 @@ struct world { template First* try_get_mut(Second second) const; - /* get_mut */ /** Get mutable singleton component. @@ -25794,7 +25186,6 @@ struct world { template First& get_mut(Second second) const; - /** Test if world has singleton component. * * @tparam T The component to check. @@ -26338,11 +25729,6 @@ struct world { return type_info(_::type::id(world_)); } -/** - * @file addons/cpp/mixins/id/mixin.inl - * @brief ID world mixin. - */ - /** Get ID from a type. * * @memberof flecs::world @@ -26377,11 +25763,6 @@ flecs::id pair(entity_t o) const; */ flecs::id pair(entity_t r, entity_t o) const; -/** - * @file addons/cpp/mixins/component/mixin.inl - * @brief Component mixin. - */ - /** Find or register a component. * * @ingroup cpp_components @@ -26399,11 +25780,6 @@ flecs::component component(Args &&... args) const; 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 @@ -26452,11 +25828,6 @@ flecs::entity entity(const char *name = nullptr) const; 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 * @ingroup cpp_addons @@ -26486,11 +25857,6 @@ flecs::event_builder_typed event() const; /** @} */ -/** - * @file addons/cpp/mixins/term/mixin.inl - * @brief Term world mixin. - */ - /** * @memberof flecs::world * @ingroup cpp_core_queries @@ -26516,11 +25882,6 @@ flecs::term term() const; /** @} */ -/** - * @file addons/cpp/mixins/observer/mixin.inl - * @brief Observer world mixin. - */ - /** Observer world mixin. * * @memberof flecs::world @@ -26548,11 +25909,6 @@ 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 @@ -26626,11 +25982,6 @@ void each(flecs::id_t term_id, Func&& func) const; /** @} */ -/** - * @file addons/cpp/mixins/enum/mixin.inl - * @brief Enum world mixin. - */ - /** Convert an enum constant to an entity. * * @memberof flecs::world @@ -26639,12 +25990,7 @@ void each(flecs::id_t term_id, Func&& func) const; template ::value > = 0> flecs::entity to_entity(E constant) const; - # ifdef FLECS_MODULE -/** - * @file addons/cpp/mixins/module/mixin.inl - * @brief Module world mixin. - */ /** * @memberof flecs::world @@ -26675,10 +26021,6 @@ flecs::entity import(); # endif # ifdef FLECS_PIPELINE -/** - * @file addons/cpp/mixins/pipeline/mixin.inl - * @brief Pipeline world mixin. - */ /** * @memberof flecs::world @@ -26773,10 +26115,6 @@ bool using_task_threads() const; # endif # ifdef FLECS_SYSTEM -/** - * @file addons/cpp/mixins/system/mixin.inl - * @brief System module world mixin. - */ /** * @memberof flecs::world @@ -26806,10 +26144,6 @@ 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 @@ -26831,10 +26165,6 @@ void randomize_timers() const; # endif # ifdef FLECS_SCRIPT -/** - * @file addons/cpp/mixins/script/mixin.inl - * @brief Script world mixin. - */ /** * @defgroup cpp_addons_script Script @@ -26918,15 +26248,10 @@ T get_const_var(const char *name, const T& default_value = {}) const; template void get_const_var(const char *name, T& out, const T& default_value = {}) const; - /** @} */ # endif # ifdef FLECS_META -/** - * @file addons/cpp/mixins/meta/world.inl - * @brief Meta world mixin. - */ /** * @memberof flecs::world @@ -26968,10 +26293,6 @@ flecs::entity vector(); # endif # ifdef FLECS_JSON -/** - * @file addons/cpp/mixins/json/world.inl - * @brief JSON world mixin. - */ /** Serialize an untyped value to JSON. * @@ -27043,10 +26364,6 @@ const char* from_json_file(const char *json, flecs::from_json_desc_t *desc = nul # endif # ifdef FLECS_APP -/** - * @file addons/cpp/mixins/app/mixin.inl - * @brief App world addon mixin. - */ /** * @ingroup cpp_addons_app @@ -27071,10 +26388,6 @@ flecs::app_builder app() { # endif # ifdef FLECS_METRICS -/** - * @file addons/cpp/mixins/metrics/mixin.inl - * @brief Metrics world mixin. - */ /** Create a metric. * @@ -27086,10 +26399,6 @@ flecs::metric_builder metric(Args &&... args) const; # endif # ifdef FLECS_ALERTS -/** - * @file addons/cpp/mixins/alerts/mixin.inl - * @brief Alert world mixin. - */ /** Create an alert. * @@ -27143,12 +26452,6 @@ struct scoped_world : world { } // namespace flecs - -/** - * @file addons/cpp/field.hpp - * @brief Wrapper classes for fields returned by flecs::iter. - */ - #pragma once /** @@ -27253,15 +26556,44 @@ struct field { bool is_shared_; }; +/** Wrapper class around a field matched through component inheritance. + * + * Same interface as field, but uses a runtime stride so it can iterate a field + * whose stored (derived) component is larger than T. Unlike field, this does + * not assert when the matched id is a subtype of T. + * + * @tparam T Base component type of the field. + * + * @ingroup cpp_iterator + */ +template +struct base_field { + static_assert(std::is_empty::value == false, + "invalid type for field, cannot iterate empty type"); + + base_field(T* array, size_t stride, size_t count, bool is_shared = false) + : data_(array) + , stride_(stride) + , count_(count) + , is_shared_(is_shared) {} + + base_field(iter &iter, int field); + + T& operator[](size_t index) const; + T& operator*() const; + T* operator->() const; + +protected: + T* data_; + size_t stride_; + size_t count_; + bool is_shared_; +}; + } // namespace flecs /** @} */ -/** - * @file addons/cpp/iter.hpp - * @brief Wrapper classes for ecs_iter_t and component arrays. - */ - #pragma once /** @@ -27566,6 +26898,28 @@ struct iter { template , if_not_t> = 0> flecs::field field(int8_t index) const; + /** Get access to a field matched through component inheritance. + * Like field(), but returns a base_field that iterates with a runtime + * stride, so it works when the matched (derived) component is larger than T. + * Unlike field(), this does not assert when the field is of type T or a + * subtype of T. + * + * @tparam T Base type of the field. + * @param index The field index. + * @return The field data. + */ + template , if_t> = 0> + flecs::base_field base_field(int8_t index) const; + + /** Get read/write access to a field matched through component inheritance. + * + * @tparam T Base type of the field. + * @param index The field index. + * @return The field data. + */ + template , if_not_t> = 0> + flecs::base_field base_field(int8_t index) const; + /** Get unchecked access to field data. * Unchecked access is required when a system does not know the type of a * field at compile time. @@ -27770,6 +27124,31 @@ struct iter { count, is_shared); } + /* Get field matched through component inheritance. Checks the queried + * (base) type matches T, not the (possibly derived) matched id, so a field + * of type T or a subtype of T is accepted. Uses a runtime stride. */ + template > + flecs::base_field get_base_field(int8_t index) const { + +#ifndef FLECS_NDEBUG + ecs_assert(ecs_field_size(iter_, index) == sizeof(A), + ECS_COLUMN_TYPE_MISMATCH, NULL); +#endif + + size_t count; + bool is_shared = !ecs_field_is_self(iter_, index); + + if (is_shared) { + count = 1; + } else { + count = static_cast(iter_->count); + } + + return flecs::base_field( + static_cast(ecs_base_field_w_size(iter_, sizeof(A), index)), + ecs_field_stride(iter_, index), count, is_shared); + } + /* Get field, check if correct type is used. */ template > flecs::field get_field_at(int8_t index, int32_t row) const { @@ -27820,11 +27199,6 @@ struct iter { /** @} */ -/** - * @file addons/cpp/ref.hpp - * @brief Class that caches data to speed up get operations. - */ - #pragma once namespace flecs @@ -27976,34 +27350,10 @@ struct ref : public untyped_ref { } -/** - * @file addons/cpp/entity.hpp - * @brief Entity class. - * - * This class provides read/write access to entities. - */ - #pragma once -/** - * @file addons/cpp/entity_view.hpp - * @brief Entity class with only read-only operations. - * - * This class provides read-only 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 - * create a new entity. - * - * To obtain a mutable handle to the entity, use the mut() function. - */ - #pragma once -/** - * @file addons/cpp/entity_component_tuple.hpp - * @brief Utilities to fetch components as tuples from entities. - */ - #pragma once /** @@ -28159,7 +27509,6 @@ namespace flecs /** @} */ - /** * @ingroup cpp_entities * @{ @@ -28419,7 +27768,6 @@ struct entity_view : public id { children(flecs::ChildOf, FLECS_MOV(func)); } - /* try_get */ /** Get component value. @@ -28565,7 +27913,6 @@ struct entity_view : public id { return try_get>(); } - /* get */ /** Get component value. @@ -28759,7 +28106,6 @@ struct entity_view : public id { return *r; } - /* try_get_mut */ /** Get mutable component value. @@ -28905,7 +28251,6 @@ struct entity_view : public id { return try_get_mut>(); } - /* get_mut */ /** Get mutable component value. @@ -29441,10 +28786,6 @@ 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 an entity to JSON. * @@ -29458,10 +28799,6 @@ flecs::string to_json(const flecs::entity_to_json_desc_t *desc = nullptr) const # endif # ifdef FLECS_DOC -/** - * @file addons/cpp/mixins/doc/entity_view.inl - * @brief Doc entity view mixin. - */ /** Get human-readable name. * @@ -29543,10 +28880,6 @@ const char* doc_uuid() const { # endif # ifdef FLECS_ALERTS -/** - * @file addons/cpp/mixins/alerts/entity_view.inl - * @brief Alerts entity mixin. - */ /** Return the number of alerts for an entity. * @@ -29559,11 +28892,6 @@ int32_t alert_count(flecs::entity_t alert = 0) const { # endif -/** - * @file addons/cpp/mixins/enum/entity_view.inl - * @brief Enum entity view mixin. - */ - /** Convert an entity to an enum constant. * * @memberof flecs::entity_view @@ -29572,12 +28900,6 @@ int32_t alert_count(flecs::entity_t alert = 0) const { template E to_constant() const; - -/** - * @file addons/cpp/mixins/event/entity_view.inl - * @brief Event entity mixin. - */ - /** Emit an event for an entity. * * @memberof flecs::entity_view @@ -29625,7 +28947,6 @@ void emit(const Evt& payload) const { .emit(); } - /** Enqueue an event for an entity. * * @memberof flecs::entity_view @@ -29673,7 +28994,6 @@ void enqueue(const Evt& payload) const { .enqueue(); } - private: flecs::entity set_stage(world_t *stage); }; @@ -29682,11 +29002,6 @@ void enqueue(const Evt& payload) const { /** @} */ -/** - * @file addons/cpp/mixins/entity/builder.hpp - * @brief Entity builder. - */ - #pragma once namespace flecs @@ -30563,7 +29878,6 @@ struct entity_builder : entity_view { return to_base(); } - /** Assign a component for an entity. * This operation sets the component value. If the entity did not yet have * the component, the operation will panic. @@ -30741,7 +30055,6 @@ struct entity_builder : entity_view { return to_base(); } - /** Set 1..N components. * This operation accepts a callback with as arguments the components to * set. If the entity does not have all of the provided components, they @@ -30873,10 +30186,6 @@ struct entity_builder : entity_view { } # ifdef FLECS_DOC -/** - * @file addons/cpp/mixins/doc/entity_builder.inl - * @brief Doc entity builder mixin. - */ /** Set human-readable name. * This adds `(flecs.doc.Description, flecs.Name)` to the entity. @@ -30971,10 +30280,6 @@ const Self& set_doc_uuid(const char *uuid) const { # endif # ifdef FLECS_META -/** - * @file addons/cpp/mixins/meta/entity_builder.inl - * @brief Meta entity builder mixin. - */ /** * @memberof flecs::entity_builder @@ -31064,10 +30369,6 @@ const Self& quantity() const { # endif # ifdef FLECS_JSON -/** - * @file addons/cpp/mixins/json/entity_builder.inl - * @brief JSON entity mixin. - */ /** Set a component from JSON. * @@ -31172,11 +30473,6 @@ const Self& set_json_second( # endif -/** - * @file addons/cpp/mixins/event/entity_builder.inl - * @brief Event entity mixin. - */ - /** Observe an event on an entity. * * @memberof flecs::entity_builder @@ -31210,7 +30506,6 @@ const Self& observe(Func&& callback) const; template const Self& observe(Func&& callback) const; - protected: const Self& to_base() const { return *static_cast(this); @@ -31219,7 +30514,6 @@ const Self& observe(Func&& callback) const; } - /** * @defgroup cpp_entities Entities * @ingroup cpp_core @@ -31546,7 +30840,6 @@ struct entity : entity_builder _::type::id(world_))); } - /** Get reference to pair component. * * @tparam First The first element of the pair. @@ -31703,10 +30996,6 @@ struct entity : entity_builder } # ifdef FLECS_JSON -/** - * @file addons/cpp/mixins/json/entity.inl - * @brief JSON entity mixin. - */ /** Deserialize an entity from JSON. * @@ -31724,11 +31013,6 @@ const char* from_json(const char *json) { /** @} */ -/** - * @file addons/cpp/delegate.hpp - * @brief Wrappers around C++ functions that provide callbacks for C APIs. - */ - #pragma once #include // std::declval @@ -31769,6 +31053,7 @@ struct component_binding_ctx { // Utility to convert a template argument pack to an array of term pointers. struct field_ptr { void *ptr = nullptr; + int32_t stride = 0; int8_t index = 0; bool is_ref = false; bool is_row = false; @@ -31778,34 +31063,41 @@ template struct field_ptrs { using array = flecs::array<_::field_ptr, sizeof...(Components)>; - void populate(const ecs_iter_t *iter) { - populate_impl(iter, std::index_sequence_for{}); - } - void populate_self(const ecs_iter_t *iter) { populate_self_impl(iter, std::index_sequence_for{}); } + void populate_inherited(const ecs_iter_t *iter) { + populate_inherited_impl(iter, std::index_sequence_for{}); + } + array fields_; private: template - void populate_field(const ecs_iter_t *iter, size_t index) { + void populate_inherited_field(const ecs_iter_t *iter, size_t index) { using A = remove_pointer_t>; if constexpr (!is_empty_v) { if (iter->row_fields & (1llu << index)) { - /* Need to fetch the value with ecs_field_at() */ fields_[index].is_row = true; fields_[index].is_ref = true; fields_[index].index = static_cast(index); } else { - fields_[index].ptr = ecs_field_w_size(iter, sizeof(A), + fields_[index].ptr = ecs_base_field_w_size(iter, sizeof(A), static_cast(index)); fields_[index].is_ref = iter->sources[index] != 0; } + fields_[index].stride = static_cast( + ecs_field_stride(iter, static_cast(index))); } } + template + void populate_inherited_impl(const ecs_iter_t *iter, std::index_sequence) { + (void)iter; + (populate_inherited_field(iter, Is), ...); + } + template void populate_self_field(const ecs_iter_t *iter, size_t index) { (void)iter; (void)index; @@ -31818,12 +31110,6 @@ struct field_ptrs { } } - template - void populate_impl(const ecs_iter_t *iter, std::index_sequence) { - (void)iter; - (populate_field(iter, Is), ...); - } - template void populate_self_impl(const ecs_iter_t *iter, std::index_sequence) { (void)iter; @@ -31914,28 +31200,85 @@ struct each_field::value && } }; +template +struct each_field_strided { }; + +template +struct each_field_strided::value && + !is_empty>::value && is_actual::value > > + : each_column_base +{ + each_field_strided(const flecs::iter_t*, _::field_ptr& field, size_t row) + : each_column_base(field, row) { } + + T& get_row() { + return *reinterpret_cast(static_cast(this->field_.ptr) + + this->row_ * static_cast(this->field_.stride)); + } +}; + +template +struct each_field_strided::value && + !is_empty>::value && !is_actual::value> > + : each_column_base +{ + each_field_strided(const flecs::iter_t*, _::field_ptr& field, size_t row) + : each_column_base(field, row) { } + + T get_row() { + return *reinterpret_cast*>( + static_cast(this->field_.ptr) + + this->row_ * static_cast(this->field_.stride)); + } +}; + +template +struct each_field_strided>::value && + !is_pointer::value > > + : each_column_base +{ + each_field_strided(const flecs::iter_t*, _::field_ptr& field, size_t row) + : each_column_base(field, row) { } + + T get_row() { + return actual_type_t(); + } +}; + +template +struct each_field_strided::value && + !is_empty>::value > > + : each_column_base +{ + each_field_strided(const flecs::iter_t*, _::field_ptr& field, size_t row) + : each_column_base(field, row) { } + + actual_type_t get_row() { + if (this->field_.ptr) { + return reinterpret_cast>( + static_cast(this->field_.ptr) + + this->row_ * static_cast(this->field_.stride)); + } else { + return nullptr; + } + } +}; + // If the query contains component references to other entities, check if the // current argument is one. template -struct each_ref_field : public each_field { +struct each_ref_field_strided : public each_field_strided { using A = remove_pointer_t>; - each_ref_field(const flecs::iter_t *iter, _::field_ptr& field, size_t row) - : each_field(iter, field, row) { + each_ref_field_strided(const flecs::iter_t *iter, _::field_ptr& field, size_t row) + : each_field_strided(iter, field, row) { if (field.is_ref) { - // If this is a reference, set the row to 0 as a ref is always a - // single value, not an array. This prevents the application from - // having to do an if-check on whether the field is owned. - // - // This check only happens when the current table being iterated - // over caused the query to match a reference. The check is - // performed once per iterated table. this->row_ = 0; } if (field.is_row) { - field.ptr = ecs_field_at_w_size(iter, sizeof(A), field.index, + field.ptr = ecs_field_at_w_size(iter, sizeof(A), field.index, static_cast(row)); } } @@ -31961,9 +31304,11 @@ struct each_delegate : public delegate { iter->flags |= EcsIterCppEach; - if (iter->ref_fields | iter->up_fields) { - terms.populate(iter); - invoke_unpack< each_ref_field >(iter, func_, 0, terms.fields_); + if (iter->ref_fields | iter->up_fields | + (iter->flags & EcsIterComponentInheritance)) + { + terms.populate_inherited(iter); + invoke_unpack< each_ref_field_strided >(iter, func_, 0, terms.fields_); } else { terms.populate_self(iter); invoke_unpack< each_field >(iter, func_, 0, terms.fields_); @@ -32130,9 +31475,11 @@ struct find_delegate : public delegate { iter->flags |= EcsIterCppEach; - if (iter->ref_fields | iter->up_fields) { - terms.populate(iter); - return invoke_callback< each_ref_field >(iter, func_, 0, terms.fields_); + if (iter->ref_fields | iter->up_fields | + (iter->flags & EcsIterComponentInheritance)) + { + terms.populate_inherited(iter); + return invoke_callback< each_ref_field_strided >(iter, func_, 0, terms.fields_); } else { terms.populate_self(iter); return invoke_callback< each_field >(iter, func_, 0, terms.fields_); @@ -32294,7 +31641,6 @@ struct run_delegate : delegate { Func func_; }; - //////////////////////////////////////////////////////////////////////////////// //// Utility class to invoke an entity observer delegate //////////////////////////////////////////////////////////////////////////////// @@ -32372,7 +31718,6 @@ struct entity_payload_observer_delegate : delegate { Func func_; }; - //////////////////////////////////////////////////////////////////////////////// //// Utility to invoke callback on entity if it has components in signature //////////////////////////////////////////////////////////////////////////////// @@ -32684,11 +32029,6 @@ using delegate = _::each_delegate::type, Args...>; } // namespace flecs -/** - * @file addons/cpp/component.hpp - * @brief Registering/obtaining info from components. - */ - #pragma once /** @@ -33110,10 +32450,6 @@ untyped_component& on_equals( } # ifdef FLECS_META -/** - * @file addons/cpp/mixins/meta/untyped_component.inl - * @brief Meta component mixin. - */ /** * @memberof flecs::component @@ -33433,10 +32769,6 @@ untyped_component& error_range( # endif # ifdef FLECS_METRICS -/** - * @file addons/cpp/mixins/metrics/untyped_component.inl - * @brief Metrics component mixin. - */ /** * @memberof flecs::component @@ -33496,6 +32828,42 @@ struct component : untyped_component { id_ = _::type::register_id(world, name, allow_tag, true, true, id); } + /** Mark this component as inheriting from a base component. + * + * @tparam Base The base component. + * @return Reference to self for chaining. + */ + template + component& is_a() { + if constexpr (std::is_base_of::value && + !std::is_same::value) + { + alignas(T) static char storage[sizeof(T)]; + T* derived_ptr = reinterpret_cast(&storage[0]); + Base* base_ptr = static_cast(derived_ptr); + ecs_assert( + reinterpret_cast(base_ptr) == + reinterpret_cast(derived_ptr), + ECS_INVALID_OPERATION, + "component inheritance requires the base component to be at " + "offset 0 in the derived component (multiple/virtual " + "inheritance is not supported)"); + (void)base_ptr; + } + this->add(flecs::IsA, _::type::id(this->world_)); + return *this; + } + + /** Mark this component as inheriting from a base component. + * + * @param base The base component id. + * @return Reference to self for chaining. + */ + component& is_a(flecs::entity_t base) { + this->add(flecs::IsA, base); + return *this; + } + /** Register on_add hook. * * @param func The callback function. @@ -33628,10 +32996,6 @@ struct component : untyped_component { } # ifdef FLECS_META -/** - * @file addons/cpp/mixins/meta/component.inl - * @brief Meta component mixin. - */ /** Register an opaque type interface. */ template @@ -33710,11 +33074,6 @@ component& constant(const char *name, T value) { /** @} */ -/** - * @file addons/cpp/type.hpp - * @brief Utility functions for ID vector. - */ - #pragma once namespace flecs { @@ -33819,11 +33178,6 @@ struct type { } -/** - * @file addons/cpp/table.hpp - * @brief Direct access to table data. - */ - #pragma once namespace flecs { @@ -34049,7 +33403,6 @@ struct table { return ecs_table_get_column(table_, index, 0); } - /* get */ /** Get a pointer to the component array by component. @@ -34107,7 +33460,6 @@ struct table { return static_cast(try_get(_::type::id(world_), second)); } - /* get */ /** Get a pointer to the component array by component. @@ -34168,7 +33520,6 @@ struct table { return static_cast(get(_::type::id(world_), second)); } - /** Get a pointer to the component array by pair. * * @tparam First The first element of the pair. @@ -34314,11 +33665,6 @@ struct table_range : table { } -/** - * @file addons/cpp/utils/iterable.hpp - * @brief Base class for iterable objects, like queries. - */ - namespace flecs { /** Forward declaration of iter_iterable. */ @@ -34333,8 +33679,13 @@ struct page_iterable; template struct worker_iterable; -/** Base class for iterable query objects. */ -template +/** Base class for iterable query objects. + * + * Uses static (CRTP) dispatch: the most-derived type (Derived) provides the + * non-virtual get_iter()/next_action() methods. This keeps the typed iterables + * non-polymorphic, so no per-signature vtables or RTTI are emitted. + */ +template struct iterable { /** Each iterator. @@ -34348,8 +33699,8 @@ struct iterable { */ template void each(Func&& func) const { - ecs_iter_t it = this->get_iter(nullptr); - ecs_iter_next_action_t next = this->next_action(); + ecs_iter_t it = this->get_iter_(nullptr); + ecs_iter_next_action_t next = this->next_action_(); while (next(&it)) { _::each_delegate(func).invoke(&it); } @@ -34364,7 +33715,7 @@ struct iterable { */ template void run(Func&& func) const { - ecs_iter_t it = this->get_iter(nullptr); + ecs_iter_t it = this->get_iter_(nullptr); _::run_delegate(func).invoke(&it); } @@ -34376,8 +33727,8 @@ struct iterable { */ template flecs::entity find(Func&& func) const { - ecs_iter_t it = this->get_iter(nullptr); - ecs_iter_next_action_t next = this->next_action(); + ecs_iter_t it = this->get_iter_(nullptr); + ecs_iter_next_action_t next = this->next_action_(); flecs::entity result; while (!result && next(&it)) { @@ -34476,20 +33827,23 @@ struct iterable { return this->iter().template set_group(); } - /** Virtual destructor. */ - virtual ~iterable() { } -protected: - friend iter_iterable; - friend page_iterable; - friend worker_iterable; +private: + const Derived& derived_() const { + return *static_cast(this); + } - virtual ecs_iter_t get_iter(flecs::world_t *stage) const = 0; - virtual ecs_iter_next_action_t next_action() const = 0; + ecs_iter_t get_iter_(flecs::world_t *stage) const { + return derived_().get_iter(stage); + } + + ecs_iter_next_action_t next_action_() const { + return derived_().next_action(); + } }; /** Iterable adapter for iterating with iter/each/run. */ template -struct iter_iterable final : iterable { +struct iter_iterable final : iterable, Components...> { /** Construct iter_iterable from an iterable and a world. */ template iter_iterable(Iterable *it, flecs::world_t *world) @@ -34542,10 +33896,6 @@ struct iter_iterable final : iterable { } # ifdef FLECS_JSON -/** - * @file addons/cpp/mixins/json/iterable.inl - * @brief JSON iterable mixin. - */ /** Serialize an iterator result to JSON. * @@ -34600,8 +33950,7 @@ flecs::string to_json(flecs::iter_to_json_desc_t *desc = nullptr) { return *this; } -protected: - ecs_iter_t get_iter(flecs::world_t *world) const override { + ecs_iter_t get_iter(flecs::world_t *world) const { if (world) { ecs_iter_t result = it_; result.world = world; @@ -34610,7 +33959,7 @@ flecs::string to_json(flecs::iter_to_json_desc_t *desc = nullptr) { return it_; } - ecs_iter_next_action_t next_action() const override { + ecs_iter_next_action_t next_action() const { return next_; } @@ -34620,27 +33969,27 @@ flecs::string to_json(flecs::iter_to_json_desc_t *desc = nullptr) { ecs_iter_next_action_t next_each_; }; -template -iter_iterable iterable::iter(flecs::world_t *world) const +template +iter_iterable iterable::iter(flecs::world_t *world) const { - return iter_iterable(this, world); + return iter_iterable(&derived_(), world); } -template -iter_iterable iterable::iter(flecs::iter& it) const +template +iter_iterable iterable::iter(flecs::iter& it) const { - return iter_iterable(this, it.world()); + return iter_iterable(&derived_(), it.world()); } -template -iter_iterable iterable::iter(flecs::entity e) const +template +iter_iterable iterable::iter(flecs::entity e) const { - return iter_iterable(this, e.world()); + return iter_iterable(&derived_(), e.world()); } /** Paged iterable adapter. Limits iteration to a range of entities. */ template -struct page_iterable final : iterable { +struct page_iterable final : iterable, Components...> { /** Construct a page_iterable from an offset, limit, and source iterable. */ template page_iterable(int32_t offset, int32_t limit, Iterable *it) @@ -34650,7 +33999,6 @@ struct page_iterable final : iterable { chain_it_ = it->get_iter(nullptr); } -protected: ecs_iter_t get_iter(flecs::world_t*) const { return ecs_page_iter(&chain_it_, offset_, limit_); } @@ -34665,26 +34013,26 @@ struct page_iterable final : iterable { int32_t limit_; }; -template -page_iterable iterable::page( +template +page_iterable iterable::page( int32_t offset, int32_t limit) { - return page_iterable(offset, limit, this); + return page_iterable(offset, limit, &derived_()); } /** Worker iterable adapter. Divides entities across workers. */ template -struct worker_iterable final : iterable { +struct worker_iterable final : iterable, Components...> { /** Construct a worker_iterable from an index, count, and source iterable. */ - worker_iterable(int32_t index, int32_t count, iterable *it) + template + worker_iterable(int32_t index, int32_t count, Iterable *it) : index_(index) , count_(count) { chain_it_ = it->get_iter(nullptr); } -protected: ecs_iter_t get_iter(flecs::world_t*) const { return ecs_worker_iter(&chain_it_, index_, count_); } @@ -34699,22 +34047,17 @@ struct worker_iterable final : iterable { int32_t count_; }; -template -worker_iterable iterable::worker( +template +worker_iterable iterable::worker( int32_t index, int32_t count) { - return worker_iterable(index, count, this); + return worker_iterable(index, count, &derived_()); } } - // Mixin implementations -/** - * @file addons/cpp/mixins/id/impl.hpp - * @brief ID class implementation. - */ #pragma once @@ -34776,7 +34119,6 @@ inline flecs::entity id::type_id() const { return flecs::entity(world_, ecs_get_typeid(world_, id_)); } - // ID mixin implementation template @@ -34821,11 +34163,6 @@ 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 { @@ -35047,11 +34384,6 @@ inline flecs::entity world::prefab(const char *name) const { } -/** - * @file addons/cpp/mixins/component/impl.hpp - * @brief Component mixin implementation. - */ - #pragma once namespace flecs { @@ -35068,25 +34400,10 @@ inline flecs::untyped_component world::component(Args &&... args) const { } // namespace flecs -/** - * @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 a parameter pack. - */ - #pragma once namespace flecs { @@ -35118,13 +34435,39 @@ namespace _ { return flecs::And; } + // Type-erased term population shared by all query/system/observer builders. + // Kept out-of-line so the per-signature builder code is reduced to building + // the id/inout/oper arrays plus a single call. + FLECS_NOINLINE + inline int32_t populate_query_terms( + ecs_query_desc_t *desc, + int32_t term_index, + const flecs::id_t *ids, + const flecs::inout_kind_t *inout, + const flecs::oper_kind_t *oper, + int32_t count) + { + for (int32_t i = 0; i < count; i ++) { + ecs_term_t *term = &desc->terms[term_index + i]; + *term = ecs_term_t{}; + if (ids[i] & ECS_ID_FLAGS_MASK) { + term->id = ids[i]; + } else { + term->first.id = ids[i]; + } + term->inout = static_cast(inout[i]); + term->oper = static_cast(oper[i]); + } + return term_index + count; + } + template struct sig { - sig(flecs::world_t *world) + sig(flecs::world_t *world) : world_(world) , ids({ (_::type>::id(world))... }) , inout ({ (type_to_inout())... }) - , oper ({ (type_to_oper())... }) + , oper ({ (type_to_oper())... }) { } flecs::world_t *world_; @@ -35134,11 +34477,8 @@ namespace _ { template void populate(const Builder& b) { - size_t i = 0; - for (auto id : ids) { - b->with(id).inout(inout[i]).oper(oper[i]); - i ++; - } + b->populate_terms(ids.ptr(), inout.ptr(), oper.ptr(), + static_cast(sizeof...(Components))); } }; @@ -35569,7 +34909,6 @@ struct term_builder_i : term_ref_builder_i { } - namespace flecs { /** Class that describes a term. @@ -35717,27 +35056,10 @@ inline flecs::term world::term() const { } -/** - * @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/utils/builder.hpp - * @brief Builder base class. - * - * Generic functionality for builder classes. - */ - #pragma once namespace flecs { @@ -35792,14 +35114,8 @@ struct builder : IBuilder } // namespace _ } // namespace flecs -/** - * @file addons/cpp/mixins/query/builder_i.hpp - * @brief Query builder interface. - */ - #pragma once - namespace flecs { @@ -35815,6 +35131,17 @@ struct query_builder_i : term_builder_i { , expr_count_(0) , desc_(desc) { } + /** Append terms from type-erased id/inout/oper arrays. + * Used by the signature population path to keep per-signature code minimal. + */ + Base& populate_terms(const flecs::id_t *ids, const flecs::inout_kind_t *inout, + const flecs::oper_kind_t *oper, int32_t count) + { + term_index_ = _::populate_query_terms( + desc_, term_index_, ids, inout, oper, count); + return *this; + } + /** Set the query flags. */ Base& query_flags(ecs_flags32_t flags) { desc_->flags |= flags; @@ -36233,7 +35560,6 @@ struct query_builder_i : term_builder_i { } - namespace flecs { namespace _ { template @@ -36276,7 +35602,6 @@ struct query_builder final : _::query_builder_base { } - namespace flecs { @@ -36517,10 +35842,6 @@ struct query_base { operator query<>() const; # ifdef FLECS_JSON -/** - * @file addons/cpp/mixins/json/query.inl - * @brief JSON query mixin. - */ /** Serialize a query to JSON. * @@ -36543,7 +35864,7 @@ flecs::string to_json(flecs::iter_to_json_desc_t *desc = nullptr) { * @ingroup cpp_core_queries */ template -struct query : query_base, iterable { +struct query : query_base, iterable, Components...> { private: using Fields = typename _::field_ptrs::array; @@ -36577,9 +35898,8 @@ struct query : query_base, iterable { return flecs::query<>(q); } -private: - ecs_iter_t get_iter(flecs::world_t *world) const override { - ecs_assert(query_ != nullptr, ECS_INVALID_PARAMETER, + ecs_iter_t get_iter(flecs::world_t *world) const { + ecs_assert(query_ != nullptr, ECS_INVALID_PARAMETER, "cannot iterate invalid query"); if (!world) { world = query_->world; @@ -36587,7 +35907,7 @@ struct query : query_base, iterable { return ecs_query_iter(world, query_); } - ecs_iter_next_action_t next_action() const override { + ecs_iter_next_action_t next_action() const { return ecs_query_next; } }; @@ -36701,25 +36021,10 @@ inline query_base::operator flecs::query<> () const { } -/** - * @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 and observers. - */ - #pragma once namespace flecs { @@ -36803,14 +36108,8 @@ struct node_builder : IBuilder } // namespace _ } // namespace flecs -/** - * @file addons/cpp/mixins/observer/builder_i.hpp - * @brief Observer builder interface. - */ - #pragma once - namespace flecs { /** Observer builder interface. @@ -36887,7 +36186,6 @@ struct observer_builder_i : query_builder_i { } - namespace flecs { namespace _ { template @@ -36912,7 +36210,6 @@ struct observer_builder final : _::observer_builder_base { } - namespace flecs { @@ -37022,14 +36319,8 @@ inline observer_builder world::observer(Args &&... args) const { } // namespace flecs -/** - * @file addons/cpp/mixins/event/impl.hpp - * @brief Event implementation. - */ - #pragma once - namespace flecs { @@ -37126,11 +36417,6 @@ inline void entity_view::enqueue(flecs::entity evt) const { } // namespace flecs -/** - * @file addons/cpp/mixins/enum/impl.hpp - * @brief Enum implementation. - */ - #pragma once namespace flecs { @@ -37152,10 +36438,6 @@ inline flecs::entity world::to_entity(E constant) const { } #ifdef FLECS_MODULE -/** - * @file addons/cpp/mixins/module/impl.hpp - * @brief Module implementation. - */ #pragma once @@ -37268,28 +36550,13 @@ inline flecs::entity world::import() { #endif #ifdef FLECS_SYSTEM -/** - * @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 { @@ -37448,7 +36715,6 @@ struct system_builder_i : query_builder_i { } - namespace flecs { namespace _ { template @@ -37487,7 +36753,6 @@ struct system_builder final : _::system_builder_base { } - namespace flecs { @@ -37650,10 +36915,6 @@ struct system final : entity } # ifdef FLECS_TIMER -/** - * @file addons/cpp/mixins/timer/system_mixin.inl - * @brief Timer module system mixin. - */ /** * @memberof flecs::system @@ -37821,28 +37082,13 @@ inline void system_builder::prepend_each_callback_signature() { #endif #ifdef FLECS_PIPELINE -/** - * @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 { /** Pipeline builder interface. @@ -37862,7 +37108,6 @@ struct pipeline_builder_i : query_builder_i { } - namespace flecs { namespace _ { template @@ -37888,7 +37133,6 @@ struct pipeline_builder final : _::pipeline_builder_base { } - namespace flecs { template @@ -37971,10 +37215,6 @@ inline bool world::using_task_threads() const { #endif #ifdef FLECS_TIMER -/** - * @file addons/cpp/mixins/timer/impl.hpp - * @brief Timer module implementation. - */ #pragma once @@ -38099,10 +37339,6 @@ inline void timer_init(flecs::world& world) { #endif #ifdef FLECS_DOC -/** - * @file addons/cpp/mixins/doc/impl.hpp - * @brief Doc mixin implementation. - */ #pragma once @@ -38267,10 +37503,6 @@ inline void init(flecs::world& world) { #endif #ifdef FLECS_REST -/** - * @file addons/cpp/mixins/rest/impl.hpp - * @brief Rest module implementation. - */ #pragma once @@ -38288,10 +37520,6 @@ inline void init(flecs::world& world) { #endif #ifdef FLECS_META -/** - * @file addons/cpp/mixins/meta/impl.hpp - * @brief Meta implementation. - */ #pragma once @@ -38381,7 +37609,6 @@ inline void init(flecs::world& world) { } // namespace meta - inline flecs::entity cursor::get_type() const { return flecs::entity(cursor_.world, ecs_meta_get_type(&cursor_)); } @@ -38450,10 +37677,6 @@ inline int ecs_serializer_t::member(const char *name) const { #endif #ifdef FLECS_UNITS -/** - * @file addons/cpp/mixins/units/impl.hpp - * @brief Units module implementation. - */ #pragma once @@ -38673,10 +37896,6 @@ inline units::units(flecs::world& world) { #endif #ifdef FLECS_STATS -/** - * @file addons/cpp/mixins/stats/impl.hpp - * @brief Stats module implementation. - */ #pragma once @@ -38699,10 +37918,6 @@ inline stats::stats(flecs::world& world) { #endif #ifdef FLECS_METRICS -/** - * @file addons/cpp/mixins/metrics/impl.hpp - * @brief Metrics module implementation. - */ #pragma once @@ -38827,28 +38042,13 @@ inline untyped_component& untyped_component::metric( #endif #ifdef FLECS_ALERTS -/** - * @file addons/cpp/mixins/alerts/impl.hpp - * @brief Alerts module implementation. - */ #pragma once -/** - * @file addons/cpp/mixins/alerts/builder.hpp - * @brief Alert builder. - */ - #pragma once -/** - * @file addons/cpp/mixins/alerts/builder_i.hpp - * @brief Alert builder interface. - */ - #pragma once - namespace flecs { /** Alert builder interface. @@ -39025,7 +38225,6 @@ struct alert_builder_i : query_builder_i { } - namespace flecs { namespace _ { template @@ -39056,7 +38255,6 @@ struct alert_builder final : _::alert_builder_base { } - namespace flecs { template @@ -39099,14 +38297,9 @@ inline flecs::alert_builder world::alert(Args &&... args) const { #endif #ifdef FLECS_SCRIPT -/** - * @file addons/cpp/mixins/script/impl.hpp - * @brief Script implementation. - */ #pragma once - namespace flecs { @@ -39289,7 +38482,6 @@ void world::get_const_var( world_, name, value, type, default_value); } - namespace script { namespace _ { @@ -39304,11 +38496,6 @@ inline void init(flecs::world& world) { #endif -/** - * @file addons/cpp/impl/field.hpp - * @brief Field implementation. - */ - #pragma once namespace flecs @@ -39373,12 +38560,43 @@ T* field::operator->() const { return data_; } +template +inline base_field::base_field(iter &iter, int32_t index) { + *this = iter.base_field(index); } -/** - * @file addons/cpp/impl/iter.hpp - * @brief Iterator implementation. - */ +template +T& base_field::operator[](size_t index) const { + ecs_assert(data_ != nullptr, ECS_INVALID_OPERATION, + "invalid nullptr dereference of component type %s", + _::type_name()); + ecs_assert(index < count_, ECS_COLUMN_INDEX_OUT_OF_RANGE, + "index %d out of range for array of component type %s", + index, _::type_name()); + ecs_assert(!index || !is_shared_, ECS_INVALID_PARAMETER, + "non-zero index invalid for shared field of component type %s", + _::type_name()); + return *reinterpret_cast( + reinterpret_cast(data_) + index * stride_); +} + +template +T& base_field::operator*() const { + ecs_assert(data_ != nullptr, ECS_INVALID_OPERATION, + "invalid nullptr dereference of component type %s", + _::type_name()); + return *data_; +} + +template +T* base_field::operator->() const { + ecs_assert(data_ != nullptr, ECS_INVALID_OPERATION, + "invalid nullptr dereference of component type %s", + _::type_name()); + return data_; +} + +} #pragma once @@ -39474,6 +38692,28 @@ inline flecs::field iter::field(int8_t index) const { return get_field(index); } +/** Get base field data for a const component type. */ +template >> +inline flecs::base_field iter::base_field(int8_t index) const { + ecs_assert(!(iter_->flags & EcsIterCppEach) || + ecs_field_src(iter_, index) != 0, ECS_INVALID_OPERATION, + "cannot .base_field from .each, use .field_at<%s>(%d, row) instead", + _::type_name(), index); + return get_base_field(index); +} + +/** Get base field data for a mutable component type. */ +template >> +inline flecs::base_field iter::base_field(int8_t index) const { + ecs_assert(!(iter_->flags & EcsIterCppEach) || + ecs_field_src(iter_, index) != 0, ECS_INVALID_OPERATION, + "cannot .base_field from .each, use .field_at<%s>(%d, row) instead", + _::type_name(), index); + ecs_assert(!ecs_field_is_readonly(iter_, index), + ECS_ACCESS_VIOLATION, NULL); + return get_base_field(index); +} + /** Get the value of a variable by ID. */ inline flecs::entity iter::get_var(int var_id) const { ecs_assert(var_id != -1, ECS_INVALID_PARAMETER, 0); @@ -39512,11 +38752,6 @@ void iter::targets(int8_t index, const Func& func) { } // namespace flecs -/** - * @file addons/cpp/impl/world.hpp - * @brief World implementation. - */ - #pragma once namespace flecs @@ -40007,7 +39242,6 @@ inline flecs::scoped_world world::scope(const char* name) const { } // namespace flecs - /** * @defgroup cpp_core Core * Core ECS functionality (entities, storage, queries). @@ -40032,6 +39266,5 @@ inline flecs::scoped_world world::scope(const char* name) const { #endif - #endif diff --git a/examples/c/queries/component_inheritance/project.json b/examples/c/queries/component_inheritance/project.json index 8eac764264..2465010479 100644 --- a/examples/c/queries/component_inheritance/project.json +++ b/examples/c/queries/component_inheritance/project.json @@ -7,4 +7,4 @@ ], "public": false } -} \ No newline at end of file +} diff --git a/examples/c/queries/component_inheritance/src/main.c b/examples/c/queries/component_inheritance/src/main.c index e5899768d0..7cdaa9e0d6 100644 --- a/examples/c/queries/component_inheritance/src/main.c +++ b/examples/c/queries/component_inheritance/src/main.c @@ -1,70 +1,74 @@ #include #include -// This example shows how queries can be used to match simple inheritance trees. +// This example shows how queries can match components that derive from a base +// component through the IsA relationship. A query for the base component +// matches all entities that have a derived component, and can read the members +// that are inherited from the base. See the tag_inheritance example for +// inheritance trees built from tags instead of components. -int main(int argc, char *argv[]) { - ecs_world_t *ecs = ecs_init_w_args(argc, argv); +// Buf is the base component. HealthBuf and ManaBuf derive from it, so they +// start with the same members as Buf (the base layout is a prefix of the +// derived layout). +typedef struct { + float value; +} Buf; + +typedef struct { + float value; +} HealthBuf; - // Use convenience macros to create simple hierarchy of unit types. - // This macro call: - // ECS_ENTITY(ecs, CombatUnit, (IsA, Unit)) - // - // is the same as these C statements: - // ecs_entity_t CombatUnit = ecs_new_entity("CombatUnit"); - // ecs_add_pair(ecs, CombatUnit, EcsIsA, Unit); +typedef struct { + float value; +} ManaBuf; - ECS_TAG(ecs, Unit); - ECS_ENTITY(ecs, CombatUnit, (IsA, Unit)); - ECS_ENTITY(ecs, MeleeUnit, (IsA, CombatUnit)); - ECS_ENTITY(ecs, RangedUnit, (IsA, CombatUnit)); +int main(int argc, char *argv[]) { + ecs_world_t *ecs = ecs_init_w_args(argc, argv); - ECS_ENTITY(ecs, Warrior, (IsA, MeleeUnit)); - ECS_ENTITY(ecs, Wizard, (IsA, RangedUnit)); - ECS_ENTITY(ecs, Marksman, (IsA, RangedUnit)); - ECS_ENTITY(ecs, Builder, (IsA, Unit)); + ECS_COMPONENT(ecs, Buf); + ECS_COMPONENT(ecs, HealthBuf); + ECS_COMPONENT(ecs, ManaBuf); - // Create a few units - ecs_entity_t warrior_1 = ecs_entity(ecs, { .name = "warrior_1" }); - ecs_add(ecs, warrior_1, Warrior); - ecs_entity_t warrior_2 = ecs_entity(ecs, { .name = "warrior_2" }); - ecs_add(ecs, warrior_2, Warrior); + // Make the ECS aware of the inheritance relationships. + ecs_add_pair(ecs, ecs_id(HealthBuf), EcsIsA, ecs_id(Buf)); + ecs_add_pair(ecs, ecs_id(ManaBuf), EcsIsA, ecs_id(Buf)); - ecs_entity_t marksman_1 = ecs_entity(ecs, { .name = "marksman_1" }); - ecs_add(ecs, marksman_1, Marksman); - ecs_entity_t marksman_2 = ecs_entity(ecs, { .name = "marksman_2" }); - ecs_add(ecs, marksman_2, Marksman); + // Create a few entities with derived buf components + ecs_entity_t warrior = ecs_entity(ecs, { .name = "warrior" }); + ecs_set(ecs, warrior, HealthBuf, { .value = 10 }); - ecs_entity_t wizard_1 = ecs_entity(ecs, { .name = "wizard_1" }); - ecs_add(ecs, wizard_1, Wizard); - ecs_entity_t wizard_2 = ecs_entity(ecs, { .name = "wizard_2" }); - ecs_add(ecs, wizard_2, Wizard); + ecs_entity_t wizard = ecs_entity(ecs, { .name = "wizard" }); + ecs_set(ecs, wizard, ManaBuf, { .value = 25 }); - ecs_entity_t builder_1 = ecs_entity(ecs, { .name = "builder_1" }); - ecs_add(ecs, builder_1, Builder); - ecs_entity_t builder_2 = ecs_entity(ecs, { .name = "builder_2" }); - ecs_add(ecs, builder_2, Builder); + ecs_entity_t paladin = ecs_entity(ecs, { .name = "paladin" }); + ecs_set(ecs, paladin, HealthBuf, { .value = 5 }); - // Create a query to find all ranged units + // Create a query for the base component. This matches all entities with a + // component that derives from Buf. ecs_query_t *q = ecs_query(ecs, { - .terms = {{ .id = RangedUnit }} + .terms = {{ .id = ecs_id(Buf) }} }); - // Iterate the query + // Iterate the query. Because the matched component can be a derived type + // that is larger than Buf, use ecs_base_field to get the field pointer and + // ecs_field_stride to advance from one element to the next. ecs_iter_t it = ecs_query_iter(ecs, q); while (ecs_query_next(&it)) { + void *bufs = ecs_base_field(&it, Buf, 0); + size_t stride = ecs_field_stride(&it, 0); for (int i = 0; i < it.count; i ++) { - printf("Unit %s found\n", ecs_get_name(ecs, it.entities[i])); + Buf *buf = ECS_OFFSET(bufs, stride * (size_t)i); + printf("%s has buf value %.0f\n", + ecs_get_name(ecs, it.entities[i]), (double)buf->value); } } ecs_query_fini(q); // Output - // Unit wizard_1 found - // Unit wizard_2 found - // Unit marksman_1 found - // Unit marksman_2 found + // warrior has buf value 10 + // paladin has buf value 5 + // wizard has buf value 25 return ecs_fini(ecs); } diff --git a/examples/c/queries/setting_variables/src/main.c b/examples/c/queries/setting_variables/src/main.c index 5d8a7abe5d..01c386c89c 100644 --- a/examples/c/queries/setting_variables/src/main.c +++ b/examples/c/queries/setting_variables/src/main.c @@ -1,7 +1,7 @@ #include #include -// This example extends the component_inheritance example, and shows how +// This example extends the tag_inheritance example, and shows how // we can use a single query to match units from different players and platoons // by setting query variables before we iterate. // @@ -14,7 +14,7 @@ static const int PlatoonsPerPlayer = 3; int main(int argc, char *argv[]) { ecs_world_t *ecs = ecs_init_w_args(argc, argv); - // Unit datamodel - see component_inheritance example + // Unit datamodel - see tag_inheritance example ECS_TAG(ecs, Unit); ECS_ENTITY(ecs, CombatUnit, (IsA, Unit)); ECS_ENTITY(ecs, MeleeUnit, (IsA, CombatUnit)); diff --git a/examples/c/queries/tag_inheritance/BUILD b/examples/c/queries/tag_inheritance/BUILD new file mode 100644 index 0000000000..8b516ca54f --- /dev/null +++ b/examples/c/queries/tag_inheritance/BUILD @@ -0,0 +1,11 @@ +cc_binary( + name = "tag_inheritance", + srcs = glob([ + "src/*.c", + "include/**/*.h", + ]), + includes = ["include"], + deps = [ + "//:flecs", + ], +) diff --git a/examples/c/queries/tag_inheritance/include/tag_inheritance.h b/examples/c/queries/tag_inheritance/include/tag_inheritance.h new file mode 100644 index 0000000000..4700f514d9 --- /dev/null +++ b/examples/c/queries/tag_inheritance/include/tag_inheritance.h @@ -0,0 +1,16 @@ +#ifndef TAG_INHERITANCE_H +#define TAG_INHERITANCE_H + +/* This generated file contains includes for project dependencies */ +#include "tag_inheritance/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/examples/c/queries/tag_inheritance/include/tag_inheritance/bake_config.h b/examples/c/queries/tag_inheritance/include/tag_inheritance/bake_config.h new file mode 100644 index 0000000000..ecdf94f922 --- /dev/null +++ b/examples/c/queries/tag_inheritance/include/tag_inheritance/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * This file is generated by bake.lang.c for your convenience. Headers of + * dependencies will automatically show up in this file. Include bake_config.h + * in your main project file. Do not edit! */ + +#ifndef TAG_INHERITANCE_BAKE_CONFIG_H +#define TAG_INHERITANCE_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include + +#endif + diff --git a/examples/c/queries/tag_inheritance/project.json b/examples/c/queries/tag_inheritance/project.json new file mode 100644 index 0000000000..7a64450ac3 --- /dev/null +++ b/examples/c/queries/tag_inheritance/project.json @@ -0,0 +1,10 @@ +{ + "id": "tag_inheritance", + "type": "application", + "value": { + "use": [ + "flecs" + ], + "public": false + } +} \ No newline at end of file diff --git a/examples/c/queries/tag_inheritance/src/main.c b/examples/c/queries/tag_inheritance/src/main.c new file mode 100644 index 0000000000..c4f87cce7e --- /dev/null +++ b/examples/c/queries/tag_inheritance/src/main.c @@ -0,0 +1,72 @@ +#include +#include + +// This example shows how queries can be used to match simple inheritance trees +// built from tags. See the component_inheritance example for inheritance trees +// built from components with data. + +int main(int argc, char *argv[]) { + ecs_world_t *ecs = ecs_init_w_args(argc, argv); + + // Use convenience macros to create simple hierarchy of unit types. + // This macro call: + // ECS_ENTITY(ecs, CombatUnit, (IsA, Unit)) + // + // is the same as these C statements: + // ecs_entity_t CombatUnit = ecs_new_entity("CombatUnit"); + // ecs_add_pair(ecs, CombatUnit, EcsIsA, Unit); + + ECS_TAG(ecs, Unit); + ECS_ENTITY(ecs, CombatUnit, (IsA, Unit)); + ECS_ENTITY(ecs, MeleeUnit, (IsA, CombatUnit)); + ECS_ENTITY(ecs, RangedUnit, (IsA, CombatUnit)); + + ECS_ENTITY(ecs, Warrior, (IsA, MeleeUnit)); + ECS_ENTITY(ecs, Wizard, (IsA, RangedUnit)); + ECS_ENTITY(ecs, Marksman, (IsA, RangedUnit)); + ECS_ENTITY(ecs, Builder, (IsA, Unit)); + + // Create a few units + ecs_entity_t warrior_1 = ecs_entity(ecs, { .name = "warrior_1" }); + ecs_add(ecs, warrior_1, Warrior); + ecs_entity_t warrior_2 = ecs_entity(ecs, { .name = "warrior_2" }); + ecs_add(ecs, warrior_2, Warrior); + + ecs_entity_t marksman_1 = ecs_entity(ecs, { .name = "marksman_1" }); + ecs_add(ecs, marksman_1, Marksman); + ecs_entity_t marksman_2 = ecs_entity(ecs, { .name = "marksman_2" }); + ecs_add(ecs, marksman_2, Marksman); + + ecs_entity_t wizard_1 = ecs_entity(ecs, { .name = "wizard_1" }); + ecs_add(ecs, wizard_1, Wizard); + ecs_entity_t wizard_2 = ecs_entity(ecs, { .name = "wizard_2" }); + ecs_add(ecs, wizard_2, Wizard); + + ecs_entity_t builder_1 = ecs_entity(ecs, { .name = "builder_1" }); + ecs_add(ecs, builder_1, Builder); + ecs_entity_t builder_2 = ecs_entity(ecs, { .name = "builder_2" }); + ecs_add(ecs, builder_2, Builder); + + // Create a query to find all ranged units + ecs_query_t *q = ecs_query(ecs, { + .terms = {{ .id = RangedUnit }} + }); + + // Iterate the query + ecs_iter_t it = ecs_query_iter(ecs, q); + while (ecs_query_next(&it)) { + for (int i = 0; i < it.count; i ++) { + printf("Unit %s found\n", ecs_get_name(ecs, it.entities[i])); + } + } + + ecs_query_fini(q); + + // Output + // Unit wizard_1 found + // Unit wizard_2 found + // Unit marksman_1 found + // Unit marksman_2 found + + return ecs_fini(ecs); +} diff --git a/examples/c/queries/transitive_queries/src/main.c b/examples/c/queries/transitive_queries/src/main.c index 57ff2596a3..9f333ee8fc 100644 --- a/examples/c/queries/transitive_queries/src/main.c +++ b/examples/c/queries/transitive_queries/src/main.c @@ -10,7 +10,7 @@ // San Francisco is located in the United States // Therefore Bob also lives in the United States. // -// An example of transitivity can be seen in the component_inheritance example. +// An example of transitivity can be seen in the tag_inheritance example. // This example uses the builtin IsA relationship, which is transitive. This // example shows how to achieve similar behavior with a user-defined relationship. diff --git a/examples/cpp/queries/component_inheritance/project.json b/examples/cpp/queries/component_inheritance/project.json index f8db06c1ec..73d6a2a15b 100644 --- a/examples/cpp/queries/component_inheritance/project.json +++ b/examples/cpp/queries/component_inheritance/project.json @@ -8,4 +8,4 @@ "language": "c++", "public": false } -} \ No newline at end of file +} diff --git a/examples/cpp/queries/component_inheritance/src/main.cpp b/examples/cpp/queries/component_inheritance/src/main.cpp index a8eb4afbcc..dd8ea94896 100644 --- a/examples/cpp/queries/component_inheritance/src/main.cpp +++ b/examples/cpp/queries/component_inheritance/src/main.cpp @@ -1,56 +1,45 @@ #include #include -// This example shows how queries can be used to match simple inheritance trees. - -struct Unit { }; -struct CombatUnit : Unit { }; -struct MeleeUnit : CombatUnit { }; -struct RangedUnit : CombatUnit { }; - -struct Warrior : MeleeUnit { }; -struct Wizard : RangedUnit { }; -struct Marksman : RangedUnit { }; -struct Builder : Unit { }; +// This example shows how queries can match components that derive from a base +// component through the IsA relationship. A query for the base component +// matches all entities that have a derived component, and can read the members +// that are inherited from the base. See the tag_inheritance example for +// inheritance trees built from tags instead of components. + +// Buf is the base component. HealthBuf and ManaBuf derive from it, so they +// inherit the base members (the base subobject is stored at the start of the +// derived component). +struct Buf { + float value; +}; + +struct HealthBuf : Buf { }; +struct ManaBuf : Buf { }; int main(int, char *[]) { flecs::world ecs; - // Make the ECS aware of the inheritance relationships. Note that IsA - // relationship used here is the same as in the prefab example. - ecs.component().is_a(); - ecs.component().is_a(); - ecs.component().is_a(); - - ecs.component().is_a(); - ecs.component().is_a(); - ecs.component().is_a(); - ecs.component().is_a(); - - // Create a few units - ecs.entity("warrior_1").add(); - ecs.entity("warrior_2").add(); - - ecs.entity("marksman_1").add(); - ecs.entity("marksman_2").add(); - - ecs.entity("wizard_1").add(); - ecs.entity("wizard_2").add(); + // Make the ECS aware of the inheritance relationships. + ecs.component().is_a(); + ecs.component().is_a(); - ecs.entity("builder_1").add(); - ecs.entity("builder_2").add(); + // Create a few entities with derived buf components + ecs.entity("warrior").set({{ 10 }}); + ecs.entity("wizard").set({{ 25 }}); + ecs.entity("paladin").set({{ 5 }}); - // Create a query to find all ranged units - flecs::query q = ecs.query(); + // Create a query for the base component. This matches all entities with a + // component that derives from Buf. + flecs::query q = ecs.query(); - // Iterate the query - q.each([](flecs::entity e, RangedUnit) { - std::cout << "Unit " << e.name() << " found\n"; + // Iterate the query. The Buf members are accessible for derived components. + q.each([](flecs::entity e, Buf& b) { + std::cout << e.name() << " has buf value " << b.value << "\n"; }); // Output: - // Unit wizard_1 found - // Unit wizard_2 found - // Unit marksman_1 found - // Unit marksman_2 found + // warrior has buf value 10 + // paladin has buf value 5 + // wizard has buf value 25 } diff --git a/examples/cpp/queries/setting_variables/src/main.cpp b/examples/cpp/queries/setting_variables/src/main.cpp index 21d7d85de6..7db5beb1c1 100644 --- a/examples/cpp/queries/setting_variables/src/main.cpp +++ b/examples/cpp/queries/setting_variables/src/main.cpp @@ -1,7 +1,7 @@ #include #include -// This example extends the component_inheritance example, and shows how +// This example extends the tag_inheritance example, and shows how // we can use a single query to match units from different players and platoons // by setting query variables before we iterate. // @@ -27,7 +27,7 @@ static const int PlatoonsPerPlayer = 3; int main(int, char *[]) { flecs::world ecs; - // See component_inheritance example + // See tag_inheritance example ecs.component().is_a(); ecs.component().is_a(); ecs.component().is_a(); diff --git a/examples/cpp/queries/tag_inheritance/BUILD b/examples/cpp/queries/tag_inheritance/BUILD new file mode 100644 index 0000000000..2f20f31616 --- /dev/null +++ b/examples/cpp/queries/tag_inheritance/BUILD @@ -0,0 +1,11 @@ +cc_binary( + name = "tag_inheritance", + srcs = glob([ + "src/*.cpp", + "include/**/*.h", + ]), + includes = ["include"], + deps = [ + "//:flecs", + ], +) diff --git a/examples/cpp/queries/tag_inheritance/include/tag_inheritance.h b/examples/cpp/queries/tag_inheritance/include/tag_inheritance.h new file mode 100644 index 0000000000..4700f514d9 --- /dev/null +++ b/examples/cpp/queries/tag_inheritance/include/tag_inheritance.h @@ -0,0 +1,16 @@ +#ifndef TAG_INHERITANCE_H +#define TAG_INHERITANCE_H + +/* This generated file contains includes for project dependencies */ +#include "tag_inheritance/bake_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/examples/cpp/queries/tag_inheritance/include/tag_inheritance/bake_config.h b/examples/cpp/queries/tag_inheritance/include/tag_inheritance/bake_config.h new file mode 100644 index 0000000000..ecdf94f922 --- /dev/null +++ b/examples/cpp/queries/tag_inheritance/include/tag_inheritance/bake_config.h @@ -0,0 +1,24 @@ +/* + ) + (.) + .|. + | | + _.--| |--._ + .-'; ;`-'& ; `&. + \ & ; & &_/ + |"""---...---"""| + \ | | | | | | | / + `---.|.|.|.---' + + * This file is generated by bake.lang.c for your convenience. Headers of + * dependencies will automatically show up in this file. Include bake_config.h + * in your main project file. Do not edit! */ + +#ifndef TAG_INHERITANCE_BAKE_CONFIG_H +#define TAG_INHERITANCE_BAKE_CONFIG_H + +/* Headers of public dependencies */ +#include + +#endif + diff --git a/examples/cpp/queries/tag_inheritance/project.json b/examples/cpp/queries/tag_inheritance/project.json new file mode 100644 index 0000000000..916fe30540 --- /dev/null +++ b/examples/cpp/queries/tag_inheritance/project.json @@ -0,0 +1,11 @@ +{ + "id": "tag_inheritance", + "type": "application", + "value": { + "use": [ + "flecs" + ], + "language": "c++", + "public": false + } +} \ No newline at end of file diff --git a/examples/cpp/queries/tag_inheritance/src/main.cpp b/examples/cpp/queries/tag_inheritance/src/main.cpp new file mode 100644 index 0000000000..04269c045a --- /dev/null +++ b/examples/cpp/queries/tag_inheritance/src/main.cpp @@ -0,0 +1,58 @@ +#include +#include + +// This example shows how queries can be used to match simple inheritance trees +// built from tags. See the component_inheritance example for inheritance trees +// built from components with data. + +struct Unit { }; +struct CombatUnit : Unit { }; +struct MeleeUnit : CombatUnit { }; +struct RangedUnit : CombatUnit { }; + +struct Warrior : MeleeUnit { }; +struct Wizard : RangedUnit { }; +struct Marksman : RangedUnit { }; +struct Builder : Unit { }; + +int main(int, char *[]) { + flecs::world ecs; + + // Make the ECS aware of the inheritance relationships. Note that IsA + // relationship used here is the same as in the prefab example. + ecs.component().is_a(); + ecs.component().is_a(); + ecs.component().is_a(); + + ecs.component().is_a(); + ecs.component().is_a(); + ecs.component().is_a(); + ecs.component().is_a(); + + // Create a few units + ecs.entity("warrior_1").add(); + ecs.entity("warrior_2").add(); + + ecs.entity("marksman_1").add(); + ecs.entity("marksman_2").add(); + + ecs.entity("wizard_1").add(); + ecs.entity("wizard_2").add(); + + ecs.entity("builder_1").add(); + ecs.entity("builder_2").add(); + + // Create a query to find all ranged units + flecs::query q = ecs.query(); + + // Iterate the query + q.each([](flecs::entity e, RangedUnit) { + std::cout << "Unit " << e.name() << " found\n"; + }); + + // Output: + // Unit wizard_1 found + // Unit wizard_2 found + // Unit marksman_1 found + // Unit marksman_2 found +} diff --git a/examples/cpp/queries/transitive_queries/src/main.cpp b/examples/cpp/queries/transitive_queries/src/main.cpp index 5ea522837f..ae46f6a015 100644 --- a/examples/cpp/queries/transitive_queries/src/main.cpp +++ b/examples/cpp/queries/transitive_queries/src/main.cpp @@ -10,7 +10,7 @@ // San Francisco is located in the United States // Therefore Bob also lives in the United States. // -// An example of transitivity can be seen in the component_inheritance example. +// An example of transitivity can be seen in the tag_inheritance example. // This example uses the builtin IsA relationship, which is transitive. This // example shows how to achieve similar behavior with a user-defined relationship. diff --git a/include/flecs.h b/include/flecs.h index d9231d73a5..77a889530e 100644 --- a/include/flecs.h +++ b/include/flecs.h @@ -5820,6 +5820,24 @@ void* ecs_field_w_size( size_t size, int8_t index); +/** Get data for a field matched through component inheritance. + * This operation is like ecs_field_w_size(), but is used when a field is + * matched on a component that is derived from the queried (base) type. Because + * the stored (derived) component can be larger than the requested (base) type, + * the returned pointer is computed using the stride of the stored component. To + * iterate the values, use the stride returned by ecs_field_stride(). + * + * @param it The iterator. + * @param size The size of the field type. + * @param index The index of the field. + * @return A pointer to the data of the field. + */ +FLECS_API +void* ecs_base_field_w_size( + const ecs_iter_t *it, + size_t size, + int8_t index); + /** Get data for a field at a specified row. * This operation should be used instead of ecs_field_w_size() for sparse * component fields. This operation should be called for each returned row in a @@ -5936,6 +5954,19 @@ size_t ecs_field_size( const ecs_iter_t *it, int8_t index); +/** Return the storage stride of a field. + * Like ecs_field_size(), but for a field matched through component inheritance + * returns the size of the stored (derived) component, i.e. the array stride. + * + * @param it The iterator. + * @param index The index of the field in the iterator. + * @return The storage stride for the field. + */ +FLECS_API +size_t ecs_field_stride( + const ecs_iter_t *it, + int8_t index); + /** Test whether the field is matched on self. * This operation returns whether the field is matched on the currently iterated * entity. This function will return false when the field is owned by another diff --git a/include/flecs/addons/cpp/component.hpp b/include/flecs/addons/cpp/component.hpp index a99cf93176..8016090fa5 100644 --- a/include/flecs/addons/cpp/component.hpp +++ b/include/flecs/addons/cpp/component.hpp @@ -457,6 +457,42 @@ struct component : untyped_component { id_ = _::type::register_id(world, name, allow_tag, true, true, id); } + /** Mark this component as inheriting from a base component. + * + * @tparam Base The base component. + * @return Reference to self for chaining. + */ + template + component& is_a() { + if constexpr (std::is_base_of::value && + !std::is_same::value) + { + alignas(T) static char storage[sizeof(T)]; + T* derived_ptr = reinterpret_cast(&storage[0]); + Base* base_ptr = static_cast(derived_ptr); + ecs_assert( + reinterpret_cast(base_ptr) == + reinterpret_cast(derived_ptr), + ECS_INVALID_OPERATION, + "component inheritance requires the base component to be at " + "offset 0 in the derived component (multiple/virtual " + "inheritance is not supported)"); + (void)base_ptr; + } + this->add(flecs::IsA, _::type::id(this->world_)); + return *this; + } + + /** Mark this component as inheriting from a base component. + * + * @param base The base component id. + * @return Reference to self for chaining. + */ + component& is_a(flecs::entity_t base) { + this->add(flecs::IsA, base); + return *this; + } + /** Register on_add hook. * * @param func The callback function. diff --git a/include/flecs/addons/cpp/delegate.hpp b/include/flecs/addons/cpp/delegate.hpp index 07467ed5db..dd24f07478 100644 --- a/include/flecs/addons/cpp/delegate.hpp +++ b/include/flecs/addons/cpp/delegate.hpp @@ -43,6 +43,7 @@ struct component_binding_ctx { // Utility to convert a template argument pack to an array of term pointers. struct field_ptr { void *ptr = nullptr; + int32_t stride = 0; int8_t index = 0; bool is_ref = false; bool is_row = false; @@ -52,34 +53,41 @@ template struct field_ptrs { using array = flecs::array<_::field_ptr, sizeof...(Components)>; - void populate(const ecs_iter_t *iter) { - populate_impl(iter, std::index_sequence_for{}); - } - void populate_self(const ecs_iter_t *iter) { populate_self_impl(iter, std::index_sequence_for{}); } + void populate_inherited(const ecs_iter_t *iter) { + populate_inherited_impl(iter, std::index_sequence_for{}); + } + array fields_; private: template - void populate_field(const ecs_iter_t *iter, size_t index) { + void populate_inherited_field(const ecs_iter_t *iter, size_t index) { using A = remove_pointer_t>; if constexpr (!is_empty_v) { if (iter->row_fields & (1llu << index)) { - /* Need to fetch the value with ecs_field_at() */ fields_[index].is_row = true; fields_[index].is_ref = true; fields_[index].index = static_cast(index); } else { - fields_[index].ptr = ecs_field_w_size(iter, sizeof(A), + fields_[index].ptr = ecs_base_field_w_size(iter, sizeof(A), static_cast(index)); fields_[index].is_ref = iter->sources[index] != 0; } + fields_[index].stride = static_cast( + ecs_field_stride(iter, static_cast(index))); } } + template + void populate_inherited_impl(const ecs_iter_t *iter, std::index_sequence) { + (void)iter; + (populate_inherited_field(iter, Is), ...); + } + template void populate_self_field(const ecs_iter_t *iter, size_t index) { (void)iter; (void)index; @@ -92,12 +100,6 @@ struct field_ptrs { } } - template - void populate_impl(const ecs_iter_t *iter, std::index_sequence) { - (void)iter; - (populate_field(iter, Is), ...); - } - template void populate_self_impl(const ecs_iter_t *iter, std::index_sequence) { (void)iter; @@ -188,28 +190,85 @@ struct each_field::value && } }; +template +struct each_field_strided { }; + +template +struct each_field_strided::value && + !is_empty>::value && is_actual::value > > + : each_column_base +{ + each_field_strided(const flecs::iter_t*, _::field_ptr& field, size_t row) + : each_column_base(field, row) { } + + T& get_row() { + return *reinterpret_cast(static_cast(this->field_.ptr) + + this->row_ * static_cast(this->field_.stride)); + } +}; + +template +struct each_field_strided::value && + !is_empty>::value && !is_actual::value> > + : each_column_base +{ + each_field_strided(const flecs::iter_t*, _::field_ptr& field, size_t row) + : each_column_base(field, row) { } + + T get_row() { + return *reinterpret_cast*>( + static_cast(this->field_.ptr) + + this->row_ * static_cast(this->field_.stride)); + } +}; + +template +struct each_field_strided>::value && + !is_pointer::value > > + : each_column_base +{ + each_field_strided(const flecs::iter_t*, _::field_ptr& field, size_t row) + : each_column_base(field, row) { } + + T get_row() { + return actual_type_t(); + } +}; + +template +struct each_field_strided::value && + !is_empty>::value > > + : each_column_base +{ + each_field_strided(const flecs::iter_t*, _::field_ptr& field, size_t row) + : each_column_base(field, row) { } + + actual_type_t get_row() { + if (this->field_.ptr) { + return reinterpret_cast>( + static_cast(this->field_.ptr) + + this->row_ * static_cast(this->field_.stride)); + } else { + return nullptr; + } + } +}; + // If the query contains component references to other entities, check if the // current argument is one. template -struct each_ref_field : public each_field { +struct each_ref_field_strided : public each_field_strided { using A = remove_pointer_t>; - each_ref_field(const flecs::iter_t *iter, _::field_ptr& field, size_t row) - : each_field(iter, field, row) { + each_ref_field_strided(const flecs::iter_t *iter, _::field_ptr& field, size_t row) + : each_field_strided(iter, field, row) { if (field.is_ref) { - // If this is a reference, set the row to 0 as a ref is always a - // single value, not an array. This prevents the application from - // having to do an if-check on whether the field is owned. - // - // This check only happens when the current table being iterated - // over caused the query to match a reference. The check is - // performed once per iterated table. this->row_ = 0; } if (field.is_row) { - field.ptr = ecs_field_at_w_size(iter, sizeof(A), field.index, + field.ptr = ecs_field_at_w_size(iter, sizeof(A), field.index, static_cast(row)); } } @@ -235,9 +294,11 @@ struct each_delegate : public delegate { iter->flags |= EcsIterCppEach; - if (iter->ref_fields | iter->up_fields) { - terms.populate(iter); - invoke_unpack< each_ref_field >(iter, func_, 0, terms.fields_); + if (iter->ref_fields | iter->up_fields | + (iter->flags & EcsIterComponentInheritance)) + { + terms.populate_inherited(iter); + invoke_unpack< each_ref_field_strided >(iter, func_, 0, terms.fields_); } else { terms.populate_self(iter); invoke_unpack< each_field >(iter, func_, 0, terms.fields_); @@ -404,9 +465,11 @@ struct find_delegate : public delegate { iter->flags |= EcsIterCppEach; - if (iter->ref_fields | iter->up_fields) { - terms.populate(iter); - return invoke_callback< each_ref_field >(iter, func_, 0, terms.fields_); + if (iter->ref_fields | iter->up_fields | + (iter->flags & EcsIterComponentInheritance)) + { + terms.populate_inherited(iter); + return invoke_callback< each_ref_field_strided >(iter, func_, 0, terms.fields_); } else { terms.populate_self(iter); return invoke_callback< each_field >(iter, func_, 0, terms.fields_); diff --git a/include/flecs/addons/cpp/field.hpp b/include/flecs/addons/cpp/field.hpp index a6ce8be146..00fc92d586 100644 --- a/include/flecs/addons/cpp/field.hpp +++ b/include/flecs/addons/cpp/field.hpp @@ -108,6 +108,40 @@ struct field { bool is_shared_; }; +/** Wrapper class around a field matched through component inheritance. + * + * Same interface as field, but uses a runtime stride so it can iterate a field + * whose stored (derived) component is larger than T. Unlike field, this does + * not assert when the matched id is a subtype of T. + * + * @tparam T Base component type of the field. + * + * @ingroup cpp_iterator + */ +template +struct base_field { + static_assert(std::is_empty::value == false, + "invalid type for field, cannot iterate empty type"); + + base_field(T* array, size_t stride, size_t count, bool is_shared = false) + : data_(array) + , stride_(stride) + , count_(count) + , is_shared_(is_shared) {} + + base_field(iter &iter, int field); + + T& operator[](size_t index) const; + T& operator*() const; + T* operator->() const; + +protected: + T* data_; + size_t stride_; + size_t count_; + bool is_shared_; +}; + } // namespace flecs /** @} */ diff --git a/include/flecs/addons/cpp/impl/field.hpp b/include/flecs/addons/cpp/impl/field.hpp index 205e4abb7c..113d973d8c 100644 --- a/include/flecs/addons/cpp/impl/field.hpp +++ b/include/flecs/addons/cpp/impl/field.hpp @@ -67,4 +67,40 @@ T* field::operator->() const { return data_; } +template +inline base_field::base_field(iter &iter, int32_t index) { + *this = iter.base_field(index); +} + +template +T& base_field::operator[](size_t index) const { + ecs_assert(data_ != nullptr, ECS_INVALID_OPERATION, + "invalid nullptr dereference of component type %s", + _::type_name()); + ecs_assert(index < count_, ECS_COLUMN_INDEX_OUT_OF_RANGE, + "index %d out of range for array of component type %s", + index, _::type_name()); + ecs_assert(!index || !is_shared_, ECS_INVALID_PARAMETER, + "non-zero index invalid for shared field of component type %s", + _::type_name()); + return *reinterpret_cast( + reinterpret_cast(data_) + index * stride_); +} + +template +T& base_field::operator*() const { + ecs_assert(data_ != nullptr, ECS_INVALID_OPERATION, + "invalid nullptr dereference of component type %s", + _::type_name()); + return *data_; +} + +template +T* base_field::operator->() const { + ecs_assert(data_ != nullptr, ECS_INVALID_OPERATION, + "invalid nullptr dereference of component type %s", + _::type_name()); + return data_; +} + } diff --git a/include/flecs/addons/cpp/impl/iter.hpp b/include/flecs/addons/cpp/impl/iter.hpp index eb3a6ad003..1165bc5ac0 100644 --- a/include/flecs/addons/cpp/impl/iter.hpp +++ b/include/flecs/addons/cpp/impl/iter.hpp @@ -97,6 +97,28 @@ inline flecs::field iter::field(int8_t index) const { return get_field(index); } +/** Get base field data for a const component type. */ +template >> +inline flecs::base_field iter::base_field(int8_t index) const { + ecs_assert(!(iter_->flags & EcsIterCppEach) || + ecs_field_src(iter_, index) != 0, ECS_INVALID_OPERATION, + "cannot .base_field from .each, use .field_at<%s>(%d, row) instead", + _::type_name(), index); + return get_base_field(index); +} + +/** Get base field data for a mutable component type. */ +template >> +inline flecs::base_field iter::base_field(int8_t index) const { + ecs_assert(!(iter_->flags & EcsIterCppEach) || + ecs_field_src(iter_, index) != 0, ECS_INVALID_OPERATION, + "cannot .base_field from .each, use .field_at<%s>(%d, row) instead", + _::type_name(), index); + ecs_assert(!ecs_field_is_readonly(iter_, index), + ECS_ACCESS_VIOLATION, NULL); + return get_base_field(index); +} + /** Get the value of a variable by ID. */ inline flecs::entity iter::get_var(int var_id) const { ecs_assert(var_id != -1, ECS_INVALID_PARAMETER, 0); diff --git a/include/flecs/addons/cpp/iter.hpp b/include/flecs/addons/cpp/iter.hpp index 3a80a9f2c0..c8bafedd22 100644 --- a/include/flecs/addons/cpp/iter.hpp +++ b/include/flecs/addons/cpp/iter.hpp @@ -307,6 +307,28 @@ struct iter { template , if_not_t> = 0> flecs::field field(int8_t index) const; + /** Get access to a field matched through component inheritance. + * Like field(), but returns a base_field that iterates with a runtime + * stride, so it works when the matched (derived) component is larger than T. + * Unlike field(), this does not assert when the field is of type T or a + * subtype of T. + * + * @tparam T Base type of the field. + * @param index The field index. + * @return The field data. + */ + template , if_t> = 0> + flecs::base_field base_field(int8_t index) const; + + /** Get read/write access to a field matched through component inheritance. + * + * @tparam T Base type of the field. + * @param index The field index. + * @return The field data. + */ + template , if_not_t> = 0> + flecs::base_field base_field(int8_t index) const; + /** Get unchecked access to field data. * Unchecked access is required when a system does not know the type of a * field at compile time. @@ -511,6 +533,31 @@ struct iter { count, is_shared); } + /* Get field matched through component inheritance. Checks the queried + * (base) type matches T, not the (possibly derived) matched id, so a field + * of type T or a subtype of T is accepted. Uses a runtime stride. */ + template > + flecs::base_field get_base_field(int8_t index) const { + +#ifndef FLECS_NDEBUG + ecs_assert(ecs_field_size(iter_, index) == sizeof(A), + ECS_COLUMN_TYPE_MISMATCH, NULL); +#endif + + size_t count; + bool is_shared = !ecs_field_is_self(iter_, index); + + if (is_shared) { + count = 1; + } else { + count = static_cast(iter_->count); + } + + return flecs::base_field( + static_cast(ecs_base_field_w_size(iter_, sizeof(A), index)), + ecs_field_stride(iter_, index), count, is_shared); + } + /* Get field, check if correct type is used. */ template > flecs::field get_field_at(int8_t index, int32_t row) const { diff --git a/include/flecs/addons/cpp/mixins/query/builder_i.hpp b/include/flecs/addons/cpp/mixins/query/builder_i.hpp index 300c72c5b8..dd008eb1ed 100644 --- a/include/flecs/addons/cpp/mixins/query/builder_i.hpp +++ b/include/flecs/addons/cpp/mixins/query/builder_i.hpp @@ -22,6 +22,17 @@ struct query_builder_i : term_builder_i { , expr_count_(0) , desc_(desc) { } + /** Append terms from type-erased id/inout/oper arrays. + * Used by the signature population path to keep per-signature code minimal. + */ + Base& populate_terms(const flecs::id_t *ids, const flecs::inout_kind_t *inout, + const flecs::oper_kind_t *oper, int32_t count) + { + term_index_ = _::populate_query_terms( + desc_, term_index_, ids, inout, oper, count); + return *this; + } + /** Set the query flags. */ Base& query_flags(ecs_flags32_t flags) { desc_->flags |= flags; diff --git a/include/flecs/addons/cpp/mixins/query/impl.hpp b/include/flecs/addons/cpp/mixins/query/impl.hpp index fe30212679..b8a0a68471 100644 --- a/include/flecs/addons/cpp/mixins/query/impl.hpp +++ b/include/flecs/addons/cpp/mixins/query/impl.hpp @@ -259,7 +259,7 @@ struct query_base { * @ingroup cpp_core_queries */ template -struct query : query_base, iterable { +struct query : query_base, iterable, Components...> { private: using Fields = typename _::field_ptrs::array; @@ -293,9 +293,8 @@ struct query : query_base, iterable { return flecs::query<>(q); } -private: - ecs_iter_t get_iter(flecs::world_t *world) const override { - ecs_assert(query_ != nullptr, ECS_INVALID_PARAMETER, + ecs_iter_t get_iter(flecs::world_t *world) const { + ecs_assert(query_ != nullptr, ECS_INVALID_PARAMETER, "cannot iterate invalid query"); if (!world) { world = query_->world; @@ -303,7 +302,7 @@ struct query : query_base, iterable { return ecs_query_iter(world, query_); } - ecs_iter_next_action_t next_action() const override { + ecs_iter_next_action_t next_action() const { return ecs_query_next; } }; diff --git a/include/flecs/addons/cpp/utils/iterable.hpp b/include/flecs/addons/cpp/utils/iterable.hpp index b7304c8a75..f0ab1f80bf 100644 --- a/include/flecs/addons/cpp/utils/iterable.hpp +++ b/include/flecs/addons/cpp/utils/iterable.hpp @@ -17,8 +17,13 @@ struct page_iterable; template struct worker_iterable; -/** Base class for iterable query objects. */ -template +/** Base class for iterable query objects. + * + * Uses static (CRTP) dispatch: the most-derived type (Derived) provides the + * non-virtual get_iter()/next_action() methods. This keeps the typed iterables + * non-polymorphic, so no per-signature vtables or RTTI are emitted. + */ +template struct iterable { /** Each iterator. @@ -32,8 +37,8 @@ struct iterable { */ template void each(Func&& func) const { - ecs_iter_t it = this->get_iter(nullptr); - ecs_iter_next_action_t next = this->next_action(); + ecs_iter_t it = this->get_iter_(nullptr); + ecs_iter_next_action_t next = this->next_action_(); while (next(&it)) { _::each_delegate(func).invoke(&it); } @@ -48,7 +53,7 @@ struct iterable { */ template void run(Func&& func) const { - ecs_iter_t it = this->get_iter(nullptr); + ecs_iter_t it = this->get_iter_(nullptr); _::run_delegate(func).invoke(&it); } @@ -60,8 +65,8 @@ struct iterable { */ template flecs::entity find(Func&& func) const { - ecs_iter_t it = this->get_iter(nullptr); - ecs_iter_next_action_t next = this->next_action(); + ecs_iter_t it = this->get_iter_(nullptr); + ecs_iter_next_action_t next = this->next_action_(); flecs::entity result; while (!result && next(&it)) { @@ -160,20 +165,23 @@ struct iterable { return this->iter().template set_group(); } - /** Virtual destructor. */ - virtual ~iterable() { } -protected: - friend iter_iterable; - friend page_iterable; - friend worker_iterable; +private: + const Derived& derived_() const { + return *static_cast(this); + } - virtual ecs_iter_t get_iter(flecs::world_t *stage) const = 0; - virtual ecs_iter_next_action_t next_action() const = 0; + ecs_iter_t get_iter_(flecs::world_t *stage) const { + return derived_().get_iter(stage); + } + + ecs_iter_next_action_t next_action_() const { + return derived_().next_action(); + } }; /** Iterable adapter for iterating with iter/each/run. */ template -struct iter_iterable final : iterable { +struct iter_iterable final : iterable, Components...> { /** Construct iter_iterable from an iterable and a world. */ template iter_iterable(Iterable *it, flecs::world_t *world) @@ -270,8 +278,7 @@ struct iter_iterable final : iterable { return *this; } -protected: - ecs_iter_t get_iter(flecs::world_t *world) const override { + ecs_iter_t get_iter(flecs::world_t *world) const { if (world) { ecs_iter_t result = it_; result.world = world; @@ -280,7 +287,7 @@ struct iter_iterable final : iterable { return it_; } - ecs_iter_next_action_t next_action() const override { + ecs_iter_next_action_t next_action() const { return next_; } @@ -290,27 +297,27 @@ struct iter_iterable final : iterable { ecs_iter_next_action_t next_each_; }; -template -iter_iterable iterable::iter(flecs::world_t *world) const +template +iter_iterable iterable::iter(flecs::world_t *world) const { - return iter_iterable(this, world); + return iter_iterable(&derived_(), world); } -template -iter_iterable iterable::iter(flecs::iter& it) const +template +iter_iterable iterable::iter(flecs::iter& it) const { - return iter_iterable(this, it.world()); + return iter_iterable(&derived_(), it.world()); } -template -iter_iterable iterable::iter(flecs::entity e) const +template +iter_iterable iterable::iter(flecs::entity e) const { - return iter_iterable(this, e.world()); + return iter_iterable(&derived_(), e.world()); } /** Paged iterable adapter. Limits iteration to a range of entities. */ template -struct page_iterable final : iterable { +struct page_iterable final : iterable, Components...> { /** Construct a page_iterable from an offset, limit, and source iterable. */ template page_iterable(int32_t offset, int32_t limit, Iterable *it) @@ -320,7 +327,6 @@ struct page_iterable final : iterable { chain_it_ = it->get_iter(nullptr); } -protected: ecs_iter_t get_iter(flecs::world_t*) const { return ecs_page_iter(&chain_it_, offset_, limit_); } @@ -335,26 +341,26 @@ struct page_iterable final : iterable { int32_t limit_; }; -template -page_iterable iterable::page( +template +page_iterable iterable::page( int32_t offset, int32_t limit) { - return page_iterable(offset, limit, this); + return page_iterable(offset, limit, &derived_()); } /** Worker iterable adapter. Divides entities across workers. */ template -struct worker_iterable final : iterable { +struct worker_iterable final : iterable, Components...> { /** Construct a worker_iterable from an index, count, and source iterable. */ - worker_iterable(int32_t index, int32_t count, iterable *it) + template + worker_iterable(int32_t index, int32_t count, Iterable *it) : index_(index) , count_(count) { chain_it_ = it->get_iter(nullptr); } -protected: ecs_iter_t get_iter(flecs::world_t*) const { return ecs_worker_iter(&chain_it_, index_, count_); } @@ -369,12 +375,12 @@ struct worker_iterable final : iterable { int32_t count_; }; -template -worker_iterable iterable::worker( +template +worker_iterable iterable::worker( int32_t index, int32_t count) { - return worker_iterable(index, count, this); + return worker_iterable(index, count, &derived_()); } } diff --git a/include/flecs/addons/cpp/utils/signature.hpp b/include/flecs/addons/cpp/utils/signature.hpp index 899a162a6f..0db91be286 100644 --- a/include/flecs/addons/cpp/utils/signature.hpp +++ b/include/flecs/addons/cpp/utils/signature.hpp @@ -34,13 +34,39 @@ namespace _ { return flecs::And; } + // Type-erased term population shared by all query/system/observer builders. + // Kept out-of-line so the per-signature builder code is reduced to building + // the id/inout/oper arrays plus a single call. + FLECS_NOINLINE + inline int32_t populate_query_terms( + ecs_query_desc_t *desc, + int32_t term_index, + const flecs::id_t *ids, + const flecs::inout_kind_t *inout, + const flecs::oper_kind_t *oper, + int32_t count) + { + for (int32_t i = 0; i < count; i ++) { + ecs_term_t *term = &desc->terms[term_index + i]; + *term = ecs_term_t{}; + if (ids[i] & ECS_ID_FLAGS_MASK) { + term->id = ids[i]; + } else { + term->first.id = ids[i]; + } + term->inout = static_cast(inout[i]); + term->oper = static_cast(oper[i]); + } + return term_index + count; + } + template struct sig { - sig(flecs::world_t *world) + sig(flecs::world_t *world) : world_(world) , ids({ (_::type>::id(world))... }) , inout ({ (type_to_inout())... }) - , oper ({ (type_to_oper())... }) + , oper ({ (type_to_oper())... }) { } flecs::world_t *world_; @@ -50,11 +76,8 @@ namespace _ { template void populate(const Builder& b) { - size_t i = 0; - for (auto id : ids) { - b->with(id).inout(inout[i]).oper(oper[i]); - i ++; - } + b->populate_terms(ids.ptr(), inout.ptr(), oper.ptr(), + static_cast(sizeof...(Components))); } }; diff --git a/include/flecs/addons/flecs_c.h b/include/flecs/addons/flecs_c.h index 073423b99d..f832e7e9d6 100644 --- a/include/flecs/addons/flecs_c.h +++ b/include/flecs/addons/flecs_c.h @@ -727,6 +727,13 @@ #define ecs_field(it, T, index)\ (ECS_CAST(T*, ecs_field_w_size(it, sizeof(T), index))) +/** Get field data for a component matched through component inheritance. + * Use this instead of ecs_field() when a field is matched on a component that + * is derived from the queried (base) type. The data is iterated with the stride + * returned by ecs_field_stride(). */ +#define ecs_base_field(it, T, index)\ + (ECS_CAST(T*, ecs_base_field_w_size(it, sizeof(T), index))) + /** Get field data for a self-owned component. */ #define ecs_field_self(it, T, index)\ (ECS_CAST(T*, ecs_field_self_w_size(it, sizeof(T), index))) diff --git a/include/flecs/private/api_defines.h b/include/flecs/private/api_defines.h index 472a209b03..f230e966e7 100644 --- a/include/flecs/private/api_defines.h +++ b/include/flecs/private/api_defines.h @@ -294,6 +294,14 @@ typedef struct ecs_allocator_t ecs_allocator_t; #define FLECS_ALWAYS_INLINE #endif +#if defined(ECS_TARGET_CLANG) || defined(ECS_TARGET_GCC) + #define FLECS_NOINLINE __attribute__((noinline)) +#elif defined(ECS_TARGET_MSVC) + #define FLECS_NOINLINE __declspec(noinline) +#else + #define FLECS_NOINLINE +#endif + #ifndef FLECS_NO_DEPRECATED_WARNINGS #if defined(ECS_TARGET_GNU) #define ECS_DEPRECATED(msg) __attribute__((deprecated(msg))) diff --git a/include/flecs/private/api_flags.h b/include/flecs/private/api_flags.h index fb97477bc9..61a252ff5f 100644 --- a/include/flecs/private/api_flags.h +++ b/include/flecs/private/api_flags.h @@ -132,6 +132,7 @@ extern "C" { #define EcsIterHasCondSet (1u << 6u) /* Does the iterator have conditionally set fields. */ #define EcsIterProfile (1u << 7u) /* Profile iterator performance. */ #define EcsIterTrivialSearch (1u << 8u) /* Trivial iterator mode. */ +#define EcsIterComponentInheritance (1u << 9u) /* Query matches via component inheritance. */ #define EcsIterTrivialTest (1u << 11u) /* Trivial test mode (constrained $this). */ #define EcsIterTrivialCached (1u << 14u) /* Trivial search for cached query. */ #define EcsIterCached (1u << 15u) /* Cached query. */ @@ -180,6 +181,7 @@ extern "C" { #define EcsQueryNested (1u << 29u) /* Query created by a query (for observer, cache). */ #define EcsQueryCacheWithFilter (1u << 30u) #define EcsQueryValid (1u << 31u) +#define EcsQueryHasComponentInheritance (1u << 5u) /* Query matches via component inheritance (low free bit; 11..31 exhausted). */ //////////////////////////////////////////////////////////////////////////////// //// Term flags (used by ecs_term_t::flags_) diff --git a/src/addons/json/serialize_iter_result_query.c b/src/addons/json/serialize_iter_result_query.c index de9a3b3605..9f99247181 100644 --- a/src/addons/json/serialize_iter_result_query.c +++ b/src/addons/json/serialize_iter_result_query.c @@ -179,7 +179,7 @@ int flecs_json_serialize_iter_result_field_values( ptr = ecs_field_at_w_size(it, 0, f, i); } else { ecs_size_t size = it->sizes[f]; - ptr = ecs_field_w_size(it, flecs_itosize(size), f); + ptr = ecs_base_field_w_size(it, flecs_itosize(size), f); if (!ptr) { ecs_strbuf_list_appendlit(buf, "0"); @@ -187,7 +187,8 @@ int flecs_json_serialize_iter_result_field_values( } if (!it->sources[f]) { - ptr = ECS_ELEM(ptr, size, i); + ecs_size_t stride = flecs_uto(ecs_size_t, ecs_field_stride(it, f)); + ptr = ECS_ELEM(ptr, stride, i); } } diff --git a/src/addons/json/serialize_type_info.c b/src/addons/json/serialize_type_info.c index 596c297cde..cea6738381 100644 --- a/src/addons/json/serialize_type_info.c +++ b/src/addons/json/serialize_type_info.c @@ -215,12 +215,13 @@ int flecs_json_typeinfo_ser_type_slice( int32_t op_count, ecs_strbuf_t *str) { - const EcsStruct *st = NULL; + ecs_entity_t struct_type = 0; if (ops[0].name) { /* If a member, previous operation is PushStruct */ const ecs_meta_op_t *push = &ops[-1]; ecs_assert(push->kind == EcsOpPushStruct, ECS_INTERNAL_ERROR, NULL); - st = ecs_get(world, push->type, EcsStruct); - ecs_assert(st != NULL, ECS_INTERNAL_ERROR, NULL); + struct_type = push->type; + ecs_assert(ecs_has(world, struct_type, EcsStruct), + ECS_INTERNAL_ERROR, NULL); } for (int i = 0; i < op_count; i ++) { @@ -314,9 +315,10 @@ int flecs_json_typeinfo_ser_type_slice( } if (op->name) { - ecs_assert(st != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_member_t *m = ecs_vec_get_t( - &st->members, ecs_member_t, op->member_index); + ecs_assert(struct_type != 0, ECS_INTERNAL_ERROR, NULL); + ecs_member_t *m = ecs_struct_get_nth_member( + ECS_CONST_CAST(ecs_world_t*, world), struct_type, + op->member_index); ecs_assert(m != NULL, ECS_INTERNAL_ERROR, NULL); bool value_range = ECS_NEQ(m->range.min, m->range.max); diff --git a/src/addons/meta/cursor.c b/src/addons/meta/cursor.c index 48f4c994f2..83efc4f28c 100644 --- a/src/addons/meta/cursor.c +++ b/src/addons/meta/cursor.c @@ -798,17 +798,12 @@ ecs_entity_t ecs_meta_get_unit( const ecs_meta_cursor_t *cursor) { ecs_meta_scope_t *scope = flecs_cursor_get_scope(cursor); - ecs_entity_t type = scope->type; - const EcsStruct *st = ecs_get(cursor->world, type, EcsStruct); - if (!st) { - return 0; - } - ecs_meta_op_t *op = flecs_cursor_get_op(scope); - ecs_member_t *m = ecs_vec_get_t( - &st->members, ecs_member_t, op->member_index); + ecs_member_t *m = ecs_struct_get_nth_member( + ECS_CONST_CAST(ecs_world_t*, cursor->world), scope->type, + op->member_index); - return m->unit; + return m ? m->unit : 0; } const char* ecs_meta_get_member( @@ -823,17 +818,12 @@ ecs_entity_t ecs_meta_get_member_id( const ecs_meta_cursor_t *cursor) { ecs_meta_scope_t *scope = flecs_cursor_get_scope(cursor); - ecs_entity_t type = scope->type; - const EcsStruct *st = ecs_get(cursor->world, type, EcsStruct); - if (!st) { - return 0; - } - ecs_meta_op_t *op = flecs_cursor_get_op(scope); - ecs_member_t *m = ecs_vec_get_t( - &st->members, ecs_member_t, op->member_index); + ecs_member_t *m = ecs_struct_get_nth_member( + ECS_CONST_CAST(ecs_world_t*, cursor->world), scope->type, + op->member_index); - return m->member; + return m ? m->member : 0; } /* Utilities for type conversions and bounds checking */ diff --git a/src/addons/meta/meta.h b/src/addons/meta/meta.h index f50e17c881..ddc4b5eb4e 100644 --- a/src/addons/meta/meta.h +++ b/src/addons/meta/meta.h @@ -35,6 +35,10 @@ void flecs_rtt_init_default_hooks( const char* flecs_meta_op_kind_str( ecs_meta_op_kind_t kind); +int32_t flecs_struct_member_count( + ecs_world_t *world, + ecs_entity_t type); + #endif #endif diff --git a/src/addons/meta/rtt_lifecycle.c b/src/addons/meta/rtt_lifecycle.c index 85baf4ce1f..1391be0116 100644 --- a/src/addons/meta/rtt_lifecycle.c +++ b/src/addons/meta/rtt_lifecycle.c @@ -344,9 +344,7 @@ void flecs_rtt_init_default_hooks_struct( ecs_entity_t component, const ecs_type_info_t *ti) { - /* Obtain struct information to figure out what members it contains: */ - const EcsStruct *struct_info = ecs_get(world, component, EcsStruct); - ecs_assert(struct_info != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ecs_has(world, component, EcsStruct), ECS_INTERNAL_ERROR, NULL); /* These flags will be set to true if we determine we need to generate a * hook of a particular type: */ @@ -359,11 +357,10 @@ void flecs_rtt_init_default_hooks_struct( /* Iterate all struct members and see if any member type has hooks. If so, * the struct itself will need to have that hook: */ - int i, member_count = ecs_vec_count(&struct_info->members); - ecs_member_t *members = ecs_vec_first(&struct_info->members); + int i, member_count = flecs_struct_member_count(world, component); ecs_flags32_t flags = 0; for (i = 0; i < member_count; i++) { - ecs_member_t *m = &members[i]; + ecs_member_t *m = ecs_struct_get_nth_member(world, component, i); const ecs_type_info_t *member_ti = ecs_get_type_info(world, m->type); if (!member_ti || member_ti == ti) { continue; @@ -401,7 +398,7 @@ void flecs_rtt_init_default_hooks_struct( * build the vector of calls that will then be executed by the generic hook * handler: */ for (i = 0; i < member_count; i++) { - ecs_member_t *m = &members[i]; + ecs_member_t *m = ecs_struct_get_nth_member(world, component, i); const ecs_type_info_t *member_ti = ecs_get_type_info(world, m->type); if (!member_ti || member_ti == ti) { continue; diff --git a/src/addons/meta/serializer.c b/src/addons/meta/serializer.c index 76629615f7..4aad1ba270 100644 --- a/src/addons/meta/serializer.c +++ b/src/addons/meta/serializer.c @@ -448,8 +448,7 @@ int flecs_meta_serialize_struct( ecs_size_t offset, ecs_vec_t *ops) { - const EcsStruct *ptr = ecs_get(world, type, EcsStruct); - ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(ecs_has(world, type, EcsStruct), ECS_INTERNAL_ERROR, NULL); int32_t cur, first = ecs_vec_count(ops); ecs_meta_op_t *op = flecs_meta_ops_add(ops, EcsOpPushStruct); @@ -458,16 +457,15 @@ int flecs_meta_serialize_struct( op->type_info = ecs_get_type_info(world, type); ecs_assert(op->type_info != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_member_t *members = ecs_vec_first(&ptr->members); - int32_t i, count = ecs_vec_count(&ptr->members); + int32_t i, count = flecs_struct_member_count(world, type); ecs_hashmap_t *member_index = NULL; - if (count) { + if (count) { op->is.members = member_index = flecs_name_index_new(&world->allocator); } for (i = 0; i < count; i ++) { - ecs_member_t *member = &members[i]; + ecs_member_t *member = ecs_struct_get_nth_member(world, type, i); cur = ecs_vec_count(ops); diff --git a/src/addons/meta/type_support/struct_ts.c b/src/addons/meta/type_support/struct_ts.c index 9dd0352f6f..d1e1c1dfe0 100644 --- a/src/addons/meta/type_support/struct_ts.c +++ b/src/addons/meta/type_support/struct_ts.c @@ -59,6 +59,202 @@ void flecs_set_struct_member( member->warning_range = m->warning_range; } +static +ecs_entity_t flecs_struct_base( + ecs_world_t *world, + ecs_entity_t type, + ecs_size_t *base_size, + ecs_size_t *base_align) +{ + if (base_size) *base_size = 0; + if (base_align) *base_align = 0; + + ecs_entity_t base; + int32_t i = 0; + while ((base = ecs_get_target(world, type, EcsIsA, i ++))) { + const EcsComponent *comp = ecs_get(world, base, EcsComponent); + if (comp) { + if (base_size) *base_size = comp->size; + if (base_align) *base_align = comp->alignment; + return base; + } + } + + return 0; +} + +/* Total number of members in the struct, including inherited base members. */ +int32_t flecs_struct_member_count( + ecs_world_t *world, + ecs_entity_t type) +{ + int32_t count = 0; + ecs_entity_t base = flecs_struct_base(world, type, NULL, NULL); + if (base) { + count = flecs_struct_member_count(world, base); + } + + const EcsStruct *s = ecs_get(world, type, EcsStruct); + if (s) { + count += ecs_vec_count(&s->members); + } + + return count; +} + +static +int flecs_struct_compute_offsets( + ecs_world_t *world, + ecs_member_t *members, + int32_t from, + int32_t count, + ecs_size_t *size_inout, + ecs_size_t *align_inout) +{ + ecs_size_t size = *size_inout; + ecs_size_t alignment = *align_inout; + + int32_t i; + for (i = from; i < count; i ++) { + ecs_member_t *elem = &members[i]; + + ecs_assert(elem->name != NULL, ECS_INTERNAL_ERROR, NULL); + ecs_assert(elem->type != 0, ECS_INTERNAL_ERROR, NULL); + + const EcsComponent *mbr_comp = ecs_get(world, elem->type, EcsComponent); + if (!mbr_comp) { + char *path = ecs_get_path(world, elem->type); + ecs_err("member '%s' is not a type", path); + ecs_os_free(path); + return -1; + } + + ecs_size_t member_size = mbr_comp->size; + ecs_size_t member_alignment = mbr_comp->alignment; + + if (!member_size || !member_alignment) { + char *path = ecs_get_path(world, elem->type); + ecs_err("member '%s' has 0 size/alignment", path); + ecs_os_free(path); + return -1; + } + + member_size *= elem->count ? elem->count : 1; + size = ECS_ALIGN(size, member_alignment); + elem->size = member_size; + elem->offset = size; + + if (elem->member) { + EcsMember *member_data = ecs_ensure(world, elem->member, EcsMember); + member_data->offset = elem->offset; + } + + size += member_size; + + if (member_alignment > alignment) { + alignment = member_alignment; + } + } + + *size_inout = size; + *align_inout = alignment; + + return 0; +} + +static +void flecs_struct_propagate_to_derived( + ecs_world_t *world, + ecs_entity_t base); + +static ecs_entity_t flecs_struct_finalizing = 0; + +static +int flecs_struct_init_layout( + ecs_world_t *world, + ecs_entity_t struct_type, + ecs_size_t size, + ecs_size_t alignment) +{ + if (!size) { + ecs_err("struct '%s' has 0 size", ecs_get_name(world, struct_type)); + return -1; + } + + if (!alignment) { + ecs_err("struct '%s' has 0 alignment", ecs_get_name(world, struct_type)); + return -1; + } + + size = ECS_ALIGN(size, alignment); + + ecs_modified(world, struct_type, EcsStruct); + + /* Do this last as it triggers the update of EcsTypeSerializer */ + if (flecs_init_type(world, struct_type, EcsStructType, size, alignment)) { + return -1; + } + + /* If current struct is also a member, assign to itself */ + if (ecs_has(world, struct_type, EcsMember)) { + EcsMember *type_mbr = ecs_ensure(world, struct_type, EcsMember); + ecs_assert(type_mbr != NULL, ECS_INTERNAL_ERROR, NULL); + + type_mbr->type = struct_type; + type_mbr->count = 0; + + ecs_modified(world, struct_type, EcsMember); + } + + flecs_struct_propagate_to_derived(world, struct_type); + + return 0; +} + +static +int flecs_struct_finalize( + ecs_world_t *world, + ecs_entity_t struct_type, + EcsStruct *s) +{ + ecs_size_t size, alignment; + flecs_struct_base(world, struct_type, &size, &alignment); + + ecs_member_t *members = ecs_vec_first_t(&s->members, ecs_member_t); + int32_t count = ecs_vec_count(&s->members); + + if (flecs_struct_compute_offsets(world, members, 0, count, &size, &alignment)) { + return -1; + } + + ecs_entity_t prev = flecs_struct_finalizing; + flecs_struct_finalizing = struct_type; + int ret = flecs_struct_init_layout(world, struct_type, size, alignment); + flecs_struct_finalizing = prev; + return ret; +} + +static +void flecs_struct_propagate_to_derived( + ecs_world_t *world, + ecs_entity_t base) +{ + if (!ecs_id_in_use(world, ecs_pair(EcsIsA, base))) { + return; + } + + ecs_iter_t it = ecs_each_id(world, ecs_pair(EcsIsA, base)); + while (ecs_each_next(&it)) { + int32_t i; + for (i = 0; i < it.count; i ++) { + if (ecs_has(world, it.entities[i], EcsStruct)) { + flecs_struct_finalize(world, it.entities[i], + ecs_ensure(world, it.entities[i], EcsStruct)); + } + } + } +} + static int flecs_add_member_to_struct( ecs_world_t *world, @@ -87,18 +283,18 @@ int flecs_add_member_to_struct( return -1; } - flecs_meta_detect_cycles(world, m.type, struct_type); - if (ecs_get_typeid(world, m.type) == 0) { char *path = ecs_get_path(world, struct_type); char *ent_path = ecs_get_path(world, m.type); - ecs_err("member '%s.%s.type' is '%s' which is not a type", + ecs_err("member '%s.%s.type' is '%s' which is not a type", path, name, ent_path); ecs_os_free(path); ecs_os_free(ent_path); return -1; } + flecs_meta_detect_cycles(world, m.type, struct_type); + ecs_entity_t unit = m.unit; if (unit) { if (!ecs_has(world, unit, EcsUnit)) { @@ -133,164 +329,64 @@ int flecs_add_member_to_struct( EcsStruct *s = ecs_ensure(world, struct_type, EcsStruct); ecs_assert(s != NULL, ECS_INTERNAL_ERROR, NULL); - /* First check if member is already added to struct */ + ecs_entity_t base = flecs_struct_base(world, struct_type, NULL, NULL); + + if (base) { + if (m.offset || m.use_offset) { + ecs_err("member '%s' of struct '%s' has an explicit offset, which is " + "not supported for a struct that inherits from a base", name, + flecs_errstr(ecs_get_path(world, struct_type))); + return -1; + } + + if (ecs_struct_get_member(world, base, name)) { + ecs_err("member '%s' shadows base member of struct '%s'", name, + flecs_errstr(ecs_get_path(world, struct_type))); + return -1; + } + } + ecs_member_t *members = ecs_vec_first_t(&s->members, ecs_member_t); int32_t i, count = ecs_vec_count(&s->members); - bool has_member = false; for (i = 0; i < count; i ++) { - if (member_entity && members[i].member) { - if (members[i].member == member_entity) { - flecs_set_struct_member(&members[i], member_entity, &m, unit); - break; - } - } else { - if (!ecs_os_strcmp(name, members[i].name)) { - flecs_set_struct_member(&members[i], member_entity, &m, unit); - break; - } + bool match = (member_entity && members[i].member) + ? members[i].member == member_entity + : !ecs_os_strcmp(name, members[i].name); + if (match) { + flecs_set_struct_member(&members[i], member_entity, &m, unit); + break; } } - has_member = i != count; - - /* If member wasn't added yet, add a new element to vector */ - if (!has_member) { + if (i == count) { ecs_vec_init_if_t(&s->members, ecs_member_t); ecs_member_t *elem = ecs_vec_append_t(NULL, &s->members, ecs_member_t); elem->name = NULL; flecs_set_struct_member(elem, member_entity, &m, unit); - - /* Reobtain members array in case it was reallocated */ - members = ecs_vec_first_t(&s->members, ecs_member_t); - count ++; } - bool explicit_offset = m.offset || m.use_offset; - - /* Compute member offsets and size & alignment of struct */ - ecs_size_t size = 0; - ecs_size_t alignment = 0; - - if (!explicit_offset) { - for (i = 0; i < count; i ++) { - ecs_member_t *elem = &members[i]; - - ecs_assert(elem->name != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(elem->type != 0, ECS_INTERNAL_ERROR, NULL); - - /* Get component of member type to get its size & alignment */ - const EcsComponent *mbr_comp = ecs_get(world, elem->type, EcsComponent); - if (!mbr_comp) { - char *path = ecs_get_path(world, elem->type); - ecs_err("member '%s' is not a type", path); - ecs_os_free(path); - return -1; - } - - ecs_size_t member_size = mbr_comp->size; - ecs_size_t member_alignment = mbr_comp->alignment; - - if (!member_size || !member_alignment) { - char *path = ecs_get_path(world, elem->type); - ecs_err("member '%s' has 0 size/alignment", path); - ecs_os_free(path); - return -1; - } - - member_size *= elem->count ? elem->count : 1; - size = ECS_ALIGN(size, member_alignment); - elem->size = member_size; - elem->offset = size; - - /* Synchronize offset with Member component */ - if (elem->member) { - EcsMember *member_data = ecs_ensure( - world, elem->member, EcsMember); - member_data->offset = elem->offset; - } - - size += member_size; - - if (member_alignment > alignment) { - alignment = member_alignment; - } - } - } else { - /* If members have explicit offsets, we can't rely on computed - * size/alignment values. Calculate size as if this is the last member - * instead, since this will validate if the member fits in the struct. - * It doesn't matter if the size is smaller than the actual struct size - * because flecs_init_type function compares computed size with actual - * (component) size to determine if the type is partial. */ + if (m.offset || m.use_offset) { + members = ecs_vec_first_t(&s->members, ecs_member_t); ecs_member_t *elem = &members[i]; - ecs_assert(elem->name != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_assert(elem->type != 0, ECS_INTERNAL_ERROR, NULL); - - /* Get component of member type to get its size & alignment */ const EcsComponent *mbr_comp = ecs_get(world, elem->type, EcsComponent); - if (!mbr_comp) { + if (!mbr_comp || !mbr_comp->size || !mbr_comp->alignment) { char *path = ecs_get_path(world, elem->type); - ecs_err("member '%s' is not a type", path); + ecs_err("member '%s' is not a type or has 0 size/alignment", path); ecs_os_free(path); return -1; } - ecs_size_t member_size = mbr_comp->size; - ecs_size_t member_alignment = mbr_comp->alignment; + elem->size = mbr_comp->size * (elem->count ? elem->count : 1); - if (!member_size || !member_alignment) { - char *path = ecs_get_path(world, elem->type); - ecs_err("member '%s' has 0 size/alignment", path); - ecs_os_free(path); - return -1; - } - - member_size *= elem->count ? elem->count : 1; - elem->size = member_size; - size = elem->offset + member_size; - - const EcsComponent* comp = ecs_get(world, struct_type, EcsComponent); - if (comp) { - alignment = comp->alignment; - } else { - alignment = member_alignment; - } - } - - if (size == 0) { - ecs_err("struct '%s' has 0 size", ecs_get_name(world, struct_type)); - return -1; - } - - if (alignment == 0) { - ecs_err("struct '%s' has 0 alignment", ecs_get_name(world, struct_type)); - return -1; - } - - /* Align struct size to struct alignment */ - size = ECS_ALIGN(size, alignment); - - ecs_modified(world, struct_type, EcsStruct); - - /* Do this last as it triggers the update of EcsTypeSerializer */ - if (flecs_init_type(world, struct_type, EcsStructType, size, alignment)) { - return -1; + const EcsComponent *comp = ecs_get(world, struct_type, EcsComponent); + return flecs_struct_init_layout(world, struct_type, + elem->offset + elem->size, + comp ? comp->alignment : mbr_comp->alignment); } - /* If current struct is also a member, assign to itself */ - if (ecs_has(world, struct_type, EcsMember)) { - EcsMember *type_mbr = ecs_ensure(world, struct_type, EcsMember); - ecs_assert(type_mbr != NULL, ECS_INTERNAL_ERROR, NULL); - - type_mbr->type = struct_type; - type_mbr->count = 0; - - ecs_modified(world, struct_type, EcsMember); - } - - return 0; + return flecs_struct_finalize(world, struct_type, s); } static @@ -349,11 +445,28 @@ void flecs_set_member_ranges(ecs_iter_t *it) { int i, count = it->count; for (i = 0; i < count; i ++) { - flecs_set_member_from_component(world, it->entities[i], + flecs_set_member_from_component(world, it->entities[i], &member[i], &ranges[i]); } } +static +void flecs_struct_on_set(ecs_iter_t *it) { + ecs_world_t *world = it->world; + EcsStruct *s = ecs_field(it, EcsStruct, 0); + + int32_t i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t e = it->entities[i]; + if (e != flecs_struct_finalizing && + flecs_struct_base(world, e, NULL, NULL) && + !ecs_has(world, e, EcsType)) + { + flecs_struct_finalize(world, e, &s[i]); + } + } +} + static bool flecs_member_range_overlaps( const ecs_member_value_range_t *range, @@ -526,6 +639,16 @@ ecs_entity_t ecs_struct_init( ecs_entity_t old_scope = ecs_set_scope(world, type); + if (ecs_has(world, type, EcsStruct)) { + EcsStruct *s = ecs_ensure(world, type, EcsStruct); + ecs_member_t *members = ecs_vec_first_t(&s->members, ecs_member_t); + int32_t m, mcount = ecs_vec_count(&s->members); + for (m = 0; m < mcount; m ++) { + ecs_os_free(ECS_CONST_CAST(char*, members[m].name)); + } + ecs_vec_clear(&s->members); + } + int i; for (i = 0; i < ECS_MEMBER_DESC_CACHE_SIZE; i ++) { const ecs_member_t *m_desc = &desc->members[i]; @@ -565,7 +688,7 @@ ecs_entity_t ecs_struct_init( if (i == 0) { EcsStruct *s = ecs_ensure(world, type, EcsStruct); ecs_assert(s != NULL, ECS_INTERNAL_ERROR, NULL); - ecs_vec_init_t(NULL, &s->members, ecs_member_t, 0); + ecs_vec_init_if_t(&s->members, ecs_member_t); ecs_modified(world, type, EcsStruct); } else if (!ecs_has(world, type, EcsStruct)) { goto error; @@ -611,17 +734,19 @@ ecs_member_t* ecs_struct_get_member( const char *name) { const EcsStruct *s = ecs_get(world, type, EcsStruct); - if (!s) { - return NULL; + if (s) { + ecs_member_t *members = ecs_vec_first(&s->members); + int32_t i, count = ecs_vec_count(&s->members); + for (i = 0; i < count; i ++) { + if (!ecs_os_strcmp(members[i].name, name)) { + return &members[i]; + } + } } - ecs_member_t *members = ecs_vec_first(&s->members); - int32_t i, count = ecs_vec_count(&s->members); - - for (i = 0; i < count; i ++) { - if (!ecs_os_strcmp(members[i].name, name)) { - return &members[i]; - } + ecs_entity_t base = flecs_struct_base(world, type, NULL, NULL); + if (base) { + return ecs_struct_get_member(world, base, name); } return NULL; @@ -632,18 +757,21 @@ ecs_member_t* ecs_struct_get_nth_member( ecs_entity_t type, int32_t i) { - const EcsStruct *s = ecs_get(world, type, EcsStruct); - if (!s) { - return NULL; + ecs_entity_t base = flecs_struct_base(world, type, NULL, NULL); + if (base) { + int32_t base_count = flecs_struct_member_count(world, base); + if (i < base_count) { + return ecs_struct_get_nth_member(world, base, i); + } + i -= base_count; } - ecs_member_t *members = ecs_vec_first(&s->members); - int32_t count = ecs_vec_count(&s->members); - if (i >= count) { + const EcsStruct *s = ecs_get(world, type, EcsStruct); + if (!s || i >= ecs_vec_count(&s->members)) { return NULL; } - return &members[i]; + return ecs_vec_get_t(&s->members, ecs_member_t, i); } void flecs_meta_struct_init( @@ -676,11 +804,12 @@ void flecs_meta_struct_init( .type.alignment = ECS_ALIGNOF(EcsStruct) }); - ecs_set_hooks(world, EcsStruct, { + ecs_set_hooks(world, EcsStruct, { .ctor = flecs_default_ctor, .move = ecs_move(EcsStruct), .copy = ecs_copy(EcsStruct), - .dtor = ecs_dtor(EcsStruct) + .dtor = ecs_dtor(EcsStruct), + .on_set = flecs_struct_on_set }); ecs_set_hooks(world, EcsMember, { diff --git a/src/addons/script/expr/visit_eval.c b/src/addons/script/expr/visit_eval.c index d2331a0f85..dc2bf7cd2f 100644 --- a/src/addons/script/expr/visit_eval.c +++ b/src/addons/script/expr/visit_eval.c @@ -821,13 +821,11 @@ int flecs_expr_member_visit_eval( ecs_os_memcpy(ECS_OFFSET(out->value.ptr, i * size), ECS_OFFSET(expr->value.ptr, node->swizzle[i]), size); } - out->value.type = node->node.type; - out->owned = false; } else { out->value.ptr = ECS_OFFSET(expr->value.ptr, node->offset); - out->value.type = node->node.type; - out->owned = false; } + out->value.type = node->node.type; + out->owned = false; flecs_expr_stack_pop(ctx->stack); return 0; diff --git a/src/bootstrap.c b/src/bootstrap.c index 0577f86fd1..957f6614b1 100644 --- a/src/bootstrap.c +++ b/src/bootstrap.c @@ -242,6 +242,38 @@ void flecs_register_flag_for_trait( return; } +#ifndef FLECS_NDEBUG +static +void flecs_assert_isa_change(ecs_iter_t *it) { + ecs_world_t *world = it->real_world; + + if (world->flags & (EcsWorldInit|EcsWorldFini|EcsWorldQuit)) { + return; + } + + int i, count = it->count; + for (i = 0; i < count; i ++) { + ecs_entity_t component = it->entities[i]; + + ecs_component_record_t *cr = flecs_components_get(world, component); + if (cr && (cr->flags & EcsIdMarkedForDelete)) { + continue; + } + + if (ecs_id_in_use(world, component) || + ecs_id_in_use(world, ecs_pair(component, EcsWildcard))) + { + ecs_throw(ECS_INVALID_OPERATION, + "cannot change (IsA) trait for '%s': component is already in use", + flecs_errstr(ecs_get_path(world, component))); + } + + error: + continue; + } +} +#endif + static void flecs_register_final(ecs_iter_t *it) { ecs_world_t *world = it->world; @@ -1075,6 +1107,18 @@ void flecs_bootstrap( .global_observer = true }); +#ifndef FLECS_NDEBUG + ecs_observer(world, { + .query.terms = { + { .id = ecs_pair(EcsIsA, EcsWildcard) } + }, + .query.flags = EcsQueryMatchPrefab|EcsQueryMatchDisabled, + .events = {EcsOnAdd, EcsOnRemove}, + .callback = flecs_assert_isa_change, + .global_observer = true + }); +#endif + static ecs_on_trait_ctx_t inheritable_trait = { EcsIdInheritable, 0 }; ecs_observer(world, { .query.terms = {{ .id = EcsInheritable }}, diff --git a/src/iter.c b/src/iter.c index c256304d39..e1ee3fc937 100644 --- a/src/iter.c +++ b/src/iter.c @@ -130,8 +130,45 @@ void* ecs_field_w_size( int16_t column = it->columns[index]; if (column >= 0) { - return ECS_ELEM(it->table->data.columns[column].data, - (ecs_size_t)size, it->offset); + ecs_column_t *col = &it->table->data.columns[column]; + ecs_assert((ecs_size_t)size == col->ti->size, + ECS_INVALID_PARAMETER, NULL); + return ECS_ELEM(col->data, (ecs_size_t)size, it->offset); + } + + return flecs_field_shared(it, size, index); +error: + return NULL; +} + +void* ecs_base_field_w_size( + const ecs_iter_t *it, + size_t size, + int8_t index) +{ + ecs_check(it->flags & EcsIterIsValid, ECS_INVALID_PARAMETER, + "operation invalid before calling next()"); + ecs_check(index >= 0, ECS_INVALID_PARAMETER, + "invalid field index %d", index); + ecs_check(index < it->field_count, ECS_INVALID_PARAMETER, + "field index %d out of bounds", index); + ecs_check(size != 0, ECS_INVALID_PARAMETER, + "missing size for field %d", index); + ecs_check(ecs_field_size(it, index) == size || + !ecs_field_size(it, index), + ECS_INVALID_PARAMETER, + "mismatching size for field %d (expected '%s')", + index, + flecs_errstr(ecs_id_str(it->world, it->ids[index]))); + + if (it->ptrs) { + return it->ptrs[index]; + } + + int16_t column = it->columns[index]; + if (column >= 0) { + ecs_column_t *col = &it->table->data.columns[column]; + return ECS_ELEM(col->data, col->ti->size, it->offset); } return flecs_field_shared(it, size, index); @@ -356,11 +393,30 @@ size_t ecs_field_size( const ecs_iter_t *it, int8_t index) { - ecs_check(index >= 0, ECS_INVALID_PARAMETER, + ecs_check(index >= 0, ECS_INVALID_PARAMETER, "invalid field index %d", index); - ecs_check(index < it->field_count, ECS_INVALID_PARAMETER, + ecs_check(index < it->field_count, ECS_INVALID_PARAMETER, + "field index %d out of bounds", index); + + return (size_t)it->sizes[index]; +error: + return 0; +} + +size_t ecs_field_stride( + const ecs_iter_t *it, + int8_t index) +{ + ecs_check(index >= 0, ECS_INVALID_PARAMETER, + "invalid field index %d", index); + ecs_check(index < it->field_count, ECS_INVALID_PARAMETER, "field index %d out of bounds", index); + int16_t column = it->columns[index]; + if (column >= 0 && it->table) { + return (size_t)it->table->data.columns[column].ti->size; + } + return (size_t)it->sizes[index]; error: return 0; diff --git a/src/observer.c b/src/observer.c index b4807e4732..169a3030a6 100644 --- a/src/observer.c +++ b/src/observer.c @@ -667,7 +667,8 @@ void flecs_multi_observer_invoke( user_it.trs[pivot_field] = it->trs[0]; user_it.sources[pivot_field] = it->sources[0]; ECS_CONST_CAST(int16_t*, user_it.columns)[pivot_field] = - it->sources[0] ? -1 : it->columns[0]; + (it->sources[0] || !(user_it.set_fields & (1llu << pivot_field))) + ? -1 : it->columns[0]; user_it.term_index = pivot_term; user_it.ctx = o->ctx; diff --git a/src/query/cache/cache.c b/src/query/cache/cache.c index c2b3d86771..fdc499f701 100644 --- a/src/query/cache/cache.c +++ b/src/query/cache/cache.c @@ -625,6 +625,7 @@ ecs_query_cache_t* flecs_query_cache_init( if ((t == count) && (q->flags & EcsQueryMatchOnlySelf) && !(q->flags & EcsQueryMatchWildcards) && + !(q->flags & EcsQueryHasComponentInheritance) && !(q->flags & EcsQueryCacheWithFilter)) { if (!const_desc->order_by && !const_desc->group_by && diff --git a/src/query/compiler/compiler.c b/src/query/compiler/compiler.c index aabbeea1f0..254e615d6f 100644 --- a/src/query/compiler/compiler.c +++ b/src/query/compiler/compiler.c @@ -173,12 +173,6 @@ int flecs_query_discover_vars( ecs_var_id_t first_var_id = flecs_query_add_var_for_term_id( query, first, vars, EcsVarEntity); if (first_var_id == EcsVarNone) { - /* If first is not a variable, check if we need to insert anonymous - * variable for resolving component inheritance */ - if (term->flags_ & EcsTermIdInherited) { - anonymous_count += 2; /* table & entity variable */ - } - /* If first is a wildcard, insert anonymous variable */ if (flecs_term_ref_is_wildcard(first)) { anonymous_count ++; diff --git a/src/query/compiler/compiler_term.c b/src/query/compiler/compiler_term.c index b91fc995d3..311d46ab5c 100644 --- a/src/query/compiler/compiler_term.c +++ b/src/query/compiler/compiler_term.c @@ -438,46 +438,6 @@ void flecs_query_insert_unconstrained_transitive( flecs_query_op_insert(&and_op, ctx); } -static -void flecs_query_insert_inheritance( - ecs_query_impl_t *query, - ecs_term_t *term, - ecs_query_op_t *op, - ecs_query_compile_ctx_t *ctx, - bool cond_write) -{ - /* Anonymous variable to store the resolved component ids */ - ecs_var_id_t tvar = flecs_query_add_var(query, NULL, NULL, EcsVarTable); - ecs_var_id_t evar = flecs_query_add_var(query, NULL, NULL, EcsVarEntity); - - flecs_set_var_label(&query->vars[tvar], ecs_get_name(query->pub.world, - ECS_TERM_REF_ID(&term->first))); - flecs_set_var_label(&query->vars[evar], ecs_get_name(query->pub.world, - ECS_TERM_REF_ID(&term->first))); - - ecs_query_op_t trav_op = {0}; - trav_op.kind = EcsQueryTrav; - trav_op.field_index = -1; - trav_op.first.entity = EcsIsA; - trav_op.second.entity = ECS_TERM_REF_ID(&term->first); - trav_op.src.var = tvar; - trav_op.flags = EcsQueryIsSelf; - trav_op.flags |= (EcsQueryIsEntity << EcsQueryFirst); - trav_op.flags |= (EcsQueryIsEntity << EcsQuerySecond); - trav_op.flags |= (EcsQueryIsVar << EcsQuerySrc); - trav_op.written |= (1ull << tvar); - if (term->first.id & EcsSelf) { - trav_op.match_flags |= EcsTermReflexive; - } - flecs_query_op_insert(&trav_op, ctx); - flecs_query_insert_each(tvar, evar, ctx, cond_write); - - ecs_query_ref_t r = { .var = evar }; - op->first = r; - op->flags &= (ecs_flags8_t)~(EcsQueryIsEntity << EcsQueryFirst); - op->flags |= (EcsQueryIsVar << EcsQueryFirst); -} - void flecs_query_compile_term_ref( ecs_world_t *world, ecs_query_impl_t *query, @@ -1424,12 +1384,6 @@ int flecs_query_compile_term( flecs_query_begin_block_or(&op, term, ctx); } - /* If term has component inheritance enabled, insert instruction to walk - * down the relationship tree of the id. */ - if (term->flags_ & EcsTermIdInherited) { - flecs_query_insert_inheritance(query, term, &op, ctx, cond_write); - } - op.match_flags = term->flags_; ecs_write_flags_t write_state = ctx->written; diff --git a/src/query/engine/engine.h b/src/query/engine/engine.h index 9e61465201..34aec9b095 100644 --- a/src/query/engine/engine.h +++ b/src/query/engine/engine.h @@ -110,6 +110,7 @@ ecs_id_t flecs_query_op_get_id( const ecs_query_run_ctx_t *ctx); int16_t flecs_query_next_column( + const ecs_world_t *world, ecs_table_t *table, ecs_id_t id, int32_t column); diff --git a/src/query/engine/eval.c b/src/query/engine/eval.c index 5b19cb2330..41524cbb7e 100644 --- a/src/query/engine/eval.c +++ b/src/query/engine/eval.c @@ -63,7 +63,8 @@ bool flecs_query_select_w_id( tr = (const ecs_table_record_t*)op_ctx->it.cur; ecs_assert(tr != NULL, ECS_INTERNAL_ERROR, NULL); table = tr->hdr.table; - op_ctx->column = flecs_query_next_column(table, cr->id, op_ctx->column); + op_ctx->column = flecs_query_next_column( + ctx->world, table, cr->id, op_ctx->column); op_ctx->remaining --; } @@ -120,14 +121,15 @@ bool flecs_query_with( op_ctx->remaining = flecs_ito(int16_t, tr->count); op_ctx->it.cur = &tr->hdr; } else { - ecs_assert((op_ctx->remaining + op_ctx->column - 1) < table->type.count, + ecs_assert((op_ctx->remaining + op_ctx->column - 1) < table->type.count, ECS_INTERNAL_ERROR, NULL); ecs_assert(op_ctx->remaining >= 0, ECS_INTERNAL_ERROR, NULL); if (--op_ctx->remaining <= 0) { return false; } - op_ctx->column = flecs_query_next_column(table, cr->id, op_ctx->column); + op_ctx->column = flecs_query_next_column( + ctx->world, table, cr->id, op_ctx->column); ecs_assert(op_ctx->column != -1, ECS_INTERNAL_ERROR, NULL); } diff --git a/src/query/engine/eval_utils.c b/src/query/engine/eval_utils.c index d0b9662b2e..303838a613 100644 --- a/src/query/engine/eval_utils.c +++ b/src/query/engine/eval_utils.c @@ -330,17 +330,14 @@ ecs_id_t flecs_query_op_get_id( } int16_t flecs_query_next_column( + const ecs_world_t *world, ecs_table_t *table, ecs_id_t id, int32_t column) { - if (!ECS_IS_PAIR(id) || (ECS_PAIR_FIRST(id) != EcsWildcard)) { - column = column + 1; - } else { - ecs_assert(column >= 0, ECS_INTERNAL_ERROR, NULL); - column = ecs_search_offset(NULL, table, column + 1, id, NULL); - ecs_assert(column != -1, ECS_INTERNAL_ERROR, NULL); - } + ecs_assert(column >= 0, ECS_INTERNAL_ERROR, NULL); + column = ecs_search_offset(world, table, column + 1, id, NULL); + ecs_assert(column != -1, ECS_INTERNAL_ERROR, NULL); return flecs_ito(int16_t, column); } diff --git a/src/query/util.c b/src/query/util.c index a3b549008e..0bb70bbc62 100644 --- a/src/query/util.c +++ b/src/query/util.c @@ -725,9 +725,11 @@ void flecs_query_apply_iter_flags( ecs_iter_t *it, const ecs_query_t *query) { - ECS_BIT_COND(it->flags, EcsIterHasCondSet, + ECS_BIT_COND(it->flags, EcsIterHasCondSet, ECS_BIT_IS_SET(query->flags, EcsQueryHasCondSet)); ECS_BIT_COND(it->flags, EcsIterNoData, query->data_fields == 0); + ECS_BIT_COND(it->flags, EcsIterComponentInheritance, + ECS_BIT_IS_SET(query->flags, EcsQueryHasComponentInheritance)); } void flecs_query_reclaim( diff --git a/src/query/validator.c b/src/query/validator.c index 830f7bc795..95493ead4b 100644 --- a/src/query/validator.c +++ b/src/query/validator.c @@ -659,7 +659,6 @@ int flecs_term_finalize( ecs_term_ref_t *src = &term->src; ecs_term_ref_t *first = &term->first; ecs_term_ref_t *second = &term->second; - ecs_flags64_t first_flags = ECS_TERM_REF_FLAGS(first); ecs_flags64_t second_flags = ECS_TERM_REF_FLAGS(second); if (first->name && (first->id & ~EcsTermRefFlags)) { @@ -838,10 +837,9 @@ int flecs_term_finalize( } if (first_entity && !ecs_term_match_0(term)) { - bool first_is_self = (first_flags & EcsTraverseFlags) == EcsSelf; ecs_record_t *first_record = flecs_entities_get(world, first_entity); ecs_table_t *first_table = first_record ? first_record->table : NULL; - + bool first_can_isa = false; if (first_table) { first_can_isa = (first_table->flags & EcsTableHasIsA) != 0; @@ -850,18 +848,14 @@ int flecs_term_finalize( } } - /* Only enable inheritance for ids which are inherited from at the time - * of query creation. To force component inheritance to be evaluated, - * an application can explicitly set traversal flags. */ - if (flecs_components_get(world, ecs_pair(EcsIsA, first->id)) || + if (flecs_components_get(world, ecs_pair(EcsIsA, first->id)) || (cr_flags & EcsIdInheritable) || first_can_isa) { - if (!first_is_self) { - term->flags_ |= EcsTermIdInherited; - } + term->flags_ |= EcsTermIdInherited; } else { #ifdef FLECS_DEBUG - if (!first_is_self) { + ecs_flags64_t first_flags = ECS_TERM_REF_FLAGS(first); + if ((first_flags & EcsTraverseFlags) != EcsSelf) { ecs_query_impl_t *q = flecs_query_impl(ctx->query); if (q) { ECS_TERMSET_SET(q->final_terms, 1u << ctx->term_index); @@ -984,7 +978,6 @@ int flecs_term_finalize( if (term->flags_ & EcsTermIdInherited) { trivial_term = false; - cacheable_term = false; } if (term->flags_ & EcsTermReflexive) { @@ -1158,6 +1151,10 @@ int flecs_query_finalize_terms( return -1; } + if (term->flags_ & EcsTermIdInherited) { + q->flags |= EcsQueryHasComponentInheritance; + } + if (term->flags_ & EcsTermNonFragmentingChildOf) { if (!i) { /* If the first term is a ChildOf pair, the query result should @@ -1716,6 +1713,7 @@ bool flecs_query_finalize_simple( /* Populate terms */ bool has_this = false, has_only_this = true; int8_t cacheable_count = 0, trivial_count = 0, up_count = 0; + int8_t inherited_count = 0; for (i = 0; i < term_count; i ++) { ecs_term_t *term = &q->terms[i]; ecs_id_t id = term->id; @@ -1830,7 +1828,9 @@ bool flecs_query_finalize_simple( if (flecs_components_get(world, ecs_pair(EcsIsA, first)) != NULL) { term->flags_ |= EcsTermIdInherited; - cacheable = false; trivial = false; + q->flags |= EcsQueryHasComponentInheritance; + trivial = false; + inherited_count ++; } if (cacheable) { @@ -1869,8 +1869,14 @@ bool flecs_query_finalize_simple( q->flags |= EcsQueryHasCacheable; } - if (cacheable_count == term_count && trivial_count == term_count) { - q->flags |= EcsQueryIsCacheable|EcsQueryIsTrivial; + if (cacheable_count == term_count && + (trivial_count + inherited_count) == term_count) + { + q->flags |= EcsQueryIsCacheable; + } + + if (trivial_count == term_count) { + q->flags |= EcsQueryIsTrivial; } if (!up_count) { diff --git a/src/search.c b/src/search.c index 329702d2f7..6f2927fd07 100644 --- a/src/search.c +++ b/src/search.c @@ -68,6 +68,108 @@ int32_t flecs_table_offset_search( return -1; } +static +bool flecs_component_inherits_from( + const ecs_world_t *world, + ecs_entity_t component, + ecs_entity_t base, + int32_t depth) +{ + if (depth >= FLECS_DAG_DEPTH_MAX) { + return false; + } + + ecs_record_t *r = flecs_entities_get(world, component); + if (!r) { + return false; + } + + ecs_table_t *table = r->table; + if (!table || !(table->flags & EcsTableHasIsA)) { + return false; + } + + const ecs_table_record_t *tr_isa = flecs_component_get_table( + world->cr_isa_wildcard, table); + if (!tr_isa) { + return false; + } + + ecs_id_t *ids = table->type.array; + int32_t i = tr_isa->index, end = i + tr_isa->count; + for (; i < end; i ++) { + ecs_entity_t b = ecs_pair_second(world, ids[i]); + if (b == base) { + return true; + } + if (flecs_component_inherits_from(world, b, base, depth + 1)) { + return true; + } + } + + return false; +} + +static +bool flecs_id_match_inherited( + const ecs_world_t *world, + ecs_id_t type_id, + ecs_id_t id) +{ + if (ECS_IS_PAIR(id)) { + if (!ECS_IS_PAIR(type_id)) { + return false; + } + ecs_entity_t tgt = ECS_PAIR_SECOND(id); + if ((tgt != EcsWildcard) && (tgt != EcsAny)) { + if (ECS_PAIR_SECOND(type_id) != tgt) { + return false; + } + } + return flecs_component_inherits_from(world, + ecs_pair_first(world, type_id), ecs_pair_first(world, id), 0); + } else { + if (type_id & ECS_ID_FLAGS_MASK) { + return false; + } + return flecs_component_inherits_from(world, type_id, id, 0); + } +} + +static +int32_t flecs_table_offset_search_w_inherited( + const ecs_world_t *world, + const ecs_table_t *table, + int32_t offset, + ecs_id_t id, + ecs_id_t *id_out) +{ + ecs_assert(id != 0, ECS_INVALID_PARAMETER, NULL); + + bool check_inherited = world && world->cr_isa_wildcard && + flecs_table_cache_count(&world->cr_isa_wildcard->cache); + + ecs_id_t *ids = table->type.array; + int32_t count = table->type.count; + while (offset < count) { + ecs_id_t type_id = ids[offset ++]; + if (ecs_id_match(type_id, id)) { + if (id_out) { + id_out[0] = type_id; + } + return offset - 1; + } + if (check_inherited && flecs_id_match_inherited(world, type_id, id)) { + if (id_out) { + id_out[0] = type_id; + } + return offset - 1; + } + } + + return -1; +} + bool flecs_type_can_inherit_id( const ecs_world_t *world, const ecs_table_t *table, @@ -353,7 +455,7 @@ int32_t ecs_search_offset( return ecs_search(world, table, id, id_out); } - return flecs_table_offset_search(table, offset, id, id_out); + return flecs_table_offset_search_w_inherited(world, table, offset, id, id_out); } static diff --git a/src/storage/table.c b/src/storage/table.c index 624f194a1e..c33dd45e08 100644 --- a/src/storage/table.c +++ b/src/storage/table.c @@ -530,9 +530,14 @@ void flecs_table_emit( ecs_table_t *table, ecs_entity_t event) { + ecs_type_t *ids = &table->type; + if (table->_->extended_type) { + ids = table->_->extended_type; + } + ecs_defer_begin(world); flecs_emit(world, world, &(ecs_event_desc_t) { - .ids = &table->type, + .ids = ids, .event = event, .table = table, .flags = EcsEventTableOnly, @@ -541,6 +546,225 @@ void flecs_table_emit( ecs_defer_end(world); } +static +bool flecs_table_register_inherited_for_base( + ecs_world_t *world, + ecs_table_t *table, + int32_t type_index, + int16_t column, + ecs_id_t base_id, + ecs_vec_t *inherited, + ecs_vec_t *inherited_wild) +{ + ecs_component_record_t *cr = flecs_components_ensure(world, base_id); + + ecs_table_record_t *existing = ECS_CONST_CAST(ecs_table_record_t*, + flecs_component_get_table(cr, table)); + if (existing) { + if (existing->index == type_index) { + return false; + } + if (ecs_id_match(table->type.array[existing->index], base_id)) { + return false; + } + + existing->count ++; + existing->index = flecs_ito(int16_t, type_index); + existing->column = column; + return true; + } + + ecs_table_record_t *tr = flecs_walloc_t(world, ecs_table_record_t); + tr->index = flecs_ito(int16_t, type_index); + tr->column = column; + tr->count = 1; + + ecs_table_cache_insert(&cr->cache, table, &tr->hdr); + flecs_component_claim(world, cr); + + ecs_vec_t *dst = ecs_id_is_wildcard(base_id) ? inherited_wild : inherited; + *ecs_vec_append_t(&world->allocator, dst, ecs_table_record_t*) = tr; + + if (base_id < FLECS_HI_COMPONENT_ID) { + world->non_trivial_lookup[base_id] |= EcsNonTrivialIdInherit; + } + + return true; +} + +static +void flecs_table_register_inherited_bases( + ecs_world_t *world, + ecs_table_t *table, + int32_t type_index, + int16_t column, + ecs_entity_t rel, + ecs_entity_t tgt, + ecs_vec_t *inherited, + ecs_vec_t *inherited_wild) +{ + ecs_record_t *r = flecs_entities_get(world, rel); + if (!r) { + return; + } + + ecs_table_t *src = r->table; + if (!src || !(src->flags & EcsTableHasIsA)) { + return; + } + + const ecs_table_record_t *tr_isa = flecs_component_get_table( + world->cr_isa_wildcard, src); + if (!tr_isa) { + return; + } + + ecs_id_t *ids = src->type.array; + int32_t i = tr_isa->index, end = i + tr_isa->count; + for (; i < end; i ++) { + ecs_entity_t base = ecs_pair_second(world, ids[i]); + if (!base) { + continue; + } + + ecs_id_t base_id = tgt ? ecs_pair(base, tgt) : base; + bool recurse = flecs_table_register_inherited_for_base( + world, table, type_index, column, base_id, inherited, + inherited_wild); + + if (tgt) { + if (flecs_table_register_inherited_for_base(world, table, + type_index, column, ecs_pair(base, EcsWildcard), inherited, + inherited_wild)) + { + recurse = true; + } + } + + if (recurse) { + flecs_table_register_inherited_bases(world, table, type_index, + column, base, tgt, inherited, inherited_wild); + } + } +} + +static +void flecs_table_register_inherited( + ecs_world_t *world, + ecs_table_t *table) +{ + ecs_component_record_t *cr_isa = world->cr_isa_wildcard; + if (!cr_isa || !flecs_table_cache_count(&cr_isa->cache)) { + return; + } + + ecs_vec_t inherited, inherited_wild; + ecs_vec_init_t(&world->allocator, &inherited, ecs_table_record_t*, 0); + ecs_vec_init_t(&world->allocator, &inherited_wild, ecs_table_record_t*, 0); + + ecs_type_t type = table->type; + int32_t ti, count = type.count; + for (ti = count - 1; ti >= 0; ti --) { + ecs_id_t id = type.array[ti]; + + ecs_entity_t rel, tgt; + if (id & ECS_ID_FLAGS_MASK) { + if (!ECS_IS_PAIR(id)) { + continue; + } + rel = ecs_pair_first(world, id); + tgt = ECS_PAIR_SECOND(id); + } else { + rel = id; + tgt = 0; + } + + int16_t column = table->_->records[ti].column; + flecs_table_register_inherited_bases( + world, table, ti, column, rel, tgt, &inherited, &inherited_wild); + } + + int32_t nw = ecs_vec_count(&inherited); + int32_t ww = ecs_vec_count(&inherited_wild); + int32_t n = nw + ww; + if (n) { + /* Inherited records are inserted right after the table's own id records + * so that the wildcard records remain at the end. Non-wildcard + * inherited records (the base component ids) are placed first, followed + * by the existing wildcard records and the inherited wildcard records. + * This ensures the extended_type array (component ids + base ids) + * mirrors the order of the records array and excludes wildcards. */ + int32_t i, type_count = type.count; + int32_t old = table->_->record_count; + int32_t aux = old - type_count; + ecs_table_record_t *old_records = table->_->records; + ecs_table_record_t *records = flecs_walloc_n( + world, ecs_table_record_t, old + n); + + ecs_table_record_t **nw_records = ecs_vec_first_t( + &inherited, ecs_table_record_t*); + ecs_table_record_t **ww_records = ecs_vec_first_t( + &inherited_wild, ecs_table_record_t*); + + ecs_os_memcpy_n(records, old_records, ecs_table_record_t, type_count); + + for (i = 0; i < nw; i ++) { + records[type_count + i] = *nw_records[i]; + } + + ecs_os_memcpy_n(&records[type_count + nw], &old_records[type_count], + ecs_table_record_t, aux); + + for (i = 0; i < ww; i ++) { + records[type_count + nw + aux + i] = *ww_records[i]; + } + + table->_->records = records; + table->_->record_count = flecs_ito(int16_t, old + n); + + for (i = 0; i < old + n; i ++) { + ecs_table_record_t *tr = &records[i]; + ecs_table_cache_replace(&tr->hdr.cr->cache, table, &tr->hdr); + + /* Propagate table event flags for inherited base components, so + * that OnTableCreate/OnTableDelete events are emitted for tables + * with derived components (used to notify query caches). */ + table->flags |= tr->hdr.cr->flags & + (EcsIdHasOnTableCreate | EcsIdHasOnTableDelete); + } + + /* Build extended type with component ids + non-wildcard base ids */ + if (nw) { + int32_t et_count = type_count + nw; + ecs_type_t *et = flecs_walloc_t(world, ecs_type_t); + et->count = et_count; + et->array = flecs_walloc_n(world, ecs_id_t, et_count); + for (i = 0; i < type_count; i ++) { + et->array[i] = type.array[i]; + } + for (i = 0; i < nw; i ++) { + ecs_id_t base_id = records[type_count + i].hdr.cr->id; + et->array[type_count + i] = base_id; + table->bloom_filter = flecs_table_bloom_filter_add( + table->bloom_filter, base_id); + } + table->_->extended_type = et; + } + + for (i = 0; i < nw; i ++) { + flecs_wfree_t(world, ecs_table_record_t, nw_records[i]); + } + for (i = 0; i < ww; i ++) { + flecs_wfree_t(world, ecs_table_record_t, ww_records[i]); + } + + flecs_wfree_n(world, ecs_table_record_t, old, old_records); + } + + ecs_vec_fini_t(&world->allocator, &inherited, ecs_table_record_t*); + ecs_vec_fini_t(&world->allocator, &inherited_wild, ecs_table_record_t*); +} + /* Main table initialization function */ void flecs_table_init( ecs_world_t *world, @@ -839,8 +1063,11 @@ void flecs_table_init( } } - /* If table has IsA pairs, create overrides cache */ + flecs_table_register_inherited(world, table); + if (isa_tr) { + isa_tr = ECS_CONST_CAST(ecs_table_record_t*, + flecs_component_get_table(world->cr_isa_wildcard, table)); flecs_table_init_overrides(world, table, isa_tr); } @@ -1312,6 +1539,12 @@ void flecs_table_fini( ecs_os_free(table->component_map); flecs_table_records_unregister(world, table); + if (table->_->extended_type) { + flecs_wfree_n(world, ecs_id_t, table->_->extended_type->count, + table->_->extended_type->array); + flecs_wfree_t(world, ecs_type_t, table->_->extended_type); + } + /* Update counters */ world->info.table_count --; world->info.table_delete_total ++; diff --git a/src/storage/table.h b/src/storage/table.h index 93eb76500d..d7f22fa523 100644 --- a/src/storage/table.h +++ b/src/storage/table.h @@ -109,6 +109,10 @@ typedef struct ecs_table__t { struct ecs_table_record_t *records; /* Array with table records */ + ecs_type_t *extended_type; /* Component ids + inherited base ids, used + * to emit OnTableCreate/OnTableDelete events + * for tables with derived components */ + #ifdef FLECS_DEBUG_INFO /* Fields used for debug visualization */ struct { diff --git a/test/core/project.json b/test/core/project.json index 19d0212465..73b01223be 100644 --- a/test/core/project.json +++ b/test/core/project.json @@ -326,6 +326,84 @@ "to_str_before_next", "to_str" ] + }, { + "id": "ComponentInheritance", + "testcases": [ + "has_2_lvl", + "has_3_lvl", + "has_not_derived", + "owns_2_lvl", + "owns_3_lvl", + "get_2_lvl", + "get_3_lvl", + "search_2_lvl", + "search_3_lvl", + "get_after_add_isa", + "has_after_add_isa", + "has_after_remove_isa", + "add_isa_3_lvl_after_populate", + "remove_isa_middle_3_lvl", + "add_isa_w_multiple_tables", + "base_and_derived_same_table", + "diamond_allowed", + "derived_w_multiple_isa_bases", + "delete_with_base_deletes_derived", + "delete_with_base_deletes_derived_3_lvl", + "delete_with_middle_keeps_base", + "remove_all_base_removes_derived", + "delete_with_after_remove_isa", + "get_mut_via_base", + "each_base_matches_derived", + "count_base_counts_derived", + "ensure_via_base", + "id_in_use_base", + "clone_derived", + "low_id_base", + "recycled_base", + "readd_isa_after_remove", + "change_grandparent_isa", + "reparent_isa", + "add_second_base_after_populate", + "remove_second_base_after_populate", + "add_isa_4_lvl_after_populate", + "wide_hierarchy", + "change_isa_deferred", + "cyclic_isa_not_allowed", + "has_tag_base", + "get_tag_base_asserts", + "get_tag_base_w_data_derived", + "delete_base_component_entity", + "delete_derived_component_entity", + "clear_with_derived", + "add_base_to_derived", + "multi_derived_has", + "multi_derived_search_first", + "multi_derived_count", + "multi_derived_each_once", + "multi_derived_get_returns_first", + "multi_derived_remove_one_keeps_base", + "multi_derived_remove_both_loses_base", + "multi_derived_3_lvl_mixed", + "multi_derived_add_isa_after_populate", + "multi_derived_delete_with", + "multi_derived_remove_all", + "multi_derived_clone", + "multi_derived_three_bases", + "multi_derived_component_diamond_plus_extra", + "multi_derived_id_in_use", + "multi_derived_deferred", + "pair_rel_has", + "pair_rel_3_lvl", + "pair_rel_search", + "pair_rel_unrelated", + "pair_rel_target_not_inherited", + "pair_rel_different_target", + "pair_rel_add_isa_after", + "pair_rel_remove_isa", + "pair_rel_multi", + "pair_rel_wildcard", + "pair_rel_wildcard_unrelated" + ] }, { "id": "Search", "testcases": [ @@ -2401,6 +2479,7 @@ "match_table_created_w_add_in_on_set", "set_optional", "set_optional_one_term", + "set_optional_one_term_16_bytes", "set_from_nothing", "add_0_entity_in_on_set", "on_set_prefab" diff --git a/test/core/src/ComponentInheritance.c b/test/core/src/ComponentInheritance.c new file mode 100644 index 0000000000..47558da2fc --- /dev/null +++ b/test/core/src/ComponentInheritance.c @@ -0,0 +1,1592 @@ +#include + +typedef struct { + int32_t hp; +} Unit; + +typedef struct { + int32_t hp; + int32_t dmg; +} Warrior; + +typedef struct { + int32_t hp; + int32_t armor; +} MeleeUnit; + +typedef struct { + int32_t hp; + int32_t mana; +} Mage; + +void ComponentInheritance_has_2_lvl(void) { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Unit); + ECS_TAG(world, Warrior); + + ecs_add_pair(world, Warrior, EcsIsA, Unit); + + ecs_entity_t e = ecs_new_w_id(world, Warrior); + + test_bool(ecs_has_id(world, e, Warrior), true); + test_bool(ecs_has_id(world, e, Unit), true); + + ecs_fini(world); +} + +void ComponentInheritance_has_3_lvl(void) { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Unit); + ECS_TAG(world, Melee); + ECS_TAG(world, Warrior); + + ecs_add_pair(world, Melee, EcsIsA, Unit); + ecs_add_pair(world, Warrior, EcsIsA, Melee); + + ecs_entity_t e = ecs_new_w_id(world, Warrior); + + test_bool(ecs_has_id(world, e, Warrior), true); + test_bool(ecs_has_id(world, e, Melee), true); + test_bool(ecs_has_id(world, e, Unit), true); + + ecs_fini(world); +} + +void ComponentInheritance_has_not_derived(void) { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Unit); + ECS_TAG(world, Warrior); + ECS_TAG(world, Wizard); + + ecs_add_pair(world, Warrior, EcsIsA, Unit); + ecs_add_pair(world, Wizard, EcsIsA, Unit); + + ecs_entity_t e = ecs_new_w_id(world, Warrior); + + test_bool(ecs_has_id(world, e, Unit), true); + test_bool(ecs_has_id(world, e, Warrior), true); + test_bool(ecs_has_id(world, e, Wizard), false); + + ecs_fini(world); +} + +void ComponentInheritance_owns_2_lvl(void) { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Unit); + ECS_TAG(world, Warrior); + + ecs_add_pair(world, Warrior, EcsIsA, Unit); + + ecs_entity_t e = ecs_new_w_id(world, Warrior); + + test_bool(ecs_owns_id(world, e, Warrior), true); + test_bool(ecs_owns_id(world, e, Unit), true); + + ecs_fini(world); +} + +void ComponentInheritance_owns_3_lvl(void) { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Unit); + ECS_TAG(world, Melee); + ECS_TAG(world, Warrior); + + ecs_add_pair(world, Melee, EcsIsA, Unit); + ecs_add_pair(world, Warrior, EcsIsA, Melee); + + ecs_entity_t e = ecs_new_w_id(world, Warrior); + + test_bool(ecs_owns_id(world, e, Warrior), true); + test_bool(ecs_owns_id(world, e, Melee), true); + test_bool(ecs_owns_id(world, e, Unit), true); + + ecs_fini(world); +} + +void ComponentInheritance_get_2_lvl(void) { + ecs_world_t *world = ecs_mini(); + + ECS_COMPONENT(world, Unit); + ECS_COMPONENT(world, Warrior); + + ecs_add_pair(world, ecs_id(Warrior), EcsIsA, ecs_id(Unit)); + + ecs_entity_t e = ecs_new(world); + ecs_set(world, e, Warrior, {.hp = 10, .dmg = 5}); + + const Warrior *w = ecs_get(world, e, Warrior); + const Unit *u = ecs_get(world, e, Unit); + + test_assert(w != NULL); + test_assert(u != NULL); + test_assert((const void*)w == (const void*)u); + test_int(u->hp, 10); + + ecs_fini(world); +} + +void ComponentInheritance_get_3_lvl(void) { + ecs_world_t *world = ecs_mini(); + + ECS_COMPONENT(world, Unit); + ECS_COMPONENT(world, MeleeUnit); + ECS_COMPONENT(world, Warrior); + + ecs_add_pair(world, ecs_id(MeleeUnit), EcsIsA, ecs_id(Unit)); + ecs_add_pair(world, ecs_id(Warrior), EcsIsA, ecs_id(MeleeUnit)); + + ecs_entity_t e = ecs_new(world); + ecs_set(world, e, Warrior, {.hp = 10, .dmg = 5}); + + const Warrior *w = ecs_get(world, e, Warrior); + const MeleeUnit *m = ecs_get(world, e, MeleeUnit); + const Unit *u = ecs_get(world, e, Unit); + + test_assert(w != NULL); + test_assert(m != NULL); + test_assert(u != NULL); + test_assert((const void*)w == (const void*)m); + test_assert((const void*)w == (const void*)u); + test_int(u->hp, 10); + + ecs_fini(world); +} + +void ComponentInheritance_search_2_lvl(void) { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Unit); + ECS_TAG(world, Warrior); + + ecs_add_pair(world, Warrior, EcsIsA, Unit); + + ecs_entity_t e = ecs_new_w_id(world, Warrior); + ecs_table_t *table = ecs_get_table(world, e); + test_assert(table != NULL); + + ecs_id_t id_out = 0; + int32_t col = ecs_search(world, table, Unit, &id_out); + test_assert(col != -1); + test_uint(id_out, Warrior); + + ecs_fini(world); +} + +void ComponentInheritance_search_3_lvl(void) { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Unit); + ECS_TAG(world, Melee); + ECS_TAG(world, Warrior); + + ecs_add_pair(world, Melee, EcsIsA, Unit); + ecs_add_pair(world, Warrior, EcsIsA, Melee); + + ecs_entity_t e = ecs_new_w_id(world, Warrior); + ecs_table_t *table = ecs_get_table(world, e); + test_assert(table != NULL); + + ecs_id_t id_out = 0; + test_assert(ecs_search(world, table, Unit, &id_out) != -1); + test_uint(id_out, Warrior); + + id_out = 0; + test_assert(ecs_search(world, table, Melee, &id_out) != -1); + test_uint(id_out, Warrior); + + ecs_fini(world); +} + +void ComponentInheritance_get_after_add_isa(void) { + install_test_abort(); + + ecs_world_t *world = ecs_mini(); + + ECS_COMPONENT(world, Unit); + ECS_COMPONENT(world, Warrior); + + ecs_entity_t e = ecs_new(world); + ecs_set(world, e, Warrior, {.hp = 10, .dmg = 5}); + + test_assert(ecs_get(world, e, Unit) == NULL); + test_bool(ecs_has_id(world, e, ecs_id(Unit)), false); + + test_expect_abort(); + ecs_add_pair(world, ecs_id(Warrior), EcsIsA, ecs_id(Unit)); +} + +void ComponentInheritance_has_after_add_isa(void) { + install_test_abort(); + + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Unit); + ECS_TAG(world, Warrior); + + ecs_entity_t e = ecs_new_w_id(world, Warrior); + + test_bool(ecs_has_id(world, e, Unit), false); + test_bool(ecs_owns_id(world, e, Unit), false); + + test_expect_abort(); + ecs_add_pair(world, Warrior, EcsIsA, Unit); +} + +void ComponentInheritance_has_after_remove_isa(void) { + install_test_abort(); + + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Unit); + ECS_TAG(world, Warrior); + + ecs_add_pair(world, Warrior, EcsIsA, Unit); + + ecs_entity_t e = ecs_new_w_id(world, Warrior); + test_bool(ecs_has_id(world, e, Unit), true); + + test_expect_abort(); + ecs_remove_pair(world, Warrior, EcsIsA, Unit); +} + +void ComponentInheritance_add_isa_3_lvl_after_populate(void) { + install_test_abort(); + + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Unit); + ECS_TAG(world, Melee); + ECS_TAG(world, Warrior); + + ecs_entity_t e = ecs_new_w_id(world, Warrior); + + test_bool(ecs_has_id(world, e, Melee), false); + test_bool(ecs_has_id(world, e, Unit), false); + + test_expect_abort(); + ecs_add_pair(world, Warrior, EcsIsA, Melee); +} + +void ComponentInheritance_remove_isa_middle_3_lvl(void) { + install_test_abort(); + + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Unit); + ECS_TAG(world, Melee); + ECS_TAG(world, Warrior); + + ecs_add_pair(world, Melee, EcsIsA, Unit); + ecs_add_pair(world, Warrior, EcsIsA, Melee); + + ecs_entity_t e = ecs_new_w_id(world, Warrior); + test_bool(ecs_has_id(world, e, Melee), true); + test_bool(ecs_has_id(world, e, Unit), true); + + test_expect_abort(); + ecs_remove_pair(world, Melee, EcsIsA, Unit); +} + +void ComponentInheritance_add_isa_w_multiple_tables(void) { + install_test_abort(); + + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Unit); + ECS_TAG(world, Warrior); + ECS_TAG(world, TagA); + ECS_TAG(world, TagB); + + ecs_entity_t e1 = ecs_new_w_id(world, Warrior); + ecs_add_id(world, e1, TagA); + ecs_entity_t e2 = ecs_new_w_id(world, Warrior); + ecs_add_id(world, e2, TagB); + + test_bool(ecs_has_id(world, e1, Unit), false); + test_bool(ecs_has_id(world, e2, Unit), false); + + test_expect_abort(); + ecs_add_pair(world, Warrior, EcsIsA, Unit); +} + +void ComponentInheritance_base_and_derived_same_table(void) { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Unit); + ECS_TAG(world, Warrior); + + ecs_add_pair(world, Warrior, EcsIsA, Unit); + + ecs_entity_t e = ecs_new_w_id(world, Unit); + ecs_add_id(world, e, Warrior); + + test_bool(ecs_has_id(world, e, Unit), true); + test_bool(ecs_has_id(world, e, Warrior), true); + test_bool(ecs_owns_id(world, e, Unit), true); + + ecs_fini(world); +} + +void ComponentInheritance_diamond_allowed(void) { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Unit); + ECS_TAG(world, Warrior); + ECS_TAG(world, Wizard); + + ecs_add_pair(world, Warrior, EcsIsA, Unit); + ecs_add_pair(world, Wizard, EcsIsA, Unit); + + ecs_entity_t e = ecs_new_w_id(world, Warrior); + ecs_add_id(world, e, Wizard); + + test_bool(ecs_has_id(world, e, Warrior), true); + test_bool(ecs_has_id(world, e, Wizard), true); + test_bool(ecs_has_id(world, e, Unit), true); + test_bool(ecs_owns_id(world, e, Unit), true); + + ecs_fini(world); +} + +void ComponentInheritance_derived_w_multiple_isa_bases(void) { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Unit); + ECS_TAG(world, Warrior); + + ecs_add_pair(world, Warrior, EcsIsA, Unit); + + ecs_entity_t base_a = ecs_new(world); + ecs_entity_t base_b = ecs_new(world); + + ecs_entity_t e = ecs_new(world); + ecs_add_pair(world, e, EcsIsA, base_a); + ecs_add_pair(world, e, EcsIsA, base_b); + ecs_add_id(world, e, Warrior); + + test_bool(ecs_has_id(world, e, Warrior), true); + test_bool(ecs_has_id(world, e, Unit), true); + test_bool(ecs_has_pair(world, e, EcsIsA, base_a), true); + test_bool(ecs_has_pair(world, e, EcsIsA, base_b), true); + + ecs_id_t id_out = 0; + test_assert(ecs_search(world, ecs_get_table(world, e), Unit, &id_out) != -1); + test_uint(id_out, Warrior); + + ecs_fini(world); +} + +void ComponentInheritance_delete_with_base_deletes_derived(void) { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Unit); + ECS_TAG(world, Warrior); + + ecs_add_pair(world, Warrior, EcsIsA, Unit); + + ecs_entity_t w1 = ecs_new_w_id(world, Warrior); + ecs_entity_t w2 = ecs_new_w_id(world, Warrior); + ecs_entity_t u1 = ecs_new_w_id(world, Unit); + + ecs_delete_with(world, Unit); + + test_bool(ecs_is_alive(world, w1), false); + test_bool(ecs_is_alive(world, w2), false); + test_bool(ecs_is_alive(world, u1), false); + + ecs_fini(world); +} + +void ComponentInheritance_delete_with_base_deletes_derived_3_lvl(void) { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Unit); + ECS_TAG(world, Melee); + ECS_TAG(world, Warrior); + + ecs_add_pair(world, Melee, EcsIsA, Unit); + ecs_add_pair(world, Warrior, EcsIsA, Melee); + + ecs_entity_t warrior = ecs_new_w_id(world, Warrior); + ecs_entity_t melee = ecs_new_w_id(world, Melee); + ecs_entity_t unit = ecs_new_w_id(world, Unit); + + ecs_delete_with(world, Unit); + + test_bool(ecs_is_alive(world, warrior), false); + test_bool(ecs_is_alive(world, melee), false); + test_bool(ecs_is_alive(world, unit), false); + + ecs_fini(world); +} + +void ComponentInheritance_delete_with_middle_keeps_base(void) { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Unit); + ECS_TAG(world, Melee); + ECS_TAG(world, Warrior); + + ecs_add_pair(world, Melee, EcsIsA, Unit); + ecs_add_pair(world, Warrior, EcsIsA, Melee); + + ecs_entity_t warrior = ecs_new_w_id(world, Warrior); + ecs_entity_t melee = ecs_new_w_id(world, Melee); + ecs_entity_t unit = ecs_new_w_id(world, Unit); + + ecs_delete_with(world, Melee); + + test_bool(ecs_is_alive(world, warrior), false); + test_bool(ecs_is_alive(world, melee), false); + test_bool(ecs_is_alive(world, unit), true); + + ecs_fini(world); +} + +void ComponentInheritance_remove_all_base_removes_derived(void) { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Unit); + ECS_TAG(world, Warrior); + + ecs_add_pair(world, Warrior, EcsIsA, Unit); + + ecs_entity_t w1 = ecs_new_w_id(world, Warrior); + ecs_entity_t u1 = ecs_new_w_id(world, Unit); + + ecs_remove_all(world, Unit); + + test_bool(ecs_is_alive(world, w1), true); + test_bool(ecs_is_alive(world, u1), true); + test_bool(ecs_has_id(world, w1, Warrior), false); + test_bool(ecs_has_id(world, u1, Unit), false); + test_bool(ecs_has_id(world, w1, Unit), false); + + ecs_fini(world); +} + +void ComponentInheritance_delete_with_after_remove_isa(void) { + install_test_abort(); + + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Unit); + ECS_TAG(world, Warrior); + + ecs_add_pair(world, Warrior, EcsIsA, Unit); + + ecs_new_w_id(world, Warrior); + ecs_new_w_id(world, Unit); + + test_expect_abort(); + ecs_remove_pair(world, Warrior, EcsIsA, Unit); +} + +void ComponentInheritance_get_mut_via_base(void) { + ecs_world_t *world = ecs_mini(); + + ECS_COMPONENT(world, Unit); + ECS_COMPONENT(world, Warrior); + + ecs_add_pair(world, ecs_id(Warrior), EcsIsA, ecs_id(Unit)); + + ecs_entity_t e = ecs_new(world); + ecs_set(world, e, Warrior, {.hp = 10, .dmg = 5}); + + Unit *u = ecs_get_mut(world, e, Unit); + test_assert(u != NULL); + u->hp = 20; + + const Warrior *w = ecs_get(world, e, Warrior); + test_assert(w != NULL); + test_int(w->hp, 20); + + ecs_fini(world); +} + +void ComponentInheritance_each_base_matches_derived(void) { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Unit); + ECS_TAG(world, Warrior); + + ecs_add_pair(world, Warrior, EcsIsA, Unit); + + ecs_entity_t w1 = ecs_new_w_id(world, Warrior); + ecs_entity_t w2 = ecs_new_w_id(world, Warrior); + ecs_entity_t u1 = ecs_new_w_id(world, Unit); + + bool found_w1 = false, found_w2 = false, found_u1 = false; + int32_t total = 0; + + ecs_iter_t it = ecs_each_id(world, Unit); + while (ecs_each_next(&it)) { + int32_t i; + for (i = 0; i < it.count; i ++) { + ecs_entity_t e = it.entities[i]; + total ++; + if (e == w1) found_w1 = true; + if (e == w2) found_w2 = true; + if (e == u1) found_u1 = true; + } + } + + test_int(total, 3); + test_bool(found_w1, true); + test_bool(found_w2, true); + test_bool(found_u1, true); + + ecs_fini(world); +} + +void ComponentInheritance_count_base_counts_derived(void) { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Unit); + ECS_TAG(world, Melee); + ECS_TAG(world, Warrior); + + ecs_add_pair(world, Melee, EcsIsA, Unit); + ecs_add_pair(world, Warrior, EcsIsA, Melee); + + ecs_new_w_id(world, Warrior); + ecs_new_w_id(world, Melee); + ecs_new_w_id(world, Unit); + + test_int(ecs_count_id(world, Unit), 3); + test_int(ecs_count_id(world, Melee), 2); + test_int(ecs_count_id(world, Warrior), 1); + + ecs_fini(world); +} + +void ComponentInheritance_ensure_via_base(void) { + ecs_world_t *world = ecs_mini(); + + ECS_COMPONENT(world, Unit); + ECS_COMPONENT(world, Warrior); + + ecs_add_pair(world, ecs_id(Warrior), EcsIsA, ecs_id(Unit)); + + ecs_entity_t e = ecs_new(world); + ecs_set(world, e, Warrior, {.hp = 10, .dmg = 5}); + + Unit *u = ecs_ensure_id(world, e, ecs_id(Unit), sizeof(Unit)); + test_assert(u != NULL); + test_bool(ecs_owns_id(world, e, ecs_id(Unit)), true); + test_bool(ecs_owns_id(world, e, ecs_id(Warrior)), true); + test_assert((void*)u != (void*)ecs_get(world, e, Warrior)); + + const Warrior *war = ecs_get(world, e, Warrior); + test_assert(war != NULL); + test_int(war->hp, 10); + + ecs_fini(world); +} + +void ComponentInheritance_id_in_use_base(void) { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Unit); + ECS_TAG(world, Warrior); + + ecs_add_pair(world, Warrior, EcsIsA, Unit); + + ecs_new_w_id(world, Warrior); + + test_bool(ecs_id_in_use(world, Unit), true); + test_bool(ecs_id_in_use(world, Warrior), true); + + ecs_fini(world); +} + +void ComponentInheritance_clone_derived(void) { + ecs_world_t *world = ecs_mini(); + + ECS_COMPONENT(world, Unit); + ECS_COMPONENT(world, Warrior); + + ecs_add_pair(world, ecs_id(Warrior), EcsIsA, ecs_id(Unit)); + + ecs_entity_t e = ecs_new(world); + ecs_set(world, e, Warrior, {.hp = 10, .dmg = 5}); + + ecs_entity_t c = ecs_clone(world, 0, e, true); + + test_bool(ecs_has_id(world, c, ecs_id(Warrior)), true); + test_bool(ecs_has_id(world, c, ecs_id(Unit)), true); + + const Unit *u = ecs_get(world, c, Unit); + test_assert(u != NULL); + test_int(u->hp, 10); + + ecs_fini(world); +} + +void ComponentInheritance_low_id_base(void) { + ecs_world_t *world = ecs_mini(); + + ECS_COMPONENT(world, Unit); + ECS_COMPONENT(world, Warrior); + + test_assert(ecs_id(Unit) < FLECS_HI_COMPONENT_ID); + test_assert(ecs_id(Warrior) < FLECS_HI_COMPONENT_ID); + + ecs_add_pair(world, ecs_id(Warrior), EcsIsA, ecs_id(Unit)); + + ecs_entity_t e = ecs_new(world); + ecs_set(world, e, Warrior, {.hp = 10, .dmg = 5}); + + test_bool(ecs_has_id(world, e, ecs_id(Unit)), true); + test_bool(ecs_owns_id(world, e, ecs_id(Unit)), true); + + const Unit *u = ecs_get(world, e, Unit); + test_assert(u != NULL); + test_int(u->hp, 10); + + ecs_id_t id_out = 0; + ecs_table_t *table = ecs_get_table(world, e); + test_assert(ecs_search(world, table, ecs_id(Unit), &id_out) != -1); + test_uint(id_out, ecs_id(Warrior)); + + ecs_fini(world); +} + +void ComponentInheritance_recycled_base(void) { + ecs_world_t *world = ecs_mini(); + + ecs_entity_t u = ecs_new(world); + ecs_delete(world, u); + test_bool(ecs_is_alive(world, u), false); + + ecs_entity_t Unit = ecs_new(world); + test_assert((uint32_t)Unit == (uint32_t)u); + test_assert(Unit != u); + + ecs_entity_t Warrior = ecs_new(world); + ecs_add_pair(world, Warrior, EcsIsA, Unit); + + ecs_entity_t e = ecs_new_w_id(world, Warrior); + + test_bool(ecs_has_id(world, e, Unit), true); + test_bool(ecs_owns_id(world, e, Unit), true); + + ecs_id_t id_out = 0; + test_assert(ecs_search(world, ecs_get_table(world, e), Unit, &id_out) != -1); + test_uint(id_out, Warrior); + + ecs_fini(world); +} + +void ComponentInheritance_readd_isa_after_remove(void) { + install_test_abort(); + + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Unit); + ECS_TAG(world, Warrior); + + ecs_add_pair(world, Warrior, EcsIsA, Unit); + ecs_entity_t e = ecs_new_w_id(world, Warrior); + test_bool(ecs_has_id(world, e, Unit), true); + + test_expect_abort(); + ecs_remove_pair(world, Warrior, EcsIsA, Unit); +} + +void ComponentInheritance_change_grandparent_isa(void) { + install_test_abort(); + + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Unit); + ECS_TAG(world, Super); + ECS_TAG(world, Melee); + ECS_TAG(world, Warrior); + + ecs_add_pair(world, Melee, EcsIsA, Unit); + ecs_add_pair(world, Warrior, EcsIsA, Melee); + + ecs_entity_t e = ecs_new_w_id(world, Warrior); + test_bool(ecs_has_id(world, e, Unit), true); + test_bool(ecs_has_id(world, e, Melee), true); + + test_expect_abort(); + ecs_remove_pair(world, Melee, EcsIsA, Unit); +} + +void ComponentInheritance_reparent_isa(void) { + install_test_abort(); + + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Unit); + ECS_TAG(world, Melee); + ECS_TAG(world, Ranged); + ECS_TAG(world, Warrior); + + ecs_add_pair(world, Melee, EcsIsA, Unit); + ecs_add_pair(world, Ranged, EcsIsA, Unit); + ecs_add_pair(world, Warrior, EcsIsA, Melee); + + ecs_entity_t e = ecs_new_w_id(world, Warrior); + test_bool(ecs_has_id(world, e, Melee), true); + test_bool(ecs_has_id(world, e, Ranged), false); + test_bool(ecs_has_id(world, e, Unit), true); + + test_expect_abort(); + ecs_remove_pair(world, Warrior, EcsIsA, Melee); +} + +void ComponentInheritance_add_second_base_after_populate(void) { + install_test_abort(); + + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Unit); + ECS_TAG(world, Warrior); + ECS_TAG(world, Wizard); + ECS_TAG(world, Warlock); + + ecs_add_pair(world, Warrior, EcsIsA, Unit); + ecs_add_pair(world, Wizard, EcsIsA, Unit); + ecs_add_pair(world, Warlock, EcsIsA, Warrior); + + ecs_entity_t e = ecs_new_w_id(world, Warlock); + test_bool(ecs_has_id(world, e, Warrior), true); + test_bool(ecs_has_id(world, e, Unit), true); + test_bool(ecs_has_id(world, e, Wizard), false); + + test_expect_abort(); + ecs_add_pair(world, Warlock, EcsIsA, Wizard); +} + +void ComponentInheritance_remove_second_base_after_populate(void) { + install_test_abort(); + + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Unit); + ECS_TAG(world, Warrior); + ECS_TAG(world, Wizard); + ECS_TAG(world, Warlock); + + ecs_add_pair(world, Warrior, EcsIsA, Unit); + ecs_add_pair(world, Wizard, EcsIsA, Unit); + ecs_add_pair(world, Warlock, EcsIsA, Warrior); + ecs_add_pair(world, Warlock, EcsIsA, Wizard); + + ecs_entity_t e = ecs_new_w_id(world, Warlock); + test_bool(ecs_has_id(world, e, Unit), true); + test_bool(ecs_has_id(world, e, Warrior), true); + test_bool(ecs_has_id(world, e, Wizard), true); + + test_expect_abort(); + ecs_remove_pair(world, Warlock, EcsIsA, Warrior); +} + +void ComponentInheritance_add_isa_4_lvl_after_populate(void) { + install_test_abort(); + + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, A); + ECS_TAG(world, B); + ECS_TAG(world, C); + ECS_TAG(world, D); + + ecs_new_w_id(world, D); + + ecs_add_pair(world, B, EcsIsA, A); + ecs_add_pair(world, C, EcsIsA, B); + + test_expect_abort(); + ecs_add_pair(world, D, EcsIsA, C); +} + +void ComponentInheritance_wide_hierarchy(void) { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Unit); + ECS_TAG(world, S1); + ECS_TAG(world, S2); + ECS_TAG(world, S3); + ECS_TAG(world, S4); + + ecs_add_pair(world, S1, EcsIsA, Unit); + ecs_add_pair(world, S2, EcsIsA, Unit); + ecs_add_pair(world, S3, EcsIsA, Unit); + ecs_add_pair(world, S4, EcsIsA, Unit); + + ecs_entity_t e1 = ecs_new_w_id(world, S1); + ecs_entity_t e2 = ecs_new_w_id(world, S2); + ecs_entity_t e3 = ecs_new_w_id(world, S3); + ecs_entity_t e4 = ecs_new_w_id(world, S4); + + test_bool(ecs_has_id(world, e1, Unit), true); + test_bool(ecs_has_id(world, e2, Unit), true); + test_bool(ecs_has_id(world, e3, Unit), true); + test_bool(ecs_has_id(world, e4, Unit), true); + test_int(ecs_count_id(world, Unit), 4); + + ecs_fini(world); +} + +void ComponentInheritance_change_isa_deferred(void) { + install_test_abort(); + + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Unit); + ECS_TAG(world, Warrior); + + ecs_entity_t e = ecs_new_w_id(world, Warrior); + test_bool(ecs_has_id(world, e, Unit), false); + + ecs_defer_begin(world); + ecs_add_pair(world, Warrior, EcsIsA, Unit); + test_bool(ecs_has_id(world, e, Unit), false); + + test_expect_abort(); + ecs_defer_end(world); +} + +void ComponentInheritance_cyclic_isa_not_allowed(void) { + install_test_abort(); + + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, A); + ECS_TAG(world, B); + + ecs_add_pair(world, A, EcsIsA, B); + + test_expect_abort(); + ecs_add_pair(world, B, EcsIsA, A); +} + +void ComponentInheritance_has_tag_base(void) { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Unit); + ECS_TAG(world, Warrior); + + ecs_add_pair(world, Warrior, EcsIsA, Unit); + ecs_entity_t e = ecs_new_w_id(world, Warrior); + + test_bool(ecs_has_id(world, e, Unit), true); + test_bool(ecs_owns_id(world, e, Unit), true); + + ecs_id_t id_out = 0; + test_assert(ecs_search(world, ecs_get_table(world, e), Unit, &id_out) != -1); + test_uint(id_out, Warrior); + + ecs_fini(world); +} + +void ComponentInheritance_get_tag_base_asserts(void) { + install_test_abort(); + + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Unit); + ECS_TAG(world, Warrior); + + ecs_add_pair(world, Warrior, EcsIsA, Unit); + ecs_entity_t e = ecs_new_w_id(world, Warrior); + + test_expect_abort(); + ecs_get_id(world, e, Unit); +} + +void ComponentInheritance_get_tag_base_w_data_derived(void) { + ecs_world_t *world = ecs_mini(); + + ecs_entity_t Unit = ecs_entity(world, { .name = "Unit" }); + + ECS_COMPONENT(world, Warrior); + ecs_add_pair(world, ecs_id(Warrior), EcsIsA, Unit); + + ecs_entity_t e = ecs_new(world); + ecs_set(world, e, Warrior, {.hp = 77, .dmg = 1}); + + const Warrior *w = ecs_get_id(world, e, Unit); + test_assert(w != NULL); + test_int(w->hp, 77); + test_assert((const void*)w == ecs_get(world, e, Warrior)); + + ecs_fini(world); +} + +void ComponentInheritance_delete_base_component_entity(void) { + install_test_abort(); + + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Unit); + ECS_TAG(world, Warrior); + + ecs_add_pair(world, Warrior, EcsIsA, Unit); + ecs_new_w_id(world, Warrior); + + test_expect_abort(); + ecs_delete(world, Unit); +} + +void ComponentInheritance_delete_derived_component_entity(void) { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Unit); + ECS_TAG(world, Warrior); + + ecs_add_pair(world, Warrior, EcsIsA, Unit); + ecs_entity_t e = ecs_new_w_id(world, Warrior); + + ecs_delete(world, Warrior); + + test_bool(ecs_is_alive(world, Warrior), false); + test_bool(ecs_is_alive(world, Unit), true); + test_bool(ecs_is_alive(world, e), true); + test_bool(ecs_has_id(world, e, Unit), false); + + ecs_fini(world); +} + +void ComponentInheritance_clear_with_derived(void) { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Unit); + ECS_TAG(world, Warrior); + + ecs_add_pair(world, Warrior, EcsIsA, Unit); + ecs_entity_t e = ecs_new_w_id(world, Warrior); + test_bool(ecs_has_id(world, e, Unit), true); + + ecs_clear(world, e); + + test_bool(ecs_is_alive(world, e), true); + test_bool(ecs_has_id(world, e, Warrior), false); + test_bool(ecs_has_id(world, e, Unit), false); + + ecs_fini(world); +} + +void ComponentInheritance_add_base_to_derived(void) { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Unit); + ECS_TAG(world, Warrior); + + ecs_add_pair(world, Warrior, EcsIsA, Unit); + + ecs_entity_t e = ecs_new_w_id(world, Warrior); + ecs_add_id(world, e, Unit); + + test_bool(ecs_has_id(world, e, Unit), true); + test_bool(ecs_has_id(world, e, Warrior), true); + test_bool(ecs_owns_id(world, e, Unit), true); + test_bool(ecs_owns_id(world, e, Warrior), true); + + ecs_fini(world); +} + +void ComponentInheritance_multi_derived_has(void) { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Unit); + ECS_TAG(world, Warrior); + ECS_TAG(world, Wizard); + + ecs_add_pair(world, Warrior, EcsIsA, Unit); + ecs_add_pair(world, Wizard, EcsIsA, Unit); + + ecs_entity_t e = ecs_new_w_id(world, Warrior); + ecs_add_id(world, e, Wizard); + + test_bool(ecs_has_id(world, e, Warrior), true); + test_bool(ecs_has_id(world, e, Wizard), true); + test_bool(ecs_has_id(world, e, Unit), true); + test_bool(ecs_owns_id(world, e, Warrior), true); + test_bool(ecs_owns_id(world, e, Wizard), true); + test_bool(ecs_owns_id(world, e, Unit), true); + + ecs_fini(world); +} + +void ComponentInheritance_multi_derived_search_first(void) { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Unit); + ECS_TAG(world, Warrior); + ECS_TAG(world, Wizard); + + ecs_add_pair(world, Warrior, EcsIsA, Unit); + ecs_add_pair(world, Wizard, EcsIsA, Unit); + + ecs_entity_t e = ecs_new_w_id(world, Warrior); + ecs_add_id(world, e, Wizard); + + ecs_table_t *table = ecs_get_table(world, e); + test_assert(table != NULL); + + ecs_id_t id_out = 0; + int32_t col = ecs_search(world, table, Unit, &id_out); + test_assert(col != -1); + test_uint(id_out, Warrior); + + ecs_fini(world); +} + +void ComponentInheritance_multi_derived_count(void) { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Unit); + ECS_TAG(world, Warrior); + ECS_TAG(world, Wizard); + + ecs_add_pair(world, Warrior, EcsIsA, Unit); + ecs_add_pair(world, Wizard, EcsIsA, Unit); + + ecs_entity_t e1 = ecs_new_w_id(world, Warrior); + ecs_add_id(world, e1, Wizard); + + ecs_new_w_id(world, Warrior); + + test_int(ecs_count_id(world, Unit), 3); + test_int(ecs_count_id(world, Warrior), 2); + test_int(ecs_count_id(world, Wizard), 1); + + ecs_fini(world); +} + +void ComponentInheritance_multi_derived_each_once(void) { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Unit); + ECS_TAG(world, Warrior); + ECS_TAG(world, Wizard); + + ecs_add_pair(world, Warrior, EcsIsA, Unit); + ecs_add_pair(world, Wizard, EcsIsA, Unit); + + ecs_entity_t e1 = ecs_new_w_id(world, Warrior); + ecs_add_id(world, e1, Wizard); + ecs_entity_t e2 = ecs_new_w_id(world, Warrior); + + bool found_e1 = false, found_e2 = false; + int32_t total = 0; + + ecs_iter_t it = ecs_each_id(world, Unit); + while (ecs_each_next(&it)) { + int32_t i; + for (i = 0; i < it.count; i ++) { + ecs_entity_t e = it.entities[i]; + total ++; + if (e == e1) found_e1 = true; + if (e == e2) found_e2 = true; + } + } + + test_int(total, 2); + test_bool(found_e1, true); + test_bool(found_e2, true); + + ecs_fini(world); +} + +void ComponentInheritance_multi_derived_get_returns_first(void) { + ecs_world_t *world = ecs_mini(); + + ECS_COMPONENT(world, Unit); + ECS_COMPONENT(world, Warrior); + ECS_COMPONENT(world, Mage); + + ecs_add_pair(world, ecs_id(Warrior), EcsIsA, ecs_id(Unit)); + ecs_add_pair(world, ecs_id(Mage), EcsIsA, ecs_id(Unit)); + + ecs_entity_t e = ecs_new(world); + ecs_set(world, e, Warrior, {.hp = 10, .dmg = 5}); + ecs_set(world, e, Mage, {.hp = 20, .mana = 7}); + + const Unit *u = ecs_get(world, e, Unit); + test_assert(u != NULL); + test_int(u->hp, 10); + test_assert((const void*)u == (const void*)ecs_get(world, e, Warrior)); + + ecs_fini(world); +} + +void ComponentInheritance_multi_derived_remove_one_keeps_base(void) { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Unit); + ECS_TAG(world, Warrior); + ECS_TAG(world, Wizard); + + ecs_add_pair(world, Warrior, EcsIsA, Unit); + ecs_add_pair(world, Wizard, EcsIsA, Unit); + + ecs_entity_t e = ecs_new_w_id(world, Warrior); + ecs_add_id(world, e, Wizard); + test_bool(ecs_has_id(world, e, Unit), true); + + ecs_remove_id(world, e, Wizard); + + test_bool(ecs_has_id(world, e, Wizard), false); + test_bool(ecs_has_id(world, e, Warrior), true); + test_bool(ecs_has_id(world, e, Unit), true); + test_int(ecs_count_id(world, Unit), 1); + + ecs_fini(world); +} + +void ComponentInheritance_multi_derived_remove_both_loses_base(void) { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Unit); + ECS_TAG(world, Warrior); + ECS_TAG(world, Wizard); + + ecs_add_pair(world, Warrior, EcsIsA, Unit); + ecs_add_pair(world, Wizard, EcsIsA, Unit); + + ecs_entity_t e = ecs_new_w_id(world, Warrior); + ecs_add_id(world, e, Wizard); + test_bool(ecs_has_id(world, e, Unit), true); + + ecs_remove_id(world, e, Warrior); + test_bool(ecs_has_id(world, e, Unit), true); + + ecs_remove_id(world, e, Wizard); + test_bool(ecs_has_id(world, e, Unit), false); + test_bool(ecs_owns_id(world, e, Unit), false); + + ecs_fini(world); +} + +void ComponentInheritance_multi_derived_3_lvl_mixed(void) { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Unit); + ECS_TAG(world, Melee); + ECS_TAG(world, Warrior); + ECS_TAG(world, Wizard); + + ecs_add_pair(world, Melee, EcsIsA, Unit); + ecs_add_pair(world, Warrior, EcsIsA, Melee); + ecs_add_pair(world, Wizard, EcsIsA, Unit); + + ecs_entity_t e = ecs_new_w_id(world, Warrior); + ecs_add_id(world, e, Wizard); + + test_bool(ecs_has_id(world, e, Unit), true); + test_bool(ecs_has_id(world, e, Melee), true); + test_int(ecs_count_id(world, Unit), 2); + test_int(ecs_count_id(world, Melee), 1); + + ecs_fini(world); +} + +void ComponentInheritance_multi_derived_add_isa_after_populate(void) { + install_test_abort(); + + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Unit); + ECS_TAG(world, Warrior); + ECS_TAG(world, Wizard); + + ecs_entity_t e = ecs_new_w_id(world, Warrior); + ecs_add_id(world, e, Wizard); + + test_bool(ecs_has_id(world, e, Unit), false); + + test_expect_abort(); + ecs_add_pair(world, Warrior, EcsIsA, Unit); +} + +void ComponentInheritance_multi_derived_delete_with(void) { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Unit); + ECS_TAG(world, Warrior); + ECS_TAG(world, Wizard); + + ecs_add_pair(world, Warrior, EcsIsA, Unit); + ecs_add_pair(world, Wizard, EcsIsA, Unit); + + ecs_entity_t e1 = ecs_new_w_id(world, Warrior); + ecs_add_id(world, e1, Wizard); + ecs_entity_t e2 = ecs_new_w_id(world, Wizard); + + ecs_delete_with(world, Unit); + + test_bool(ecs_is_alive(world, e1), false); + test_bool(ecs_is_alive(world, e2), false); + + ecs_fini(world); +} + +void ComponentInheritance_multi_derived_remove_all(void) { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Unit); + ECS_TAG(world, Warrior); + ECS_TAG(world, Wizard); + + ecs_add_pair(world, Warrior, EcsIsA, Unit); + ecs_add_pair(world, Wizard, EcsIsA, Unit); + + ecs_entity_t e = ecs_new_w_id(world, Warrior); + ecs_add_id(world, e, Wizard); + + ecs_remove_all(world, Unit); + + test_bool(ecs_is_alive(world, e), true); + test_bool(ecs_has_id(world, e, Warrior), false); + test_bool(ecs_has_id(world, e, Wizard), false); + test_bool(ecs_has_id(world, e, Unit), false); + + ecs_fini(world); +} + +void ComponentInheritance_multi_derived_clone(void) { + ecs_world_t *world = ecs_mini(); + + ECS_COMPONENT(world, Unit); + ECS_COMPONENT(world, Warrior); + ECS_COMPONENT(world, Mage); + + ecs_add_pair(world, ecs_id(Warrior), EcsIsA, ecs_id(Unit)); + ecs_add_pair(world, ecs_id(Mage), EcsIsA, ecs_id(Unit)); + + ecs_entity_t e = ecs_new(world); + ecs_set(world, e, Warrior, {.hp = 10, .dmg = 5}); + ecs_set(world, e, Mage, {.hp = 20, .mana = 7}); + + ecs_entity_t c = ecs_clone(world, 0, e, true); + + test_bool(ecs_has_id(world, c, ecs_id(Warrior)), true); + test_bool(ecs_has_id(world, c, ecs_id(Mage)), true); + test_bool(ecs_has_id(world, c, ecs_id(Unit)), true); + + const Warrior *w = ecs_get(world, c, Warrior); + test_assert(w != NULL); + test_int(w->hp, 10); + test_int(w->dmg, 5); + + const Mage *m = ecs_get(world, c, Mage); + test_assert(m != NULL); + test_int(m->hp, 20); + test_int(m->mana, 7); + + ecs_fini(world); +} + +void ComponentInheritance_multi_derived_three_bases(void) { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Unit); + ECS_TAG(world, S1); + ECS_TAG(world, S2); + ECS_TAG(world, S3); + + ecs_add_pair(world, S1, EcsIsA, Unit); + ecs_add_pair(world, S2, EcsIsA, Unit); + ecs_add_pair(world, S3, EcsIsA, Unit); + + ecs_entity_t e = ecs_new_w_id(world, S1); + ecs_add_id(world, e, S2); + ecs_add_id(world, e, S3); + + test_bool(ecs_has_id(world, e, Unit), true); + test_int(ecs_count_id(world, Unit), 3); + + ecs_id_t id_out = 0; + test_assert(ecs_search(world, ecs_get_table(world, e), Unit, &id_out) != -1); + test_uint(id_out, S1); + + ecs_fini(world); +} + +void ComponentInheritance_multi_derived_component_diamond_plus_extra(void) { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Unit); + ECS_TAG(world, Warrior); + ECS_TAG(world, Wizard); + ECS_TAG(world, Warlock); + ECS_TAG(world, Mage); + + ecs_add_pair(world, Warrior, EcsIsA, Unit); + ecs_add_pair(world, Wizard, EcsIsA, Unit); + ecs_add_pair(world, Warlock, EcsIsA, Warrior); + ecs_add_pair(world, Warlock, EcsIsA, Wizard); + ecs_add_pair(world, Mage, EcsIsA, Unit); + + ecs_entity_t e = ecs_new_w_id(world, Warlock); + ecs_add_id(world, e, Mage); + + test_bool(ecs_has_id(world, e, Unit), true); + test_int(ecs_count_id(world, Unit), 2); + + ecs_fini(world); +} + +void ComponentInheritance_multi_derived_id_in_use(void) { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Unit); + ECS_TAG(world, Warrior); + ECS_TAG(world, Wizard); + + ecs_add_pair(world, Warrior, EcsIsA, Unit); + ecs_add_pair(world, Wizard, EcsIsA, Unit); + + ecs_entity_t e = ecs_new_w_id(world, Warrior); + ecs_add_id(world, e, Wizard); + + test_bool(ecs_id_in_use(world, Unit), true); + + ecs_fini(world); +} + +void ComponentInheritance_multi_derived_deferred(void) { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Unit); + ECS_TAG(world, Warrior); + ECS_TAG(world, Wizard); + + ecs_add_pair(world, Warrior, EcsIsA, Unit); + ecs_add_pair(world, Wizard, EcsIsA, Unit); + + ecs_entity_t e = ecs_new_w_id(world, Warrior); + + ecs_defer_begin(world); + ecs_add_id(world, e, Wizard); + ecs_defer_end(world); + + test_bool(ecs_has_id(world, e, Warrior), true); + test_bool(ecs_has_id(world, e, Wizard), true); + test_bool(ecs_has_id(world, e, Unit), true); + test_int(ecs_count_id(world, Unit), 2); + + ecs_fini(world); +} + +void ComponentInheritance_pair_rel_has(void) { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Likes); + ECS_TAG(world, Loves); + ECS_TAG(world, Apples); + + ecs_add_pair(world, Loves, EcsIsA, Likes); + + ecs_entity_t e = ecs_new(world); + ecs_add_pair(world, e, Loves, Apples); + + test_bool(ecs_has_pair(world, e, Loves, Apples), true); + test_bool(ecs_has_pair(world, e, Likes, Apples), true); + + ecs_fini(world); +} + +void ComponentInheritance_pair_rel_3_lvl(void) { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Likes); + ECS_TAG(world, Loves); + ECS_TAG(world, Worships); + ECS_TAG(world, Apples); + + ecs_add_pair(world, Loves, EcsIsA, Likes); + ecs_add_pair(world, Worships, EcsIsA, Loves); + + ecs_entity_t e = ecs_new(world); + ecs_add_pair(world, e, Worships, Apples); + + test_bool(ecs_has_pair(world, e, Worships, Apples), true); + test_bool(ecs_has_pair(world, e, Loves, Apples), true); + test_bool(ecs_has_pair(world, e, Likes, Apples), true); + + ecs_fini(world); +} + +void ComponentInheritance_pair_rel_search(void) { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Likes); + ECS_TAG(world, Loves); + ECS_TAG(world, Apples); + + ecs_add_pair(world, Loves, EcsIsA, Likes); + + ecs_entity_t e = ecs_new(world); + ecs_add_pair(world, e, Loves, Apples); + + ecs_id_t id_out = 0; + ecs_table_t *table = ecs_get_table(world, e); + test_assert(ecs_search(world, table, ecs_pair(Likes, Apples), &id_out) != -1); + test_uint(id_out, ecs_pair(Loves, Apples)); + + ecs_fini(world); +} + +void ComponentInheritance_pair_rel_unrelated(void) { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Likes); + ECS_TAG(world, Loves); + ECS_TAG(world, Eats); + ECS_TAG(world, Apples); + + ecs_add_pair(world, Loves, EcsIsA, Likes); + + ecs_entity_t e = ecs_new(world); + ecs_add_pair(world, e, Loves, Apples); + + test_bool(ecs_has_pair(world, e, Likes, Apples), true); + test_bool(ecs_has_pair(world, e, Eats, Apples), false); + + ecs_fini(world); +} + +void ComponentInheritance_pair_rel_target_not_inherited(void) { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Likes); + ECS_TAG(world, Fruit); + ECS_TAG(world, Apples); + + ecs_add_pair(world, Apples, EcsIsA, Fruit); + + ecs_entity_t e = ecs_new(world); + ecs_add_pair(world, e, Likes, Apples); + + test_bool(ecs_has_pair(world, e, Likes, Apples), true); + test_bool(ecs_has_pair(world, e, Likes, Fruit), false); + + ecs_fini(world); +} + +void ComponentInheritance_pair_rel_different_target(void) { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Likes); + ECS_TAG(world, Loves); + ECS_TAG(world, Apples); + ECS_TAG(world, Pears); + + ecs_add_pair(world, Loves, EcsIsA, Likes); + + ecs_entity_t e = ecs_new(world); + ecs_add_pair(world, e, Loves, Apples); + + test_bool(ecs_has_pair(world, e, Likes, Apples), true); + test_bool(ecs_has_pair(world, e, Likes, Pears), false); + + ecs_fini(world); +} + +void ComponentInheritance_pair_rel_add_isa_after(void) { + install_test_abort(); + + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Likes); + ECS_TAG(world, Loves); + ECS_TAG(world, Apples); + + ecs_entity_t e = ecs_new(world); + ecs_add_pair(world, e, Loves, Apples); + test_bool(ecs_has_pair(world, e, Likes, Apples), false); + + test_expect_abort(); + ecs_add_pair(world, Loves, EcsIsA, Likes); +} + +void ComponentInheritance_pair_rel_remove_isa(void) { + install_test_abort(); + + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Likes); + ECS_TAG(world, Loves); + ECS_TAG(world, Apples); + + ecs_add_pair(world, Loves, EcsIsA, Likes); + + ecs_entity_t e = ecs_new(world); + ecs_add_pair(world, e, Loves, Apples); + test_bool(ecs_has_pair(world, e, Likes, Apples), true); + + test_expect_abort(); + ecs_remove_pair(world, Loves, EcsIsA, Likes); +} + +void ComponentInheritance_pair_rel_multi(void) { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Likes); + ECS_TAG(world, Loves); + ECS_TAG(world, Adores); + ECS_TAG(world, Apples); + + ecs_add_pair(world, Loves, EcsIsA, Likes); + ecs_add_pair(world, Adores, EcsIsA, Likes); + + ecs_entity_t e = ecs_new(world); + ecs_add_pair(world, e, Loves, Apples); + ecs_add_pair(world, e, Adores, Apples); + + test_bool(ecs_has_pair(world, e, Likes, Apples), true); + test_int(ecs_count_id(world, ecs_pair(Likes, Apples)), 2); + + ecs_fini(world); +} + +void ComponentInheritance_pair_rel_wildcard(void) { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Likes); + ECS_TAG(world, Loves); + ECS_TAG(world, Apples); + ECS_TAG(world, Pears); + + ecs_add_pair(world, Loves, EcsIsA, Likes); + + ecs_entity_t e = ecs_new(world); + ecs_add_pair(world, e, Loves, Apples); + ecs_add_pair(world, e, Loves, Pears); + + test_bool(ecs_has_id(world, e, ecs_pair(Likes, EcsWildcard)), true); + test_int(ecs_count_id(world, ecs_pair(Likes, EcsWildcard)), 2); + + ecs_fini(world); +} + +void ComponentInheritance_pair_rel_wildcard_unrelated(void) { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Likes); + ECS_TAG(world, Loves); + ECS_TAG(world, Eats); + ECS_TAG(world, Apples); + + ecs_add_pair(world, Loves, EcsIsA, Likes); + + ecs_entity_t e = ecs_new(world); + ecs_add_pair(world, e, Loves, Apples); + + test_bool(ecs_has_id(world, e, ecs_pair(Likes, EcsWildcard)), true); + test_bool(ecs_has_id(world, e, ecs_pair(Eats, EcsWildcard)), false); + + ecs_fini(world); +} diff --git a/test/core/src/ObserverOnSet.c b/test/core/src/ObserverOnSet.c index 46cb734fde..ad1e211126 100644 --- a/test/core/src/ObserverOnSet.c +++ b/test/core/src/ObserverOnSet.c @@ -6,6 +6,22 @@ void OnPosition(ecs_iter_t *it) { probe_iter(it); } +static +void OnColor(ecs_iter_t *it) { + if (ecs_field_is_set(it, 0)) { + test_assert(ecs_field(it, Color, 0) != NULL); + } + probe_iter(it); +} + +static +void OnPositionOptional(ecs_iter_t *it) { + if (ecs_field_is_set(it, 0)) { + test_assert(ecs_field(it, Position, 0) != NULL); + } + probe_iter(it); +} + static void Add_to_current(ecs_iter_t *it) { IterData *ctx = ecs_get_ctx(it->world); @@ -898,7 +914,7 @@ void ObserverOnSet_set_optional_one_term(void) { ecs_world_t *world = ecs_mini(); ECS_COMPONENT(world, Position); - ECS_OBSERVER(world, OnPosition, EcsOnSet, ?Position); + ECS_OBSERVER(world, OnPositionOptional, EcsOnSet, ?Position); Probe ctx = { 0 }; ecs_set_ctx(world, &ctx, NULL); @@ -909,7 +925,7 @@ void ObserverOnSet_set_optional_one_term(void) { ecs_set(world, e, Position, {10, 20}); test_int(ctx.invoked, 1); test_int(ctx.count, 1); - test_int(ctx.system, OnPosition); + test_int(ctx.system, OnPositionOptional); test_int(ctx.term_count, 1); test_null(ctx.param); test_int(ctx.e[0], e); @@ -919,6 +935,31 @@ void ObserverOnSet_set_optional_one_term(void) { ecs_fini(world); } +void ObserverOnSet_set_optional_one_term_16_bytes(void) { + ecs_world_t *world = ecs_mini(); + + ECS_COMPONENT(world, Color); + ECS_OBSERVER(world, OnColor, EcsOnSet, ?Color); + + Probe ctx = { 0 }; + ecs_set_ctx(world, &ctx, NULL); + + ecs_entity_t e = ecs_new_w(world, Color); + test_int(ctx.invoked, 0); + + ecs_set(world, e, Color, {10, 20, 30, 40}); + test_int(ctx.invoked, 1); + test_int(ctx.count, 1); + test_int(ctx.system, OnColor); + test_int(ctx.term_count, 1); + test_null(ctx.param); + test_int(ctx.e[0], e); + test_int(ctx.c[0][0], ecs_id(Color)); + test_int(ctx.s[0][0], 0); + + ecs_fini(world); +} + void ObserverOnSet_set_from_nothing(void) { ecs_world_t *world = ecs_mini(); diff --git a/test/core/src/main.c b/test/core/src/main.c index e0832a694b..aa2f4452b0 100644 --- a/test/core/src/main.c +++ b/test/core/src/main.c @@ -315,6 +315,82 @@ void Iter_rule_worker_iter_w_fini(void); void Iter_to_str_before_next(void); void Iter_to_str(void); +// Testsuite 'ComponentInheritance' +void ComponentInheritance_has_2_lvl(void); +void ComponentInheritance_has_3_lvl(void); +void ComponentInheritance_has_not_derived(void); +void ComponentInheritance_owns_2_lvl(void); +void ComponentInheritance_owns_3_lvl(void); +void ComponentInheritance_get_2_lvl(void); +void ComponentInheritance_get_3_lvl(void); +void ComponentInheritance_search_2_lvl(void); +void ComponentInheritance_search_3_lvl(void); +void ComponentInheritance_get_after_add_isa(void); +void ComponentInheritance_has_after_add_isa(void); +void ComponentInheritance_has_after_remove_isa(void); +void ComponentInheritance_add_isa_3_lvl_after_populate(void); +void ComponentInheritance_remove_isa_middle_3_lvl(void); +void ComponentInheritance_add_isa_w_multiple_tables(void); +void ComponentInheritance_base_and_derived_same_table(void); +void ComponentInheritance_diamond_allowed(void); +void ComponentInheritance_derived_w_multiple_isa_bases(void); +void ComponentInheritance_delete_with_base_deletes_derived(void); +void ComponentInheritance_delete_with_base_deletes_derived_3_lvl(void); +void ComponentInheritance_delete_with_middle_keeps_base(void); +void ComponentInheritance_remove_all_base_removes_derived(void); +void ComponentInheritance_delete_with_after_remove_isa(void); +void ComponentInheritance_get_mut_via_base(void); +void ComponentInheritance_each_base_matches_derived(void); +void ComponentInheritance_count_base_counts_derived(void); +void ComponentInheritance_ensure_via_base(void); +void ComponentInheritance_id_in_use_base(void); +void ComponentInheritance_clone_derived(void); +void ComponentInheritance_low_id_base(void); +void ComponentInheritance_recycled_base(void); +void ComponentInheritance_readd_isa_after_remove(void); +void ComponentInheritance_change_grandparent_isa(void); +void ComponentInheritance_reparent_isa(void); +void ComponentInheritance_add_second_base_after_populate(void); +void ComponentInheritance_remove_second_base_after_populate(void); +void ComponentInheritance_add_isa_4_lvl_after_populate(void); +void ComponentInheritance_wide_hierarchy(void); +void ComponentInheritance_change_isa_deferred(void); +void ComponentInheritance_cyclic_isa_not_allowed(void); +void ComponentInheritance_has_tag_base(void); +void ComponentInheritance_get_tag_base_asserts(void); +void ComponentInheritance_get_tag_base_w_data_derived(void); +void ComponentInheritance_delete_base_component_entity(void); +void ComponentInheritance_delete_derived_component_entity(void); +void ComponentInheritance_clear_with_derived(void); +void ComponentInheritance_add_base_to_derived(void); +void ComponentInheritance_multi_derived_has(void); +void ComponentInheritance_multi_derived_search_first(void); +void ComponentInheritance_multi_derived_count(void); +void ComponentInheritance_multi_derived_each_once(void); +void ComponentInheritance_multi_derived_get_returns_first(void); +void ComponentInheritance_multi_derived_remove_one_keeps_base(void); +void ComponentInheritance_multi_derived_remove_both_loses_base(void); +void ComponentInheritance_multi_derived_3_lvl_mixed(void); +void ComponentInheritance_multi_derived_add_isa_after_populate(void); +void ComponentInheritance_multi_derived_delete_with(void); +void ComponentInheritance_multi_derived_remove_all(void); +void ComponentInheritance_multi_derived_clone(void); +void ComponentInheritance_multi_derived_three_bases(void); +void ComponentInheritance_multi_derived_component_diamond_plus_extra(void); +void ComponentInheritance_multi_derived_id_in_use(void); +void ComponentInheritance_multi_derived_deferred(void); +void ComponentInheritance_pair_rel_has(void); +void ComponentInheritance_pair_rel_3_lvl(void); +void ComponentInheritance_pair_rel_search(void); +void ComponentInheritance_pair_rel_unrelated(void); +void ComponentInheritance_pair_rel_target_not_inherited(void); +void ComponentInheritance_pair_rel_different_target(void); +void ComponentInheritance_pair_rel_add_isa_after(void); +void ComponentInheritance_pair_rel_remove_isa(void); +void ComponentInheritance_pair_rel_multi(void); +void ComponentInheritance_pair_rel_wildcard(void); +void ComponentInheritance_pair_rel_wildcard_unrelated(void); + // Testsuite 'Search' void Search_search(void); void Search_search_wildcard(void); @@ -2332,6 +2408,7 @@ void ObserverOnSet_remove_set_component_in_on_set(void); void ObserverOnSet_match_table_created_w_add_in_on_set(void); void ObserverOnSet_set_optional(void); void ObserverOnSet_set_optional_one_term(void); +void ObserverOnSet_set_optional_one_term_16_bytes(void); void ObserverOnSet_set_from_nothing(void); void ObserverOnSet_add_0_entity_in_on_set(void); void ObserverOnSet_on_set_prefab(void); @@ -4528,6 +4605,305 @@ bake_test_case Iter_testcases[] = { } }; +bake_test_case ComponentInheritance_testcases[] = { + { + "has_2_lvl", + ComponentInheritance_has_2_lvl + }, + { + "has_3_lvl", + ComponentInheritance_has_3_lvl + }, + { + "has_not_derived", + ComponentInheritance_has_not_derived + }, + { + "owns_2_lvl", + ComponentInheritance_owns_2_lvl + }, + { + "owns_3_lvl", + ComponentInheritance_owns_3_lvl + }, + { + "get_2_lvl", + ComponentInheritance_get_2_lvl + }, + { + "get_3_lvl", + ComponentInheritance_get_3_lvl + }, + { + "search_2_lvl", + ComponentInheritance_search_2_lvl + }, + { + "search_3_lvl", + ComponentInheritance_search_3_lvl + }, + { + "get_after_add_isa", + ComponentInheritance_get_after_add_isa + }, + { + "has_after_add_isa", + ComponentInheritance_has_after_add_isa + }, + { + "has_after_remove_isa", + ComponentInheritance_has_after_remove_isa + }, + { + "add_isa_3_lvl_after_populate", + ComponentInheritance_add_isa_3_lvl_after_populate + }, + { + "remove_isa_middle_3_lvl", + ComponentInheritance_remove_isa_middle_3_lvl + }, + { + "add_isa_w_multiple_tables", + ComponentInheritance_add_isa_w_multiple_tables + }, + { + "base_and_derived_same_table", + ComponentInheritance_base_and_derived_same_table + }, + { + "diamond_allowed", + ComponentInheritance_diamond_allowed + }, + { + "derived_w_multiple_isa_bases", + ComponentInheritance_derived_w_multiple_isa_bases + }, + { + "delete_with_base_deletes_derived", + ComponentInheritance_delete_with_base_deletes_derived + }, + { + "delete_with_base_deletes_derived_3_lvl", + ComponentInheritance_delete_with_base_deletes_derived_3_lvl + }, + { + "delete_with_middle_keeps_base", + ComponentInheritance_delete_with_middle_keeps_base + }, + { + "remove_all_base_removes_derived", + ComponentInheritance_remove_all_base_removes_derived + }, + { + "delete_with_after_remove_isa", + ComponentInheritance_delete_with_after_remove_isa + }, + { + "get_mut_via_base", + ComponentInheritance_get_mut_via_base + }, + { + "each_base_matches_derived", + ComponentInheritance_each_base_matches_derived + }, + { + "count_base_counts_derived", + ComponentInheritance_count_base_counts_derived + }, + { + "ensure_via_base", + ComponentInheritance_ensure_via_base + }, + { + "id_in_use_base", + ComponentInheritance_id_in_use_base + }, + { + "clone_derived", + ComponentInheritance_clone_derived + }, + { + "low_id_base", + ComponentInheritance_low_id_base + }, + { + "recycled_base", + ComponentInheritance_recycled_base + }, + { + "readd_isa_after_remove", + ComponentInheritance_readd_isa_after_remove + }, + { + "change_grandparent_isa", + ComponentInheritance_change_grandparent_isa + }, + { + "reparent_isa", + ComponentInheritance_reparent_isa + }, + { + "add_second_base_after_populate", + ComponentInheritance_add_second_base_after_populate + }, + { + "remove_second_base_after_populate", + ComponentInheritance_remove_second_base_after_populate + }, + { + "add_isa_4_lvl_after_populate", + ComponentInheritance_add_isa_4_lvl_after_populate + }, + { + "wide_hierarchy", + ComponentInheritance_wide_hierarchy + }, + { + "change_isa_deferred", + ComponentInheritance_change_isa_deferred + }, + { + "cyclic_isa_not_allowed", + ComponentInheritance_cyclic_isa_not_allowed + }, + { + "has_tag_base", + ComponentInheritance_has_tag_base + }, + { + "get_tag_base_asserts", + ComponentInheritance_get_tag_base_asserts + }, + { + "get_tag_base_w_data_derived", + ComponentInheritance_get_tag_base_w_data_derived + }, + { + "delete_base_component_entity", + ComponentInheritance_delete_base_component_entity + }, + { + "delete_derived_component_entity", + ComponentInheritance_delete_derived_component_entity + }, + { + "clear_with_derived", + ComponentInheritance_clear_with_derived + }, + { + "add_base_to_derived", + ComponentInheritance_add_base_to_derived + }, + { + "multi_derived_has", + ComponentInheritance_multi_derived_has + }, + { + "multi_derived_search_first", + ComponentInheritance_multi_derived_search_first + }, + { + "multi_derived_count", + ComponentInheritance_multi_derived_count + }, + { + "multi_derived_each_once", + ComponentInheritance_multi_derived_each_once + }, + { + "multi_derived_get_returns_first", + ComponentInheritance_multi_derived_get_returns_first + }, + { + "multi_derived_remove_one_keeps_base", + ComponentInheritance_multi_derived_remove_one_keeps_base + }, + { + "multi_derived_remove_both_loses_base", + ComponentInheritance_multi_derived_remove_both_loses_base + }, + { + "multi_derived_3_lvl_mixed", + ComponentInheritance_multi_derived_3_lvl_mixed + }, + { + "multi_derived_add_isa_after_populate", + ComponentInheritance_multi_derived_add_isa_after_populate + }, + { + "multi_derived_delete_with", + ComponentInheritance_multi_derived_delete_with + }, + { + "multi_derived_remove_all", + ComponentInheritance_multi_derived_remove_all + }, + { + "multi_derived_clone", + ComponentInheritance_multi_derived_clone + }, + { + "multi_derived_three_bases", + ComponentInheritance_multi_derived_three_bases + }, + { + "multi_derived_component_diamond_plus_extra", + ComponentInheritance_multi_derived_component_diamond_plus_extra + }, + { + "multi_derived_id_in_use", + ComponentInheritance_multi_derived_id_in_use + }, + { + "multi_derived_deferred", + ComponentInheritance_multi_derived_deferred + }, + { + "pair_rel_has", + ComponentInheritance_pair_rel_has + }, + { + "pair_rel_3_lvl", + ComponentInheritance_pair_rel_3_lvl + }, + { + "pair_rel_search", + ComponentInheritance_pair_rel_search + }, + { + "pair_rel_unrelated", + ComponentInheritance_pair_rel_unrelated + }, + { + "pair_rel_target_not_inherited", + ComponentInheritance_pair_rel_target_not_inherited + }, + { + "pair_rel_different_target", + ComponentInheritance_pair_rel_different_target + }, + { + "pair_rel_add_isa_after", + ComponentInheritance_pair_rel_add_isa_after + }, + { + "pair_rel_remove_isa", + ComponentInheritance_pair_rel_remove_isa + }, + { + "pair_rel_multi", + ComponentInheritance_pair_rel_multi + }, + { + "pair_rel_wildcard", + ComponentInheritance_pair_rel_wildcard + }, + { + "pair_rel_wildcard_unrelated", + ComponentInheritance_pair_rel_wildcard_unrelated + } +}; + bake_test_case Search_testcases[] = { { "search", @@ -12431,6 +12807,10 @@ bake_test_case ObserverOnSet_testcases[] = { "set_optional_one_term", ObserverOnSet_set_optional_one_term }, + { + "set_optional_one_term_16_bytes", + ObserverOnSet_set_optional_one_term_16_bytes + }, { "set_from_nothing", ObserverOnSet_set_from_nothing @@ -16298,6 +16678,13 @@ static bake_test_suite suites[] = { 62, Iter_testcases }, + { + "ComponentInheritance", + NULL, + NULL, + 74, + ComponentInheritance_testcases + }, { "Search", NULL, @@ -16486,7 +16873,7 @@ static bake_test_suite suites[] = { "ObserverOnSet", NULL, NULL, - 27, + 28, ObserverOnSet_testcases }, { @@ -16611,5 +16998,5 @@ static bake_test_suite suites[] = { }; int main(int argc, char *argv[]) { - return bake_test_run("core", argc, argv, suites, 48); + return bake_test_run("core", argc, argv, suites, 49); } diff --git a/test/cpp/project.json b/test/cpp/project.json index 78c184a24b..59f6b4e1b3 100644 --- a/test/cpp/project.json +++ b/test/cpp/project.json @@ -870,7 +870,13 @@ "optional_module", "has_entity", "has_table", - "has_range" + "has_range", + "component_inheritance_each", + "component_inheritance_each_multiple_derived", + "component_inheritance_each_entity", + "component_inheritance_base_field", + "component_inheritance_field_asserts", + "component_inheritance_virtual" ] }, { "id": "QueryBuilder", diff --git a/test/cpp/src/Query.cpp b/test/cpp/src/Query.cpp index 399942547d..69be83b674 100644 --- a/test/cpp/src/Query.cpp +++ b/test/cpp/src/Query.cpp @@ -4,6 +4,31 @@ struct Pair { float value; }; +struct InheritUnit { + int32_t hp; +}; + +struct InheritWarrior : InheritUnit { + int32_t dmg; +}; + +struct InheritMage : InheritUnit { + int32_t mana; +}; + +struct VirtualBase { + int32_t x; + VirtualBase(int32_t x_ = 0) : x(x_) { } + virtual ~VirtualBase() { } + virtual int32_t value() const { return -1; } +}; + +struct VirtualDerived : VirtualBase { + int32_t y; + VirtualDerived(int32_t x_ = 0, int32_t y_ = 0) : VirtualBase(x_), y(y_) { } + int32_t value() const override { return x + y; } +}; + void Query_term_each_component(void) { flecs::world ecs; @@ -3971,3 +3996,173 @@ void Query_has_range(void) { test_bool(q.has(e1.range()), true); test_bool(q.has(e2.range()), false); } + +void Query_component_inheritance_each(void) { + flecs::world ecs; + + ecs.component(); + ecs.component().is_a(); + + auto w1 = ecs.entity().set({{10}, 1}); + auto w2 = ecs.entity().set({{20}, 2}); + auto w3 = ecs.entity().set({{30}, 3}); + + int32_t sum = 0, count = 0; + ecs.query().each([&](InheritUnit& u) { + sum += u.hp; + count ++; + }); + + test_int(count, 3); + test_int(sum, 60); + + ecs.query().each([](InheritUnit& u) { + u.hp *= 2; + }); + + test_int(w1.try_get()->hp, 20); + test_int(w2.try_get()->hp, 40); + test_int(w3.try_get()->hp, 60); + test_int(w1.try_get()->dmg, 1); + test_int(w2.try_get()->dmg, 2); + test_int(w3.try_get()->dmg, 3); +} + +void Query_component_inheritance_each_multiple_derived(void) { + flecs::world ecs; + + ecs.component(); + ecs.component().is_a(); + ecs.component().is_a(); + + auto w = ecs.entity().set({{10}, 5}); + auto m = ecs.entity().set({{20}, 7}); + + int32_t sum = 0, count = 0; + ecs.query().each([&](InheritUnit& u) { + sum += u.hp; + count ++; + }); + + test_int(count, 2); + test_int(sum, 30); + + test_int(w.try_get()->dmg, 5); + test_int(m.try_get()->mana, 7); +} + +void Query_component_inheritance_each_entity(void) { + flecs::world ecs; + + ecs.component(); + ecs.component().is_a(); + + auto w1 = ecs.entity().set({{10}, 1}); + auto w2 = ecs.entity().set({{20}, 2}); + + int32_t found = 0; + ecs.query().each([&](flecs::entity e, InheritUnit& u) { + if (e == w1) { test_int(u.hp, 10); found ++; } + if (e == w2) { test_int(u.hp, 20); found ++; } + }); + + test_int(found, 2); +} + +void Query_component_inheritance_base_field(void) { + flecs::world ecs; + + ecs.component(); + ecs.component().is_a(); + + auto w1 = ecs.entity().set({{10}, 1}); + auto w2 = ecs.entity().set({{20}, 2}); + auto w3 = ecs.entity().set({{30}, 3}); + + int32_t sum = 0, count = 0; + ecs.query().run([&](flecs::iter& it) { + while (it.next()) { + auto f = it.base_field(0); + for (auto i : it) { + sum += f[i].hp; + count ++; + } + } + }); + + test_int(count, 3); + test_int(sum, 60); + + ecs.query().run([](flecs::iter& it) { + while (it.next()) { + auto f = it.base_field(0); + for (auto i : it) { + f[i].hp *= 2; + } + } + }); + + test_int(w1.try_get()->hp, 20); + test_int(w2.try_get()->hp, 40); + test_int(w3.try_get()->hp, 60); + test_int(w1.try_get()->dmg, 1); +} + +void Query_component_inheritance_field_asserts(void) { + install_test_abort(); + + flecs::world ecs; + + ecs.component(); + ecs.component().is_a(); + ecs.entity().set({{10}, 1}); + + ecs.query().run([](flecs::iter& it) { + while (it.next()) { + test_expect_abort(); + it.field(0); + } + }); +} + +void Query_component_inheritance_virtual(void) { + flecs::world ecs; + + ecs.component(); + ecs.component().is_a(); + + auto e1 = ecs.entity().set(VirtualDerived(10, 1)); + auto e2 = ecs.entity().set(VirtualDerived(20, 2)); + auto e3 = ecs.entity().set(VirtualDerived(30, 3)); + + const VirtualBase *b1 = e1.try_get(); + const VirtualBase *b2 = e2.try_get(); + const VirtualBase *b3 = e3.try_get(); + test_assert(b1 != nullptr); + test_assert(b2 != nullptr); + test_assert(b3 != nullptr); + test_int(b1->value(), 11); + test_int(b2->value(), 22); + test_int(b3->value(), 33); + + int32_t sum = 0, count = 0; + ecs.query().each([&](VirtualBase& b) { + sum += b.value(); + count ++; + }); + test_int(count, 3); + test_int(sum, 66); + + sum = 0; count = 0; + ecs.query().run([&](flecs::iter& it) { + while (it.next()) { + auto f = it.base_field(0); + for (auto i : it) { + sum += f[i].value(); + count ++; + } + } + }); + test_int(count, 3); + test_int(sum, 66); +} diff --git a/test/cpp/src/main.cpp b/test/cpp/src/main.cpp index 8b70ee784a..977223881c 100644 --- a/test/cpp/src/main.cpp +++ b/test/cpp/src/main.cpp @@ -842,6 +842,12 @@ void Query_optional_module(void); void Query_has_entity(void); void Query_has_table(void); void Query_has_range(void); +void Query_component_inheritance_each(void); +void Query_component_inheritance_each_multiple_derived(void); +void Query_component_inheritance_each_entity(void); +void Query_component_inheritance_base_field(void); +void Query_component_inheritance_field_asserts(void); +void Query_component_inheritance_virtual(void); // Testsuite 'QueryBuilder' void QueryBuilder_setup(void); @@ -4930,6 +4936,30 @@ bake_test_case Query_testcases[] = { { "has_range", Query_has_range + }, + { + "component_inheritance_each", + Query_component_inheritance_each + }, + { + "component_inheritance_each_multiple_derived", + Query_component_inheritance_each_multiple_derived + }, + { + "component_inheritance_each_entity", + Query_component_inheritance_each_entity + }, + { + "component_inheritance_base_field", + Query_component_inheritance_base_field + }, + { + "component_inheritance_field_asserts", + Query_component_inheritance_field_asserts + }, + { + "component_inheritance_virtual", + Query_component_inheritance_virtual } }; @@ -8169,7 +8199,7 @@ static bake_test_suite suites[] = { "Query", NULL, NULL, - 141, + 147, Query_testcases }, { diff --git a/test/meta/project.json b/test/meta/project.json index de76de2a8a..7a08757d18 100644 --- a/test/meta/project.json +++ b/test/meta/project.json @@ -198,7 +198,23 @@ "direct_cycle", "indirect_cycle", "use_before_registering_reflection", - "use_before_registering_reflection_w_hooks" + "use_before_registering_reflection_w_hooks", + "inherit_layout", + "inherit_multi_level", + "inherit_base_change_propagates", + "inherit_base_tail_padding", + "inherit_derived_larger_align", + "inherit_shadow_member_fails", + "inherit_empty_base", + "inherit_empty_derived", + "inherit_empty_both", + "inherit_from_non_component", + "inherit_propagate_stress", + "inherit_base_member_type_change", + "inherit_explicit_offset_fails", + "inherit_base_redefined_fewer_members", + "redefine_fewer_members", + "member_w_type_not_alive" ] }, { "id": "NestedStructTypes", @@ -359,7 +375,8 @@ "ops_struct_w_bitmask", "ops_enum", "ops_struct_w_enum", - "ops_missing_metatype" + "ops_missing_metatype", + "ops_inherit" ] }, { "id": "Cursor", @@ -521,7 +538,9 @@ "set_out_of_bounds", "get_member_id", "get_array_type", - "get_vector_type" + "get_vector_type", + "struct_inherit", + "struct_inherit_by_member" ] }, { "id": "DeserializeFromJson", @@ -685,7 +704,8 @@ "ser_deser_dont_fragment_component", "ser_deser_dont_fragment_pair_multi_target", "ser_deser_dont_fragment_component_pair", - "ser_deser_dont_fragment_tag_removes_stale" + "ser_deser_dont_fragment_tag_removes_stale", + "struct_inherit" ] }, { "id": "SerializeToJson", @@ -745,7 +765,8 @@ "enum_underlying_u32", "enum_underlying_u64", "enum_underlying_uptr", - "serialize_from_stage" + "serialize_from_stage", + "struct_inherit" ] }, { "id": "SerializeEntityToJson", @@ -939,7 +960,8 @@ "serialize_children_w_tag_w_parent_component", "serialize_children_w_tag_w_parent_component_table", "serialize_childof_var_w_parent", - "serialize_childof_wildcard_w_parent" + "serialize_childof_wildcard_w_parent", + "serialize_w_inherited_component" ] }, { "id": "SerializeIterToRowJson", @@ -1048,7 +1070,9 @@ "struct_nested", "struct_nested_2_lvls", "struct_nested_2_members", - "struct_nested_3_members" + "struct_nested_3_members", + "struct_inherit", + "struct_inherit_w_range" ] }, { "id": "SerializeQueryInfoToJson", @@ -1108,7 +1132,9 @@ "enum_constant_w_type_prefix", "enum_constant_w_name_type_prefix", "struct_has_member_entities", - "fwd_decl" + "fwd_decl", + "struct_inherit", + "struct_inherit_from_expr" ] }, { "id": "OpaqueTypes", diff --git a/test/meta/src/Cursor.c b/test/meta/src/Cursor.c index 93bc900472..5735d1ef8c 100644 --- a/test/meta/src/Cursor.c +++ b/test/meta/src/Cursor.c @@ -5298,3 +5298,92 @@ void Cursor_get_vector_type(void) { ecs_fini(world); } + +void Cursor_struct_inherit(void) { + typedef struct { + ecs_i32_t x; + ecs_i32_t y; + ecs_i32_t z; + } Point3D; + + ecs_world_t *world = ecs_init(); + + ecs_entity_t base = ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_entity(world, {.name = "Point2D"}), + .members = { + {"x", ecs_id(ecs_i32_t)}, + {"y", ecs_id(ecs_i32_t)} + } + }); + + ecs_entity_t t = ecs_entity(world, {.name = "Point3D"}); + ecs_add_pair(world, t, EcsIsA, base); + ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = t, + .members = { + {"z", ecs_id(ecs_i32_t)} + } + }); + + Point3D value = {0, 0, 0}; + + ecs_meta_cursor_t cur = ecs_meta_cursor(world, t, &value); + test_ok( ecs_meta_push(&cur) ); + test_ok( ecs_meta_set_int(&cur, 10) ); + test_ok( ecs_meta_next(&cur) ); + test_ok( ecs_meta_set_int(&cur, 20) ); + test_ok( ecs_meta_next(&cur) ); + test_ok( ecs_meta_set_int(&cur, 30) ); + test_ok( ecs_meta_pop(&cur) ); + + test_int(value.x, 10); + test_int(value.y, 20); + test_int(value.z, 30); + + ecs_fini(world); +} + +void Cursor_struct_inherit_by_member(void) { + typedef struct { + ecs_i32_t x; + ecs_i32_t y; + ecs_i32_t z; + } Point3D; + + ecs_world_t *world = ecs_init(); + + ecs_entity_t base = ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_entity(world, {.name = "Point2D"}), + .members = { + {"x", ecs_id(ecs_i32_t)}, + {"y", ecs_id(ecs_i32_t)} + } + }); + + ecs_entity_t t = ecs_entity(world, {.name = "Point3D"}); + ecs_add_pair(world, t, EcsIsA, base); + ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = t, + .members = { + {"z", ecs_id(ecs_i32_t)} + } + }); + + Point3D value = {0, 0, 0}; + + ecs_meta_cursor_t cur = ecs_meta_cursor(world, t, &value); + test_ok( ecs_meta_push(&cur) ); + test_ok( ecs_meta_member(&cur, "z") ); + test_ok( ecs_meta_set_int(&cur, 30) ); + test_ok( ecs_meta_member(&cur, "x") ); + test_ok( ecs_meta_set_int(&cur, 10) ); + test_ok( ecs_meta_member(&cur, "y") ); + test_ok( ecs_meta_set_int(&cur, 20) ); + test_ok( ecs_meta_pop(&cur) ); + + test_int(value.x, 10); + test_int(value.y, 20); + test_int(value.z, 30); + + ecs_fini(world); +} diff --git a/test/meta/src/DeserializeFromJson.c b/test/meta/src/DeserializeFromJson.c index 20c565d04a..559f460a53 100644 --- a/test/meta/src/DeserializeFromJson.c +++ b/test/meta/src/DeserializeFromJson.c @@ -7242,3 +7242,42 @@ void DeserializeFromJson_ser_deser_dont_fragment_tag_removes_stale(void) { ecs_os_free(json); } + +void DeserializeFromJson_struct_inherit(void) { + typedef struct { + ecs_i32_t x; + ecs_i32_t y; + ecs_i32_t z; + } Point3D; + + ecs_world_t *world = ecs_init(); + + ecs_entity_t base = ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_entity(world, {.name = "Point2D"}), + .members = { + {"x", ecs_id(ecs_i32_t)}, + {"y", ecs_id(ecs_i32_t)} + } + }); + + ecs_entity_t t = ecs_entity(world, {.name = "Point3D"}); + ecs_add_pair(world, t, EcsIsA, base); + ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = t, + .members = { + {"z", ecs_id(ecs_i32_t)} + } + }); + + Point3D value = {0, 0, 0}; + const char *r = ecs_ptr_from_json( + world, t, &value, "{\"x\":10, \"y\":20, \"z\":30}", NULL); + test_assert(r != NULL); + test_assert(r[0] == '\0'); + + test_int(value.x, 10); + test_int(value.y, 20); + test_int(value.z, 30); + + ecs_fini(world); +} diff --git a/test/meta/src/MetaUtils.c b/test/meta/src/MetaUtils.c index d0dbd25303..7f6328ce8e 100644 --- a/test/meta/src/MetaUtils.c +++ b/test/meta/src/MetaUtils.c @@ -589,3 +589,76 @@ void MetaUtils_fwd_decl(void) { ecs_fini(world); } + +void MetaUtils_struct_inherit(void) { + typedef struct { + ecs_i32_t x; + ecs_i32_t y; + ecs_i32_t z; + } Point3D; + + ecs_world_t *world = ecs_init(); + + ecs_entity_t base = ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_entity(world, {.name = "Point2D"}), + .members = { + {"x", ecs_id(ecs_i32_t)}, + {"y", ecs_id(ecs_i32_t)} + } + }); + + ecs_entity_t t = ecs_entity(world, {.name = "Point3D"}); + ecs_add_pair(world, t, EcsIsA, base); + ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = t, + .members = { + {"z", ecs_id(ecs_i32_t)} + } + }); + + Point3D v = {10, 20, 30}; + char *expr = ecs_ptr_to_expr(world, t, &v); + test_assert(expr != NULL); + test_str(expr, "{x: 10, y: 20, z: 30}"); + ecs_os_free(expr); + + ecs_fini(world); +} + +void MetaUtils_struct_inherit_from_expr(void) { + typedef struct { + ecs_i32_t x; + ecs_i32_t y; + ecs_i32_t z; + } Point3D; + + ecs_world_t *world = ecs_init(); + + ecs_entity_t base = ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_entity(world, {.name = "Point2D"}), + .members = { + {"x", ecs_id(ecs_i32_t)}, + {"y", ecs_id(ecs_i32_t)} + } + }); + + ecs_entity_t t = ecs_entity(world, {.name = "Point3D"}); + ecs_add_pair(world, t, EcsIsA, base); + ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = t, + .members = { + {"z", ecs_id(ecs_i32_t)} + } + }); + + Point3D value = {0, 0, 0}; + ecs_value_t v = { .type = t, .ptr = &value }; + const char *r = ecs_expr_run(world, "{x: 10, y: 20, z: 30}", &v, NULL); + test_assert(r != NULL); + + test_int(value.x, 10); + test_int(value.y, 20); + test_int(value.z, 30); + + ecs_fini(world); +} diff --git a/test/meta/src/SerializeIterToJson.c b/test/meta/src/SerializeIterToJson.c index b747935a62..da606b6862 100644 --- a/test/meta/src/SerializeIterToJson.c +++ b/test/meta/src/SerializeIterToJson.c @@ -3077,3 +3077,45 @@ void SerializeIterToJson_serialize_childof_var_w_parent(void) { ecs_fini(world); } +void SerializeIterToJson_serialize_w_inherited_component(void) { + typedef struct { + ecs_i32_t x; + ecs_i32_t y; + ecs_i32_t z; + } Point3D; + + ecs_world_t *world = ecs_init(); + + ecs_entity_t base = ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_entity(world, {.name = "Point2D"}), + .members = { + {"x", ecs_id(ecs_i32_t)}, + {"y", ecs_id(ecs_i32_t)} + } + }); + + ecs_entity_t t = ecs_entity(world, {.name = "Point3D"}); + ecs_add_pair(world, t, EcsIsA, base); + ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = t, + .members = { + {"z", ecs_id(ecs_i32_t)} + } + }); + + ecs_entity_t e1 = ecs_entity(world, { .name = "e1" }); + Point3D value = {10, 20, 30}; + ecs_set_id(world, e1, t, sizeof(Point3D), &value); + + ecs_query_t *q = ecs_query(world, { .expr = "Point2D" }); + ecs_iter_t it = ecs_query_iter(world, q); + + char *json = ecs_iter_to_json(&it, NULL); + test_assert(json != NULL); + test_json(json, "{\"results\":[{\"name\":\"e1\", \"fields\":{\"values\":[{\"x\":10, \"y\":20, \"z\":30}]}}]}"); + ecs_os_free(json); + + ecs_query_fini(q); + ecs_fini(world); +} + diff --git a/test/meta/src/SerializeToJson.c b/test/meta/src/SerializeToJson.c index ba6949ecc0..3cb05d4f44 100644 --- a/test/meta/src/SerializeToJson.c +++ b/test/meta/src/SerializeToJson.c @@ -1819,3 +1819,38 @@ void SerializeToJson_serialize_from_stage(void) { ecs_fini(world); } + +void SerializeToJson_struct_inherit(void) { + typedef struct { + ecs_i32_t x; + ecs_i32_t y; + ecs_i32_t z; + } Point3D; + + ecs_world_t *world = ecs_init(); + + ecs_entity_t base = ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_entity(world, {.name = "Point2D"}), + .members = { + {"x", ecs_id(ecs_i32_t)}, + {"y", ecs_id(ecs_i32_t)} + } + }); + + ecs_entity_t t = ecs_entity(world, {.name = "Point3D"}); + ecs_add_pair(world, t, EcsIsA, base); + ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = t, + .members = { + {"z", ecs_id(ecs_i32_t)} + } + }); + + Point3D value = {10, 20, 30}; + char *json = ecs_ptr_to_json(world, t, &value); + test_assert(json != NULL); + test_str(json, "{\"x\":10, \"y\":20, \"z\":30}"); + ecs_os_free(json); + + ecs_fini(world); +} diff --git a/test/meta/src/SerializeTypeInfoToJson.c b/test/meta/src/SerializeTypeInfoToJson.c index 16214b7482..956280f5f2 100644 --- a/test/meta/src/SerializeTypeInfoToJson.c +++ b/test/meta/src/SerializeTypeInfoToJson.c @@ -677,3 +677,60 @@ void SerializeTypeInfoToJson_struct_nested_3_members(void) { ecs_fini(world); } + +void SerializeTypeInfoToJson_struct_inherit(void) { + ecs_world_t *world = ecs_init(); + + ecs_entity_t base = ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_entity(world, {.name = "Point2D"}), + .members = { + {"x", ecs_id(ecs_i32_t)}, + {"y", ecs_id(ecs_i32_t)} + } + }); + + ecs_entity_t t = ecs_entity(world, {.name = "Point3D"}); + ecs_add_pair(world, t, EcsIsA, base); + ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = t, + .members = { + {"z", ecs_id(ecs_i32_t)} + } + }); + + char *str = ecs_type_info_to_json(world, t); + test_assert(str != NULL); + test_str(str, "{\"x\":[\"int\"], \"y\":[\"int\"], \"z\":[\"int\"]}"); + ecs_os_free(str); + + ecs_fini(world); +} + +void SerializeTypeInfoToJson_struct_inherit_w_range(void) { + ecs_world_t *world = ecs_init(); + + ecs_entity_t base = ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_entity(world, {.name = "Base"}), + .members = { + {"x", ecs_id(ecs_i32_t), .range = {-1, 1}}, + {"y", ecs_id(ecs_i32_t)} + } + }); + + ecs_entity_t t = ecs_entity(world, {.name = "Derived"}); + ecs_add_pair(world, t, EcsIsA, base); + ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = t, + .members = { + {"z", ecs_id(ecs_i32_t)} + } + }); + + char *str = ecs_type_info_to_json(world, t); + test_assert(str != NULL); + test_str(str, + "{\"x\":[\"int\", {\"range\":[-1, 1]}], \"y\":[\"int\"], \"z\":[\"int\"]}"); + ecs_os_free(str); + + ecs_fini(world); +} diff --git a/test/meta/src/Serialized.c b/test/meta/src/Serialized.c index 5b1488fca9..b2694485e2 100644 --- a/test/meta/src/Serialized.c +++ b/test/meta/src/Serialized.c @@ -2187,3 +2187,42 @@ void Serialized_ops_missing_metatype(void) { ecs_fini(world); } + +void Serialized_ops_inherit(void) { + typedef struct { + ecs_i32_t x; + ecs_i32_t y; + ecs_i32_t z; + } Point3D; + + ecs_world_t *world = ecs_init(); + + ecs_entity_t base = ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_entity(world, {.name = "Point2D"}), + .members = { + {"x", ecs_id(ecs_i32_t)}, + {"y", ecs_id(ecs_i32_t)} + } + }); + + ecs_entity_t t = ecs_entity(world, {.name = "Point3D"}); + ecs_add_pair(world, t, EcsIsA, base); + ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = t, + .members = { + {"z", ecs_id(ecs_i32_t)} + } + }); + + const EcsTypeSerializer *s = ecs_get(world, t, EcsTypeSerializer); + test_assert(s != NULL); + test_int(ecs_vec_count(&s->ops), 5); + + test_op(&s->ops, 0, EcsOpPushStruct, 1, 5, t); + test_mp(&s->ops, 1, EcsOpPrimitive + EcsI32, 1, 1, Point3D, x, ecs_id(ecs_i32_t)); + test_mp(&s->ops, 2, EcsOpPrimitive + EcsI32, 1, 1, Point3D, y, ecs_id(ecs_i32_t)); + test_mp(&s->ops, 3, EcsOpPrimitive + EcsI32, 1, 1, Point3D, z, ecs_id(ecs_i32_t)); + test_op(&s->ops, 4, EcsOpPop, 1, 1, t); + + ecs_fini(world); +} diff --git a/test/meta/src/StructTypes.c b/test/meta/src/StructTypes.c index 999c768679..56cacf814c 100644 --- a/test/meta/src/StructTypes.c +++ b/test/meta/src/StructTypes.c @@ -33,29 +33,22 @@ void _meta_test_member( test_assert(mptr->type == type); } - const EcsStruct *sptr = ecs_get(world, t, EcsStruct); - test_assert(sptr != NULL); + ecs_member_t *member = ecs_struct_get_member(world, t, name); + test_assert(member != NULL); - ecs_member_t *members = ecs_vec_first_t(&sptr->members, ecs_member_t); - int i, count = ecs_vec_count(&sptr->members); + test_str(member->name, name); + test_assert(member->type == type); + test_int(member->offset, offset); + test_int(member->count, elem_count); +} - for (i = 0; i < count; i ++) { - if (m && (members[i].member == m)) { - break; - } else { - if (!ecs_os_strcmp(name, members[i].name)) { - break; - } - } +static +int32_t meta_member_count(ecs_world_t *world, ecs_entity_t t) { + int32_t n = 0; + while (ecs_struct_get_nth_member(world, t, n)) { + n ++; } - - /* Make sure member was found */ - test_assert(i != count); - - test_str(members[i].name, name); - test_assert(members[i].type == type); - test_int(members[i].offset, offset); - test_int(members[i].count, elem_count); + return n; } void StructTypes_i32(void) { @@ -1106,3 +1099,630 @@ void StructTypes_use_before_registering_reflection_w_hooks(void) { ecs_fini(world); } + +void StructTypes_inherit_layout(void) { + typedef struct { + ecs_i32_t x; + ecs_i32_t y; + } Point2D; + + typedef struct { + ecs_i32_t x; + ecs_i32_t y; + ecs_i32_t z; + } Point3D; + + ecs_world_t *world = ecs_init(); + + ecs_entity_t base = ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_entity(world, {.name = "Point2D"}), + .members = { + {"x", ecs_id(ecs_i32_t)}, + {"y", ecs_id(ecs_i32_t)} + } + }); + test_assert(base != 0); + + ecs_entity_t derived = ecs_entity(world, {.name = "Point3D"}); + ecs_add_pair(world, derived, EcsIsA, base); + + ecs_entity_t t = ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = derived, + .members = { + {"z", ecs_id(ecs_i32_t)} + } + }); + test_assert(t == derived); + + meta_test_struct(world, base, Point2D); + meta_test_member(world, base, Point2D, x, ecs_id(ecs_i32_t), 0); + meta_test_member(world, base, Point2D, y, ecs_id(ecs_i32_t), 0); + + meta_test_struct(world, t, Point3D); + meta_test_member(world, t, Point3D, x, ecs_id(ecs_i32_t), 0); + meta_test_member(world, t, Point3D, y, ecs_id(ecs_i32_t), 0); + meta_test_member(world, t, Point3D, z, ecs_id(ecs_i32_t), 0); + + test_int(meta_member_count(world, t), 3); + + test_str(ecs_struct_get_nth_member(world, t, 0)->name, "x"); + test_int(ecs_struct_get_nth_member(world, t, 0)->offset, 0); + test_str(ecs_struct_get_nth_member(world, t, 1)->name, "y"); + test_int(ecs_struct_get_nth_member(world, t, 1)->offset, 4); + test_str(ecs_struct_get_nth_member(world, t, 2)->name, "z"); + test_int(ecs_struct_get_nth_member(world, t, 2)->offset, 8); + + ecs_fini(world); +} + +void StructTypes_inherit_multi_level(void) { + typedef struct { + ecs_i32_t x; + ecs_i32_t y; + ecs_i32_t z; + ecs_i32_t w; + } Point4D; + + ecs_world_t *world = ecs_init(); + + ecs_entity_t base2 = ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_entity(world, {.name = "Point2D"}), + .members = { + {"x", ecs_id(ecs_i32_t)}, + {"y", ecs_id(ecs_i32_t)} + } + }); + + ecs_entity_t base3 = ecs_entity(world, {.name = "Point3D"}); + ecs_add_pair(world, base3, EcsIsA, base2); + ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = base3, + .members = { + {"z", ecs_id(ecs_i32_t)} + } + }); + + ecs_entity_t t = ecs_entity(world, {.name = "Point4D"}); + ecs_add_pair(world, t, EcsIsA, base3); + ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = t, + .members = { + {"w", ecs_id(ecs_i32_t)} + } + }); + + meta_test_struct(world, t, Point4D); + meta_test_member(world, t, Point4D, x, ecs_id(ecs_i32_t), 0); + meta_test_member(world, t, Point4D, y, ecs_id(ecs_i32_t), 0); + meta_test_member(world, t, Point4D, z, ecs_id(ecs_i32_t), 0); + meta_test_member(world, t, Point4D, w, ecs_id(ecs_i32_t), 0); + + test_int(meta_member_count(world, t), 4); + + test_str(ecs_struct_get_nth_member(world, t, 0)->name, "x"); + test_int(ecs_struct_get_nth_member(world, t, 0)->offset, 0); + test_str(ecs_struct_get_nth_member(world, t, 1)->name, "y"); + test_int(ecs_struct_get_nth_member(world, t, 1)->offset, 4); + test_str(ecs_struct_get_nth_member(world, t, 2)->name, "z"); + test_int(ecs_struct_get_nth_member(world, t, 2)->offset, 8); + test_str(ecs_struct_get_nth_member(world, t, 3)->name, "w"); + test_int(ecs_struct_get_nth_member(world, t, 3)->offset, 12); + + ecs_fini(world); +} + +void StructTypes_inherit_base_change_propagates(void) { + ecs_world_t *world = ecs_init(); + + ecs_entity_t base = ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_entity(world, {.name = "Base"}), + .members = { + {"x", ecs_id(ecs_i32_t)} + } + }); + + ecs_entity_t derived = ecs_entity(world, {.name = "Derived"}); + ecs_add_pair(world, derived, EcsIsA, base); + ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = derived, + .members = { + {"z", ecs_id(ecs_i32_t)} + } + }); + + { + const EcsComponent *c = ecs_get(world, derived, EcsComponent); + test_assert(c != NULL); + test_int(c->size, 8); + + test_int(meta_member_count(world, derived), 2); + test_str(ecs_struct_get_nth_member(world, derived, 0)->name, "x"); + test_int(ecs_struct_get_nth_member(world, derived, 0)->offset, 0); + test_str(ecs_struct_get_nth_member(world, derived, 1)->name, "z"); + test_int(ecs_struct_get_nth_member(world, derived, 1)->offset, 4); + } + + ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = base, + .members = { + {"x", ecs_id(ecs_i32_t)}, + {"y", ecs_id(ecs_i32_t)} + } + }); + + { + const EcsComponent *c = ecs_get(world, derived, EcsComponent); + test_assert(c != NULL); + test_int(c->size, 12); + + test_int(meta_member_count(world, derived), 3); + test_str(ecs_struct_get_nth_member(world, derived, 0)->name, "x"); + test_int(ecs_struct_get_nth_member(world, derived, 0)->offset, 0); + test_str(ecs_struct_get_nth_member(world, derived, 1)->name, "y"); + test_int(ecs_struct_get_nth_member(world, derived, 1)->offset, 4); + test_str(ecs_struct_get_nth_member(world, derived, 2)->name, "z"); + test_int(ecs_struct_get_nth_member(world, derived, 2)->offset, 8); + } + + ecs_fini(world); +} + +void StructTypes_inherit_base_tail_padding(void) { + typedef struct { + ecs_i32_t i; + ecs_char_t c; + } BaseTP; + + typedef struct { + BaseTP base; + ecs_char_t d; + } DerivedTP; + + ecs_world_t *world = ecs_init(); + + ecs_entity_t base = ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_entity(world, {.name = "BaseTP"}), + .members = { + {"i", ecs_id(ecs_i32_t)}, + {"c", ecs_id(ecs_char_t)} + } + }); + + ecs_entity_t t = ecs_entity(world, {.name = "DerivedTP"}); + ecs_add_pair(world, t, EcsIsA, base); + ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = t, + .members = { + {"d", ecs_id(ecs_char_t)} + } + }); + + meta_test_struct(world, base, BaseTP); + meta_test_struct(world, t, DerivedTP); + + test_int(meta_member_count(world, t), 3); + + test_str(ecs_struct_get_nth_member(world, t, 0)->name, "i"); + test_int(ecs_struct_get_nth_member(world, t, 0)->offset, 0); + test_str(ecs_struct_get_nth_member(world, t, 1)->name, "c"); + test_int(ecs_struct_get_nth_member(world, t, 1)->offset, 4); + + test_str(ecs_struct_get_nth_member(world, t, 2)->name, "d"); + test_int(ecs_struct_get_nth_member(world, t, 2)->offset, 8); + test_int(ecs_struct_get_nth_member(world, t, 2)->offset, offsetof(DerivedTP, d)); + + ecs_fini(world); +} + +void StructTypes_inherit_derived_larger_align(void) { + typedef struct { + ecs_i32_t x; + } Base; + + typedef struct { + Base base; + ecs_f64_t v; + } Derived; + + ecs_world_t *world = ecs_init(); + + ecs_entity_t base = ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_entity(world, {.name = "Base"}), + .members = { + {"x", ecs_id(ecs_i32_t)} + } + }); + + ecs_entity_t t = ecs_entity(world, {.name = "Derived"}); + ecs_add_pair(world, t, EcsIsA, base); + ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = t, + .members = { + {"v", ecs_id(ecs_f64_t)} + } + }); + + meta_test_struct(world, base, Base); + meta_test_struct(world, t, Derived); + + test_int(meta_member_count(world, t), 2); + + test_str(ecs_struct_get_nth_member(world, t, 0)->name, "x"); + test_int(ecs_struct_get_nth_member(world, t, 0)->offset, 0); + + test_str(ecs_struct_get_nth_member(world, t, 1)->name, "v"); + test_int(ecs_struct_get_nth_member(world, t, 1)->offset, 8); + test_int(ecs_struct_get_nth_member(world, t, 1)->offset, offsetof(Derived, v)); + + ecs_fini(world); +} + +void StructTypes_inherit_shadow_member_fails(void) { + ecs_log_set_level(-4); + + ecs_world_t *world = ecs_init(); + + ecs_entity_t base = ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_entity(world, {.name = "Base"}), + .members = { + {"x", ecs_id(ecs_i32_t)} + } + }); + test_assert(base != 0); + + ecs_entity_t derived = ecs_entity(world, {.name = "Derived"}); + ecs_add_pair(world, derived, EcsIsA, base); + + ecs_entity_t t = ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = derived, + .members = { + {"x", ecs_id(ecs_i32_t)} + } + }); + + test_assert(t == 0); + + ecs_fini(world); +} + +void StructTypes_inherit_empty_base(void) { + typedef struct { + ecs_i32_t z; + } Derived; + + ecs_world_t *world = ecs_init(); + + ecs_entity_t base = ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_entity(world, {.name = "Base"}) + }); + test_assert(base != 0); + test_assert(!ecs_has(world, base, EcsType)); + + ecs_entity_t t = ecs_entity(world, {.name = "Derived"}); + ecs_add_pair(world, t, EcsIsA, base); + ecs_entity_t r = ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = t, + .members = { + {"z", ecs_id(ecs_i32_t)} + } + }); + test_assert(r == t); + + meta_test_struct(world, t, Derived); + + test_int(meta_member_count(world, t), 1); + test_str(ecs_struct_get_nth_member(world, t, 0)->name, "z"); + test_int(ecs_struct_get_nth_member(world, t, 0)->offset, 0); + + ecs_fini(world); +} + +void StructTypes_inherit_empty_derived(void) { + typedef struct { + ecs_i32_t x; + ecs_i32_t y; + } Point2D; + + ecs_world_t *world = ecs_init(); + + ecs_entity_t base = ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_entity(world, {.name = "Point2D"}), + .members = { + {"x", ecs_id(ecs_i32_t)}, + {"y", ecs_id(ecs_i32_t)} + } + }); + + ecs_entity_t t = ecs_entity(world, {.name = "Derived"}); + ecs_add_pair(world, t, EcsIsA, base); + ecs_entity_t r = ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = t + }); + test_assert(r == t); + + test_assert(ecs_has(world, t, EcsType)); + meta_test_struct(world, t, Point2D); + + test_int(meta_member_count(world, t), 2); + test_str(ecs_struct_get_nth_member(world, t, 0)->name, "x"); + test_int(ecs_struct_get_nth_member(world, t, 0)->offset, 0); + test_str(ecs_struct_get_nth_member(world, t, 1)->name, "y"); + test_int(ecs_struct_get_nth_member(world, t, 1)->offset, 4); + + ecs_fini(world); +} + +void StructTypes_inherit_empty_both(void) { + ecs_world_t *world = ecs_init(); + + ecs_entity_t base = ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_entity(world, {.name = "Base"}) + }); + test_assert(base != 0); + + ecs_entity_t t = ecs_entity(world, {.name = "Derived"}); + ecs_add_pair(world, t, EcsIsA, base); + ecs_entity_t r = ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = t + }); + test_assert(r == t); + + test_int(meta_member_count(world, t), 0); + + ecs_fini(world); +} + +void StructTypes_inherit_from_non_component(void) { + typedef struct { + ecs_i32_t x; + } Derived; + + ecs_world_t *world = ecs_init(); + + ecs_entity_t base = ecs_entity(world, {.name = "NotAType"}); + + ecs_entity_t t = ecs_entity(world, {.name = "Derived"}); + ecs_add_pair(world, t, EcsIsA, base); + ecs_entity_t r = ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = t, + .members = { + {"x", ecs_id(ecs_i32_t)} + } + }); + test_assert(r == t); + + meta_test_struct(world, t, Derived); + + test_int(meta_member_count(world, t), 1); + test_str(ecs_struct_get_nth_member(world, t, 0)->name, "x"); + test_int(ecs_struct_get_nth_member(world, t, 0)->offset, 0); + + ecs_fini(world); +} + +void StructTypes_inherit_propagate_stress(void) { + ecs_world_t *world = ecs_init(); + + ecs_entity_t base = ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_entity(world, {.name = "Base"}), + .members = { + {"x", ecs_id(ecs_i32_t)} + } + }); + + ecs_entity_t d[16]; + int i; + for (i = 0; i < 16; i ++) { + d[i] = ecs_new(world); + ecs_add_pair(world, d[i], EcsIsA, base); + ecs_add_id(world, d[i], ecs_id(EcsStruct)); + test_assert(!ecs_has(world, d[i], EcsType)); + } + + ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = base, + .members = { + {"x", ecs_id(ecs_i32_t)}, + {"y", ecs_id(ecs_i32_t)} + } + }); + + for (i = 0; i < 16; i ++) { + test_int(meta_member_count(world, d[i]), 2); + test_assert(ecs_has(world, d[i], EcsType)); + } + + ecs_entity_t mid = ecs_entity(world, {.name = "Mid"}); + ecs_add_pair(world, mid, EcsIsA, base); + ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = mid, .members = {{"z", ecs_id(ecs_i32_t)}} + }); + + ecs_entity_t leaf = ecs_entity(world, {.name = "Leaf"}); + ecs_add_pair(world, leaf, EcsIsA, mid); + ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = leaf, .members = {{"w", ecs_id(ecs_i32_t)}} + }); + + ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = base, + .members = { + {"x", ecs_id(ecs_i32_t)}, + {"y", ecs_id(ecs_i32_t)} + } + }); + + test_int(meta_member_count(world, mid), 3); + test_int(meta_member_count(world, leaf), 4); + + ecs_fini(world); +} + +void StructTypes_inherit_base_member_type_change(void) { + ecs_world_t *world = ecs_init(); + + ecs_entity_t base = ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_entity(world, {.name = "Base"}), + .members = { + {"x", ecs_id(ecs_i32_t)} + } + }); + + ecs_entity_t derived = ecs_entity(world, {.name = "Derived"}); + ecs_add_pair(world, derived, EcsIsA, base); + ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = derived, + .members = { + {"z", ecs_id(ecs_i32_t)} + } + }); + + { + test_int(meta_member_count(world, derived), 2); + test_uint(ecs_struct_get_nth_member(world, derived, 0)->type, ecs_id(ecs_i32_t)); + test_int(ecs_struct_get_nth_member(world, derived, 0)->offset, 0); + test_int(ecs_struct_get_nth_member(world, derived, 1)->offset, 4); + test_int(ecs_get(world, derived, EcsComponent)->size, 8); + } + + ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = base, + .members = { + {"x", ecs_id(ecs_i64_t)} + } + }); + + { + test_int(meta_member_count(world, derived), 2); + test_str(ecs_struct_get_nth_member(world, derived, 0)->name, "x"); + test_uint(ecs_struct_get_nth_member(world, derived, 0)->type, ecs_id(ecs_i64_t)); + test_int(ecs_struct_get_nth_member(world, derived, 0)->offset, 0); + test_str(ecs_struct_get_nth_member(world, derived, 1)->name, "z"); + test_int(ecs_struct_get_nth_member(world, derived, 1)->offset, 8); + test_int(ecs_get(world, derived, EcsComponent)->size, 16); + } + + ecs_fini(world); +} + +void StructTypes_inherit_explicit_offset_fails(void) { + ecs_log_set_level(-4); + + ecs_world_t *world = ecs_init(); + + ecs_entity_t base = ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_entity(world, {.name = "Base"}), + .members = { + {"x", ecs_id(ecs_i32_t)} + } + }); + test_assert(base != 0); + + ecs_entity_t derived = ecs_entity(world, {.name = "Derived"}); + ecs_add_pair(world, derived, EcsIsA, base); + + ecs_entity_t t = ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = derived, + .members = { + {"z", ecs_id(ecs_i32_t), .offset = 64} + } + }); + + test_assert(t == 0); + + ecs_fini(world); +} + +void StructTypes_inherit_base_redefined_fewer_members(void) { + ecs_world_t *world = ecs_init(); + + ecs_entity_t base = ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_entity(world, {.name = "Base"}), + .members = { + {"x", ecs_id(ecs_i32_t)}, + {"y", ecs_id(ecs_i32_t)} + } + }); + + ecs_entity_t derived = ecs_entity(world, {.name = "Derived"}); + ecs_add_pair(world, derived, EcsIsA, base); + ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = derived, + .members = { + {"z", ecs_id(ecs_i32_t)} + } + }); + + test_int(meta_member_count(world, derived), 3); + + ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = base, + .members = { + {"x", ecs_id(ecs_i32_t)} + } + }); + + test_int(meta_member_count(world, base), 1); + test_int(meta_member_count(world, derived), 2); + + test_str(ecs_struct_get_nth_member(world, derived, 0)->name, "x"); + test_int(ecs_struct_get_nth_member(world, derived, 0)->offset, 0); + test_str(ecs_struct_get_nth_member(world, derived, 1)->name, "z"); + test_int(ecs_struct_get_nth_member(world, derived, 1)->offset, 4); + + test_assert(ecs_struct_get_member(world, derived, "y") == NULL); + test_int(ecs_get(world, derived, EcsComponent)->size, 8); + + ecs_fini(world); +} + +void StructTypes_member_w_type_not_alive(void) { + ecs_world_t *world = ecs_init(); + + ecs_log_set_level(-4); + + ecs_entity_t not_alive = ecs_new(world); + ecs_delete(world, not_alive); + test_assert(!ecs_is_alive(world, not_alive)); + + ecs_entity_t s = ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_entity(world, {.name = "S"}), + .members = { + {"x", not_alive} + } + }); + + test_assert(s == 0); + + ecs_fini(world); +} + +void StructTypes_redefine_fewer_members(void) { + ecs_world_t *world = ecs_init(); + + ecs_entity_t t = ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = ecs_entity(world, {.name = "T"}), + .members = { + {"x", ecs_id(ecs_i32_t)}, + {"y", ecs_id(ecs_i32_t)} + } + }); + + test_int(meta_member_count(world, t), 2); + test_int(ecs_get(world, t, EcsComponent)->size, 8); + + ecs_struct_init(world, &(ecs_struct_desc_t){ + .entity = t, + .members = { + {"x", ecs_id(ecs_i32_t)} + } + }); + + test_int(meta_member_count(world, t), 1); + test_str(ecs_struct_get_nth_member(world, t, 0)->name, "x"); + test_int(ecs_struct_get_nth_member(world, t, 0)->offset, 0); + test_assert(ecs_struct_get_member(world, t, "y") == NULL); + test_int(ecs_get(world, t, EcsComponent)->size, 4); + + ecs_fini(world); +} diff --git a/test/meta/src/main.c b/test/meta/src/main.c index c35b87c86b..18965d18dd 100644 --- a/test/meta/src/main.c +++ b/test/meta/src/main.c @@ -186,6 +186,22 @@ void StructTypes_direct_cycle(void); void StructTypes_indirect_cycle(void); void StructTypes_use_before_registering_reflection(void); void StructTypes_use_before_registering_reflection_w_hooks(void); +void StructTypes_inherit_layout(void); +void StructTypes_inherit_multi_level(void); +void StructTypes_inherit_base_change_propagates(void); +void StructTypes_inherit_base_tail_padding(void); +void StructTypes_inherit_derived_larger_align(void); +void StructTypes_inherit_shadow_member_fails(void); +void StructTypes_inherit_empty_base(void); +void StructTypes_inherit_empty_derived(void); +void StructTypes_inherit_empty_both(void); +void StructTypes_inherit_from_non_component(void); +void StructTypes_inherit_propagate_stress(void); +void StructTypes_inherit_base_member_type_change(void); +void StructTypes_inherit_explicit_offset_fails(void); +void StructTypes_inherit_base_redefined_fewer_members(void); +void StructTypes_redefine_fewer_members(void); +void StructTypes_member_w_type_not_alive(void); // Testsuite 'NestedStructTypes' void NestedStructTypes_1_bool(void); @@ -337,6 +353,7 @@ void Serialized_ops_struct_w_bitmask(void); void Serialized_ops_enum(void); void Serialized_ops_struct_w_enum(void); void Serialized_ops_missing_metatype(void); +void Serialized_ops_inherit(void); // Testsuite 'Cursor' void Cursor_set_bool(void); @@ -497,6 +514,8 @@ void Cursor_set_out_of_bounds(void); void Cursor_get_member_id(void); void Cursor_get_array_type(void); void Cursor_get_vector_type(void); +void Cursor_struct_inherit(void); +void Cursor_struct_inherit_by_member(void); // Testsuite 'DeserializeFromJson' void DeserializeFromJson_struct_bool(void); @@ -659,6 +678,7 @@ void DeserializeFromJson_ser_deser_dont_fragment_component(void); void DeserializeFromJson_ser_deser_dont_fragment_pair_multi_target(void); void DeserializeFromJson_ser_deser_dont_fragment_component_pair(void); void DeserializeFromJson_ser_deser_dont_fragment_tag_removes_stale(void); +void DeserializeFromJson_struct_inherit(void); // Testsuite 'SerializeToJson' void SerializeToJson_struct_bool(void); @@ -717,6 +737,7 @@ void SerializeToJson_enum_underlying_u32(void); void SerializeToJson_enum_underlying_u64(void); void SerializeToJson_enum_underlying_uptr(void); void SerializeToJson_serialize_from_stage(void); +void SerializeToJson_struct_inherit(void); // Testsuite 'SerializeEntityToJson' void SerializeEntityToJson_serialize_empty(void); @@ -907,6 +928,7 @@ void SerializeIterToJson_serialize_children_w_tag_w_parent_component(void); void SerializeIterToJson_serialize_children_w_tag_w_parent_component_table(void); void SerializeIterToJson_serialize_childof_var_w_parent(void); void SerializeIterToJson_serialize_childof_wildcard_w_parent(void); +void SerializeIterToJson_serialize_w_inherited_component(void); // Testsuite 'SerializeIterToRowJson' void SerializeIterToRowJson_serialize_this_w_1_tag(void); @@ -1012,6 +1034,8 @@ void SerializeTypeInfoToJson_struct_nested(void); void SerializeTypeInfoToJson_struct_nested_2_lvls(void); void SerializeTypeInfoToJson_struct_nested_2_members(void); void SerializeTypeInfoToJson_struct_nested_3_members(void); +void SerializeTypeInfoToJson_struct_inherit(void); +void SerializeTypeInfoToJson_struct_inherit_w_range(void); // Testsuite 'SerializeQueryInfoToJson' void SerializeQueryInfoToJson_1_tag(void); @@ -1068,6 +1092,8 @@ void MetaUtils_enum_constant_w_type_prefix(void); void MetaUtils_enum_constant_w_name_type_prefix(void); void MetaUtils_struct_has_member_entities(void); void MetaUtils_fwd_decl(void); +void MetaUtils_struct_inherit(void); +void MetaUtils_struct_inherit_from_expr(void); // Testsuite 'OpaqueTypes' void OpaqueTypes_ser_i32_type_to_json(void); @@ -1881,6 +1907,70 @@ bake_test_case StructTypes_testcases[] = { { "use_before_registering_reflection_w_hooks", StructTypes_use_before_registering_reflection_w_hooks + }, + { + "inherit_layout", + StructTypes_inherit_layout + }, + { + "inherit_multi_level", + StructTypes_inherit_multi_level + }, + { + "inherit_base_change_propagates", + StructTypes_inherit_base_change_propagates + }, + { + "inherit_base_tail_padding", + StructTypes_inherit_base_tail_padding + }, + { + "inherit_derived_larger_align", + StructTypes_inherit_derived_larger_align + }, + { + "inherit_shadow_member_fails", + StructTypes_inherit_shadow_member_fails + }, + { + "inherit_empty_base", + StructTypes_inherit_empty_base + }, + { + "inherit_empty_derived", + StructTypes_inherit_empty_derived + }, + { + "inherit_empty_both", + StructTypes_inherit_empty_both + }, + { + "inherit_from_non_component", + StructTypes_inherit_from_non_component + }, + { + "inherit_propagate_stress", + StructTypes_inherit_propagate_stress + }, + { + "inherit_base_member_type_change", + StructTypes_inherit_base_member_type_change + }, + { + "inherit_explicit_offset_fails", + StructTypes_inherit_explicit_offset_fails + }, + { + "inherit_base_redefined_fewer_members", + StructTypes_inherit_base_redefined_fewer_members + }, + { + "redefine_fewer_members", + StructTypes_redefine_fewer_members + }, + { + "member_w_type_not_alive", + StructTypes_member_w_type_not_alive } }; @@ -2460,6 +2550,10 @@ bake_test_case Serialized_testcases[] = { { "ops_missing_metatype", Serialized_ops_missing_metatype + }, + { + "ops_inherit", + Serialized_ops_inherit } }; @@ -3095,6 +3189,14 @@ bake_test_case Cursor_testcases[] = { { "get_vector_type", Cursor_get_vector_type + }, + { + "struct_inherit", + Cursor_struct_inherit + }, + { + "struct_inherit_by_member", + Cursor_struct_inherit_by_member } }; @@ -3738,6 +3840,10 @@ bake_test_case DeserializeFromJson_testcases[] = { { "ser_deser_dont_fragment_tag_removes_stale", DeserializeFromJson_ser_deser_dont_fragment_tag_removes_stale + }, + { + "struct_inherit", + DeserializeFromJson_struct_inherit } }; @@ -3965,6 +4071,10 @@ bake_test_case SerializeToJson_testcases[] = { { "serialize_from_stage", SerializeToJson_serialize_from_stage + }, + { + "struct_inherit", + SerializeToJson_struct_inherit } }; @@ -4715,6 +4825,10 @@ bake_test_case SerializeIterToJson_testcases[] = { { "serialize_childof_wildcard_w_parent", SerializeIterToJson_serialize_childof_wildcard_w_parent + }, + { + "serialize_w_inherited_component", + SerializeIterToJson_serialize_w_inherited_component } }; @@ -5125,6 +5239,14 @@ bake_test_case SerializeTypeInfoToJson_testcases[] = { { "struct_nested_3_members", SerializeTypeInfoToJson_struct_nested_3_members + }, + { + "struct_inherit", + SerializeTypeInfoToJson_struct_inherit + }, + { + "struct_inherit_w_range", + SerializeTypeInfoToJson_struct_inherit_w_range } }; @@ -5339,6 +5461,14 @@ bake_test_case MetaUtils_testcases[] = { { "fwd_decl", MetaUtils_fwd_decl + }, + { + "struct_inherit", + MetaUtils_struct_inherit + }, + { + "struct_inherit_from_expr", + MetaUtils_struct_inherit_from_expr } }; @@ -5851,7 +5981,7 @@ static bake_test_suite suites[] = { "StructTypes", NULL, NULL, - 36, + 52, StructTypes_testcases }, { @@ -5886,28 +6016,28 @@ static bake_test_suite suites[] = { "Serialized", NULL, NULL, - 62, + 63, Serialized_testcases }, { "Cursor", NULL, NULL, - 158, + 160, Cursor_testcases }, { "DeserializeFromJson", NULL, NULL, - 160, + 161, DeserializeFromJson_testcases }, { "SerializeToJson", NULL, NULL, - 56, + 57, SerializeToJson_testcases }, { @@ -5921,7 +6051,7 @@ static bake_test_suite suites[] = { "SerializeIterToJson", NULL, NULL, - 86, + 87, SerializeIterToJson_testcases }, { @@ -5935,7 +6065,7 @@ static bake_test_suite suites[] = { "SerializeTypeInfoToJson", NULL, NULL, - 40, + 42, SerializeTypeInfoToJson_testcases }, { @@ -5949,7 +6079,7 @@ static bake_test_suite suites[] = { "MetaUtils", NULL, NULL, - 25, + 27, MetaUtils_testcases }, { diff --git a/test/query/project.json b/test/query/project.json index 1f608fb98c..f46c54bd44 100644 --- a/test/query/project.json +++ b/test/query/project.json @@ -1339,11 +1339,15 @@ ] }, { "id": "ComponentInheritance", + "setup": true, + "params": { + "cache_kind": ["default", "auto"] + }, "testcases": [ - "1_ent_0_lvl", - "1_ent_1_lvl", - "1_ent_2_lvl", - "1_ent_3_lvl", + "1_fixed_0_lvl", + "1_fixed_1_lvl", + "1_fixed_2_lvl", + "1_fixed_3_lvl", "1_this_0_lvl", "1_this_1_lvl", "1_this_2_lvl", @@ -1360,12 +1364,27 @@ "1_var_1_lvl_written", "1_var_2_lvl_written", "1_var_3_lvl_written", - "1_ent_1_lvl_self", + "1_fixed_component", + "1_this_component", + "1_this_component_written", + "1_var_component", + "1_var_component_written", + "1_fixed_pair", + "1_this_pair", + "1_this_pair_written", + "1_var_pair", + "1_var_pair_written", + "1_fixed_component_pair", + "1_this_component_pair", + "1_this_component_pair_written", + "1_var_component_pair", + "1_var_component_pair_written", + "1_fixed_1_lvl_self", "1_this_1_lvl_self", "1_this_1_lvl_written_self", "1_var_1_lvl_self", "1_var_1_lvl_written_self", - "1_ent_src_not", + "1_fixed_src_not", "1_this_src_not", "1_var_src_not", "1_this_src_not_written", @@ -1375,7 +1394,28 @@ "query_before_isa_relationship_1st_term", "query_before_isa_relationship_2nd_term", "query_before_isa_relationship_subtype", - "query_before_isa_relationship_0_src" + "query_before_isa_relationship_0_src", + "populated_remove_isa_2_lvl", + "populated_remove_isa_3_lvl", + "populated_match_entities_added_after", + "populated_remove_isa_var", + "optional_inherited", + "or_inherited", + "multiple_inherited_terms", + "up_traversal_inherited", + "multi_derived_fixed", + "multi_derived_this", + "multi_derived_this_written", + "multi_derived_var", + "multi_derived_mid_level_once", + "multi_derived_count_results", + "pair_3_lvl", + "pair_multi", + "pair_different_target", + "pair_wildcard", + "pair_wildcard_multi_rel", + "cached_match_derived_table_added_after", + "cached_unmatch_derived_table_on_delete" ] }, { "id": "BuiltinPredicates", diff --git a/test/query/src/ComponentInheritance.c b/test/query/src/ComponentInheritance.c index d7d4906341..dff61392a4 100644 --- a/test/query/src/ComponentInheritance.c +++ b/test/query/src/ComponentInheritance.c @@ -1,5 +1,29 @@ #include +typedef struct { + int32_t value; +} Weight; + +typedef struct { + int32_t value; + int32_t bonus; +} HeavyWeight; + +static ecs_query_cache_kind_t cache_kind = EcsQueryCacheDefault; + +void ComponentInheritance_setup(void) { + const char *cache_param = test_param("cache_kind"); + if (cache_param) { + if (!strcmp(cache_param, "default")) { + // already set to default + } else if (!strcmp(cache_param, "auto")) { + cache_kind = EcsQueryCacheAuto; + } else { + printf("unexpected value for cache_param '%s'\n", cache_param); + } + } +} + ECS_TAG_DECLARE(Unit); ECS_TAG_DECLARE(MeleeUnit); ECS_TAG_DECLARE(RangedUnit); @@ -19,7 +43,7 @@ void populate_facts(ecs_world_t *world) { ECS_ENTITY_DEFINE(world, Warlock, (IsA, Wizard), (IsA, Warrior)); } -void ComponentInheritance_1_ent_0_lvl(void) { +void ComponentInheritance_1_fixed_0_lvl(void) { ecs_world_t *world = ecs_mini(); populate_facts(world); @@ -28,6 +52,7 @@ void ComponentInheritance_1_ent_0_lvl(void) { ecs_add(world, e1, Warlock); ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "Warlock(e1)" }); @@ -49,7 +74,7 @@ void ComponentInheritance_1_ent_0_lvl(void) { ecs_fini(world); } -void ComponentInheritance_1_ent_1_lvl(void) { +void ComponentInheritance_1_fixed_1_lvl(void) { ecs_world_t *world = ecs_mini(); populate_facts(world); @@ -58,6 +83,7 @@ void ComponentInheritance_1_ent_1_lvl(void) { ecs_add(world, e1, Warlock); ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "Warrior(e1)" }); @@ -79,7 +105,7 @@ void ComponentInheritance_1_ent_1_lvl(void) { ecs_fini(world); } -void ComponentInheritance_1_ent_2_lvl(void) { +void ComponentInheritance_1_fixed_2_lvl(void) { ecs_world_t *world = ecs_mini(); populate_facts(world); @@ -88,6 +114,7 @@ void ComponentInheritance_1_ent_2_lvl(void) { ecs_add(world, e1, Warlock); ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "MeleeUnit(e1)" }); @@ -109,7 +136,7 @@ void ComponentInheritance_1_ent_2_lvl(void) { ecs_fini(world); } -void ComponentInheritance_1_ent_3_lvl(void) { +void ComponentInheritance_1_fixed_3_lvl(void) { ecs_world_t *world = ecs_mini(); populate_facts(world); @@ -118,6 +145,7 @@ void ComponentInheritance_1_ent_3_lvl(void) { ecs_add(world, e1, Warlock); ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "Unit(e1)" }); @@ -131,12 +159,6 @@ void ComponentInheritance_1_ent_3_lvl(void) { test_bool(true, ecs_field_is_set(&it, 0)); test_uint(e1, ecs_field_src(&it, 0)); - test_bool(true, ecs_query_next(&it)); /* not ideal, diamond problem */ - test_uint(0, it.count); - test_uint(Warlock, ecs_field_id(&it, 0)); - test_bool(true, ecs_field_is_set(&it, 0)); - test_uint(e1, ecs_field_src(&it, 0)); - test_bool(false, ecs_query_next(&it)); } @@ -160,6 +182,7 @@ void ComponentInheritance_1_this_0_lvl(void) { ecs_entity_t e7 = ecs_new_w(world, Unit); ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "Unit($this)" }); @@ -169,24 +192,17 @@ void ComponentInheritance_1_this_0_lvl(void) { ecs_iter_t it = ecs_query_iter(world, r); test_bool(true, ecs_query_next(&it)); test_uint(1, it.count); - test_uint(Unit, ecs_field_id(&it, 0)); - test_uint(0, ecs_field_src(&it, 0)); - test_bool(true, ecs_field_is_set(&it, 0)); - test_uint(e7, it.entities[0]); - - test_bool(true, ecs_query_next(&it)); - test_uint(1, it.count); - test_uint(MeleeUnit, ecs_field_id(&it, 0)); + test_uint(Warlock, ecs_field_id(&it, 0)); test_uint(0, ecs_field_src(&it, 0)); test_bool(true, ecs_field_is_set(&it, 0)); - test_uint(e5, it.entities[0]); + test_uint(e1, it.entities[0]); test_bool(true, ecs_query_next(&it)); test_uint(1, it.count); - test_uint(RangedUnit, ecs_field_id(&it, 0)); + test_uint(Wizard, ecs_field_id(&it, 0)); test_uint(0, ecs_field_src(&it, 0)); test_bool(true, ecs_field_is_set(&it, 0)); - test_uint(e6, it.entities[0]); + test_uint(e2, it.entities[0]); test_bool(true, ecs_query_next(&it)); test_uint(1, it.count); @@ -197,31 +213,31 @@ void ComponentInheritance_1_this_0_lvl(void) { test_bool(true, ecs_query_next(&it)); test_uint(1, it.count); - test_uint(Warlock, ecs_field_id(&it, 0)); + test_uint(Archer, ecs_field_id(&it, 0)); test_uint(0, ecs_field_src(&it, 0)); test_bool(true, ecs_field_is_set(&it, 0)); - test_uint(e1, it.entities[0]); + test_uint(e4, it.entities[0]); test_bool(true, ecs_query_next(&it)); test_uint(1, it.count); - test_uint(Archer, ecs_field_id(&it, 0)); + test_uint(MeleeUnit, ecs_field_id(&it, 0)); test_uint(0, ecs_field_src(&it, 0)); test_bool(true, ecs_field_is_set(&it, 0)); - test_uint(e4, it.entities[0]); + test_uint(e5, it.entities[0]); test_bool(true, ecs_query_next(&it)); test_uint(1, it.count); - test_uint(Wizard, ecs_field_id(&it, 0)); + test_uint(RangedUnit, ecs_field_id(&it, 0)); test_uint(0, ecs_field_src(&it, 0)); test_bool(true, ecs_field_is_set(&it, 0)); - test_uint(e2, it.entities[0]); + test_uint(e6, it.entities[0]); test_bool(true, ecs_query_next(&it)); test_uint(1, it.count); - test_uint(Warlock, ecs_field_id(&it, 0)); + test_uint(Unit, ecs_field_id(&it, 0)); test_uint(0, ecs_field_src(&it, 0)); test_bool(true, ecs_field_is_set(&it, 0)); - test_uint(e1, it.entities[0]); + test_uint(e7, it.entities[0]); test_bool(false, ecs_query_next(&it)); } @@ -245,6 +261,7 @@ void ComponentInheritance_1_this_1_lvl(void) { /* ecs_entity_t e7 = */ ecs_new_w(world, Unit); ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "MeleeUnit($this)" }); @@ -254,10 +271,10 @@ void ComponentInheritance_1_this_1_lvl(void) { ecs_iter_t it = ecs_query_iter(world, r); test_bool(true, ecs_query_next(&it)); test_uint(1, it.count); - test_uint(MeleeUnit, ecs_field_id(&it, 0)); + test_uint(Warlock, ecs_field_id(&it, 0)); test_uint(0, ecs_field_src(&it, 0)); test_bool(true, ecs_field_is_set(&it, 0)); - test_uint(e5, it.entities[0]); + test_uint(e1, it.entities[0]); test_bool(true, ecs_query_next(&it)); test_uint(1, it.count); @@ -268,10 +285,10 @@ void ComponentInheritance_1_this_1_lvl(void) { test_bool(true, ecs_query_next(&it)); test_uint(1, it.count); - test_uint(Warlock, ecs_field_id(&it, 0)); + test_uint(MeleeUnit, ecs_field_id(&it, 0)); test_uint(0, ecs_field_src(&it, 0)); test_bool(true, ecs_field_is_set(&it, 0)); - test_uint(e1, it.entities[0]); + test_uint(e5, it.entities[0]); test_bool(false, ecs_query_next(&it)); } @@ -295,6 +312,7 @@ void ComponentInheritance_1_this_2_lvl(void) { /* ecs_entity_t e7 = */ ecs_new_w(world, Unit); ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "Warrior($this)" }); @@ -304,17 +322,17 @@ void ComponentInheritance_1_this_2_lvl(void) { ecs_iter_t it = ecs_query_iter(world, r); test_bool(true, ecs_query_next(&it)); test_uint(1, it.count); - test_uint(Warrior, ecs_field_id(&it, 0)); + test_uint(Warlock, ecs_field_id(&it, 0)); test_uint(0, ecs_field_src(&it, 0)); test_bool(true, ecs_field_is_set(&it, 0)); - test_uint(e3, it.entities[0]); + test_uint(e1, it.entities[0]); test_bool(true, ecs_query_next(&it)); test_uint(1, it.count); - test_uint(Warlock, ecs_field_id(&it, 0)); + test_uint(Warrior, ecs_field_id(&it, 0)); test_uint(0, ecs_field_src(&it, 0)); test_bool(true, ecs_field_is_set(&it, 0)); - test_uint(e1, it.entities[0]); + test_uint(e3, it.entities[0]); test_bool(false, ecs_query_next(&it)); } @@ -338,6 +356,7 @@ void ComponentInheritance_1_this_3_lvl(void) { /* ecs_entity_t e7 = */ ecs_new_w(world, Unit); ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "Warlock($this)" }); @@ -390,6 +409,7 @@ void ComponentInheritance_1_this_0_lvl_written(void) { ecs_new_w(world, Warlock); ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "Tag($this), Unit($this)" }); @@ -407,16 +427,6 @@ void ComponentInheritance_1_this_0_lvl_written(void) { test_bool(true, ecs_field_is_set(&it, 1)); test_uint(e1, it.entities[0]); - test_bool(true, ecs_query_next(&it)); - test_uint(1, it.count); - test_uint(Tag, ecs_field_id(&it, 0)); - test_uint(Warlock, ecs_field_id(&it, 1)); - test_uint(0, ecs_field_src(&it, 0)); - test_uint(0, ecs_field_src(&it, 1)); - test_bool(true, ecs_field_is_set(&it, 0)); - test_bool(true, ecs_field_is_set(&it, 1)); - test_uint(e1, it.entities[0]); - test_bool(true, ecs_query_next(&it)); test_uint(1, it.count); test_uint(Tag, ecs_field_id(&it, 0)); @@ -515,6 +525,7 @@ void ComponentInheritance_1_this_1_lvl_written(void) { ecs_new_w(world, Warlock); ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "Tag($this), MeleeUnit($this)" }); @@ -590,6 +601,7 @@ void ComponentInheritance_1_this_2_lvl_written(void) { ecs_new_w(world, Warlock); ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "Tag($this), Warrior($this)" }); @@ -655,6 +667,7 @@ void ComponentInheritance_1_this_3_lvl_written(void) { ecs_new_w(world, Warlock); ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "Tag($this), Warlock($this)" }); @@ -694,6 +707,7 @@ void ComponentInheritance_1_var_0_lvl(void) { ecs_entity_t e7 = ecs_new_w(world, Unit); ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "Unit($x)" }); @@ -706,24 +720,17 @@ void ComponentInheritance_1_var_0_lvl(void) { ecs_iter_t it = ecs_query_iter(world, r); test_bool(true, ecs_query_next(&it)); test_uint(0, it.count); - test_uint(Unit, ecs_field_id(&it, 0)); - test_uint(e7, ecs_field_src(&it, 0)); - test_bool(true, ecs_field_is_set(&it, 0)); - test_uint(e7, ecs_iter_get_var(&it, x_var)); - - test_bool(true, ecs_query_next(&it)); - test_uint(0, it.count); - test_uint(MeleeUnit, ecs_field_id(&it, 0)); - test_uint(e5, ecs_field_src(&it, 0)); + test_uint(Warlock, ecs_field_id(&it, 0)); + test_uint(e1, ecs_field_src(&it, 0)); test_bool(true, ecs_field_is_set(&it, 0)); - test_uint(e5, ecs_iter_get_var(&it, x_var)); + test_uint(e1, ecs_iter_get_var(&it, x_var)); test_bool(true, ecs_query_next(&it)); test_uint(0, it.count); - test_uint(RangedUnit, ecs_field_id(&it, 0)); - test_uint(e6, ecs_field_src(&it, 0)); + test_uint(Wizard, ecs_field_id(&it, 0)); + test_uint(e2, ecs_field_src(&it, 0)); test_bool(true, ecs_field_is_set(&it, 0)); - test_uint(e6, ecs_iter_get_var(&it, x_var)); + test_uint(e2, ecs_iter_get_var(&it, x_var)); test_bool(true, ecs_query_next(&it)); test_uint(0, it.count); @@ -734,31 +741,31 @@ void ComponentInheritance_1_var_0_lvl(void) { test_bool(true, ecs_query_next(&it)); test_uint(0, it.count); - test_uint(Warlock, ecs_field_id(&it, 0)); - test_uint(e1, ecs_field_src(&it, 0)); + test_uint(Archer, ecs_field_id(&it, 0)); + test_uint(e4, ecs_field_src(&it, 0)); test_bool(true, ecs_field_is_set(&it, 0)); - test_uint(e1, ecs_iter_get_var(&it, x_var)); + test_uint(e4, ecs_iter_get_var(&it, x_var)); test_bool(true, ecs_query_next(&it)); test_uint(0, it.count); - test_uint(Archer, ecs_field_id(&it, 0)); - test_uint(e4, ecs_field_src(&it, 0)); + test_uint(MeleeUnit, ecs_field_id(&it, 0)); + test_uint(e5, ecs_field_src(&it, 0)); test_bool(true, ecs_field_is_set(&it, 0)); - test_uint(e4, ecs_iter_get_var(&it, x_var)); + test_uint(e5, ecs_iter_get_var(&it, x_var)); test_bool(true, ecs_query_next(&it)); test_uint(0, it.count); - test_uint(Wizard, ecs_field_id(&it, 0)); - test_uint(e2, ecs_field_src(&it, 0)); + test_uint(RangedUnit, ecs_field_id(&it, 0)); + test_uint(e6, ecs_field_src(&it, 0)); test_bool(true, ecs_field_is_set(&it, 0)); - test_uint(e2, ecs_iter_get_var(&it, x_var)); + test_uint(e6, ecs_iter_get_var(&it, x_var)); test_bool(true, ecs_query_next(&it)); test_uint(0, it.count); - test_uint(Warlock, ecs_field_id(&it, 0)); - test_uint(e1, ecs_field_src(&it, 0)); + test_uint(Unit, ecs_field_id(&it, 0)); + test_uint(e7, ecs_field_src(&it, 0)); test_bool(true, ecs_field_is_set(&it, 0)); - test_uint(e1, ecs_iter_get_var(&it, x_var)); + test_uint(e7, ecs_iter_get_var(&it, x_var)); test_bool(false, ecs_query_next(&it)); } @@ -782,6 +789,7 @@ void ComponentInheritance_1_var_1_lvl(void) { /* ecs_entity_t e7 = */ ecs_new_w(world, Unit); ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "MeleeUnit($x)" }); @@ -794,10 +802,10 @@ void ComponentInheritance_1_var_1_lvl(void) { ecs_iter_t it = ecs_query_iter(world, r); test_bool(true, ecs_query_next(&it)); test_uint(0, it.count); - test_uint(MeleeUnit, ecs_field_id(&it, 0)); - test_uint(e5, ecs_field_src(&it, 0)); + test_uint(Warlock, ecs_field_id(&it, 0)); + test_uint(e1, ecs_field_src(&it, 0)); test_bool(true, ecs_field_is_set(&it, 0)); - test_uint(e5, ecs_iter_get_var(&it, x_var)); + test_uint(e1, ecs_iter_get_var(&it, x_var)); test_bool(true, ecs_query_next(&it)); test_uint(0, it.count); @@ -808,10 +816,10 @@ void ComponentInheritance_1_var_1_lvl(void) { test_bool(true, ecs_query_next(&it)); test_uint(0, it.count); - test_uint(Warlock, ecs_field_id(&it, 0)); - test_uint(e1, ecs_field_src(&it, 0)); + test_uint(MeleeUnit, ecs_field_id(&it, 0)); + test_uint(e5, ecs_field_src(&it, 0)); test_bool(true, ecs_field_is_set(&it, 0)); - test_uint(e1, ecs_iter_get_var(&it, x_var)); + test_uint(e5, ecs_iter_get_var(&it, x_var)); test_bool(false, ecs_query_next(&it)); } @@ -835,6 +843,7 @@ void ComponentInheritance_1_var_2_lvl(void) { /* ecs_entity_t e7 = */ ecs_new_w(world, Unit); ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "Warrior($x)" }); @@ -847,15 +856,15 @@ void ComponentInheritance_1_var_2_lvl(void) { ecs_iter_t it = ecs_query_iter(world, r); test_bool(true, ecs_query_next(&it)); test_uint(0, it.count); - test_uint(Warrior, ecs_field_id(&it, 0)); + test_uint(Warlock, ecs_field_id(&it, 0)); test_bool(true, ecs_field_is_set(&it, 0)); - test_uint(e3, ecs_iter_get_var(&it, x_var)); + test_uint(e1, ecs_iter_get_var(&it, x_var)); test_bool(true, ecs_query_next(&it)); test_uint(0, it.count); - test_uint(Warlock, ecs_field_id(&it, 0)); + test_uint(Warrior, ecs_field_id(&it, 0)); test_bool(true, ecs_field_is_set(&it, 0)); - test_uint(e1, ecs_iter_get_var(&it, x_var)); + test_uint(e3, ecs_iter_get_var(&it, x_var)); test_bool(false, ecs_query_next(&it)); } @@ -879,6 +888,7 @@ void ComponentInheritance_1_var_3_lvl(void) { /* ecs_entity_t e7 = */ ecs_new_w(world, Unit); ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "Warlock($x)" }); @@ -933,6 +943,7 @@ void ComponentInheritance_1_var_0_lvl_written(void) { ecs_new_w(world, Warlock); ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "Tag($x), Unit($x)" }); @@ -953,16 +964,6 @@ void ComponentInheritance_1_var_0_lvl_written(void) { test_bool(true, ecs_field_is_set(&it, 1)); test_uint(e1, ecs_iter_get_var(&it, x_var)); - test_bool(true, ecs_query_next(&it)); - test_uint(0, it.count); - test_uint(Tag, ecs_field_id(&it, 0)); - test_uint(Warlock, ecs_field_id(&it, 1)); - test_uint(e1, ecs_field_src(&it, 0)); - test_uint(e1, ecs_field_src(&it, 1)); - test_bool(true, ecs_field_is_set(&it, 0)); - test_bool(true, ecs_field_is_set(&it, 1)); - test_uint(e1, ecs_iter_get_var(&it, x_var)); - test_bool(true, ecs_query_next(&it)); test_uint(0, it.count); test_uint(Tag, ecs_field_id(&it, 0)); @@ -1061,6 +1062,7 @@ void ComponentInheritance_1_var_1_lvl_written(void) { ecs_new_w(world, Warlock); ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "Tag($x), MeleeUnit($x)" }); @@ -1139,6 +1141,7 @@ void ComponentInheritance_1_var_2_lvl_written(void) { ecs_new_w(world, Warlock); ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "Tag($x), Warrior($x)" }); @@ -1207,6 +1210,7 @@ void ComponentInheritance_1_var_3_lvl_written(void) { ecs_new_w(world, Warlock); ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "Tag($x), Warlock($x)" }); @@ -1235,7 +1239,7 @@ void ComponentInheritance_1_var_3_lvl_written(void) { ecs_fini(world); } -void ComponentInheritance_1_ent_1_lvl_self(void) { +void ComponentInheritance_1_fixed_1_lvl_self(void) { ecs_world_t *world = ecs_mini(); populate_facts(world); @@ -1244,6 +1248,7 @@ void ComponentInheritance_1_ent_1_lvl_self(void) { ecs_add(world, e1, Warlock); ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "Warrior(e1|self)" }); @@ -1279,6 +1284,7 @@ void ComponentInheritance_1_this_1_lvl_self(void) { /* ecs_entity_t e7 = */ ecs_new_w(world, Unit); ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "MeleeUnit(self)" }); @@ -1288,10 +1294,10 @@ void ComponentInheritance_1_this_1_lvl_self(void) { ecs_iter_t it = ecs_query_iter(world, r); test_bool(true, ecs_query_next(&it)); test_uint(1, it.count); - test_uint(MeleeUnit, ecs_field_id(&it, 0)); + test_uint(Warlock, ecs_field_id(&it, 0)); test_uint(0, ecs_field_src(&it, 0)); test_bool(true, ecs_field_is_set(&it, 0)); - test_uint(e5, it.entities[0]); + test_uint(e1, it.entities[0]); test_bool(true, ecs_query_next(&it)); test_uint(1, it.count); @@ -1302,10 +1308,10 @@ void ComponentInheritance_1_this_1_lvl_self(void) { test_bool(true, ecs_query_next(&it)); test_uint(1, it.count); - test_uint(Warlock, ecs_field_id(&it, 0)); + test_uint(MeleeUnit, ecs_field_id(&it, 0)); test_uint(0, ecs_field_src(&it, 0)); test_bool(true, ecs_field_is_set(&it, 0)); - test_uint(e1, it.entities[0]); + test_uint(e5, it.entities[0]); test_bool(false, ecs_query_next(&it)); } @@ -1345,6 +1351,7 @@ void ComponentInheritance_1_this_1_lvl_written_self(void) { ecs_new_w(world, Warlock); ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "Tag(self), MeleeUnit(self)" }); @@ -1404,6 +1411,7 @@ void ComponentInheritance_1_var_1_lvl_self(void) { /* ecs_entity_t e7 = */ ecs_new_w(world, Unit); ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "MeleeUnit($x|self)" }); @@ -1416,10 +1424,10 @@ void ComponentInheritance_1_var_1_lvl_self(void) { ecs_iter_t it = ecs_query_iter(world, r); test_bool(true, ecs_query_next(&it)); test_uint(0, it.count); - test_uint(MeleeUnit, ecs_field_id(&it, 0)); - test_uint(e5, ecs_field_src(&it, 0)); + test_uint(Warlock, ecs_field_id(&it, 0)); + test_uint(e1, ecs_field_src(&it, 0)); test_bool(true, ecs_field_is_set(&it, 0)); - test_uint(e5, ecs_iter_get_var(&it, x_var)); + test_uint(e1, ecs_iter_get_var(&it, x_var)); test_bool(true, ecs_query_next(&it)); test_uint(0, it.count); @@ -1430,10 +1438,10 @@ void ComponentInheritance_1_var_1_lvl_self(void) { test_bool(true, ecs_query_next(&it)); test_uint(0, it.count); - test_uint(Warlock, ecs_field_id(&it, 0)); - test_uint(e1, ecs_field_src(&it, 0)); + test_uint(MeleeUnit, ecs_field_id(&it, 0)); + test_uint(e5, ecs_field_src(&it, 0)); test_bool(true, ecs_field_is_set(&it, 0)); - test_uint(e1, ecs_iter_get_var(&it, x_var)); + test_uint(e5, ecs_iter_get_var(&it, x_var)); test_bool(false, ecs_query_next(&it)); } @@ -1473,6 +1481,7 @@ void ComponentInheritance_1_var_1_lvl_written_self(void) { ecs_new_w(world, Warlock); ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "Tag($x), MeleeUnit($x)" }); @@ -1521,7 +1530,7 @@ void ComponentInheritance_1_var_1_lvl_written_self(void) { ecs_fini(world); } -void ComponentInheritance_1_ent_src_not(void) { +void ComponentInheritance_1_fixed_src_not(void) { ecs_world_t *world = ecs_mini(); populate_facts(world); @@ -1530,7 +1539,8 @@ void ComponentInheritance_1_ent_src_not(void) { ecs_add(world, e1, Warrior); { - ecs_query_t *r = ecs_query(world, { .expr = "!Unit(e1)" }); + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "!Unit(e1)" }); test_assert(r != NULL); { @@ -1542,7 +1552,8 @@ void ComponentInheritance_1_ent_src_not(void) { } { - ecs_query_t *r = ecs_query(world, { .expr = "!MeleeUnit(e1)" }); + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "!MeleeUnit(e1)" }); test_assert(r != NULL); { @@ -1554,7 +1565,8 @@ void ComponentInheritance_1_ent_src_not(void) { } { - ecs_query_t *r = ecs_query(world, { .expr = "!Warrior(e1)" }); + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "!Warrior(e1)" }); test_assert(r != NULL); { @@ -1566,7 +1578,8 @@ void ComponentInheritance_1_ent_src_not(void) { } { - ecs_query_t *r = ecs_query(world, { .expr = "!Warlock(e1)" }); + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "!Warlock(e1)" }); test_assert(r != NULL); { @@ -1583,7 +1596,8 @@ void ComponentInheritance_1_ent_src_not(void) { } { - ecs_query_t *r = ecs_query(world, { .expr = "!RangedUnit(e1)" }); + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "!RangedUnit(e1)" }); test_assert(r != NULL); { @@ -1600,7 +1614,8 @@ void ComponentInheritance_1_ent_src_not(void) { } { - ecs_query_t *r = ecs_query(world, { .expr = "!Archer(e1)" }); + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "!Archer(e1)" }); test_assert(r != NULL); { @@ -1617,7 +1632,8 @@ void ComponentInheritance_1_ent_src_not(void) { } { - ecs_query_t *r = ecs_query(world, { .expr = "!Wizard(e1)" }); + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "!Wizard(e1)" }); test_assert(r != NULL); { @@ -1660,7 +1676,8 @@ void ComponentInheritance_1_this_src_not(void) { ecs_add(world, e7, Warlock); { - ecs_query_t *r = ecs_query(world, { .expr = "!Unit($this), Tag($this)" }); + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "!Unit($this), Tag($this)" }); test_assert(r != NULL); { @@ -1672,7 +1689,8 @@ void ComponentInheritance_1_this_src_not(void) { } { - ecs_query_t *r = ecs_query(world, { .expr = "!MeleeUnit($this), Tag($this)" }); + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "!MeleeUnit($this), Tag($this)" }); test_assert(r != NULL); { @@ -1724,7 +1742,8 @@ void ComponentInheritance_1_this_src_not(void) { } { - ecs_query_t *r = ecs_query(world, { .expr = "!Warrior($this), Tag($this)" }); + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "!Warrior($this), Tag($this)" }); test_assert(r != NULL); { @@ -1786,7 +1805,8 @@ void ComponentInheritance_1_this_src_not(void) { } { - ecs_query_t *r = ecs_query(world, { .expr = "!Warlock($this), Tag($this)" }); + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "!Warlock($this), Tag($this)" }); test_assert(r != NULL); { @@ -1858,7 +1878,8 @@ void ComponentInheritance_1_this_src_not(void) { } { - ecs_query_t *r = ecs_query(world, { .expr = "!RangedUnit($this), Tag($this)" }); + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "!RangedUnit($this), Tag($this)" }); test_assert(r != NULL); { @@ -1900,7 +1921,8 @@ void ComponentInheritance_1_this_src_not(void) { } { - ecs_query_t *r = ecs_query(world, { .expr = "!Archer($this), Tag($this)" }); + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "!Archer($this), Tag($this)" }); test_assert(r != NULL); { @@ -1972,7 +1994,8 @@ void ComponentInheritance_1_this_src_not(void) { } { - ecs_query_t *r = ecs_query(world, { .expr = "!Wizard($this), Tag($this)" }); + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "!Wizard($this), Tag($this)" }); test_assert(r != NULL); { @@ -2060,7 +2083,8 @@ void ComponentInheritance_1_var_src_not(void) { ecs_add(world, e7, Warlock); { - ecs_query_t *r = ecs_query(world, { .expr = "!Unit($x), Tag($x)" }); + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "!Unit($x), Tag($x)" }); test_assert(r != NULL); int x_var = ecs_query_find_var(r, "x"); @@ -2075,7 +2099,8 @@ void ComponentInheritance_1_var_src_not(void) { } { - ecs_query_t *r = ecs_query(world, { .expr = "!MeleeUnit($x), Tag($x)" }); + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "!MeleeUnit($x), Tag($x)" }); test_assert(r != NULL); int x_var = ecs_query_find_var(r, "x"); @@ -2130,7 +2155,8 @@ void ComponentInheritance_1_var_src_not(void) { } { - ecs_query_t *r = ecs_query(world, { .expr = "!Warrior($x), Tag($x)" }); + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "!Warrior($x), Tag($x)" }); test_assert(r != NULL); int x_var = ecs_query_find_var(r, "x"); @@ -2195,7 +2221,8 @@ void ComponentInheritance_1_var_src_not(void) { } { - ecs_query_t *r = ecs_query(world, { .expr = "!Warlock($x), Tag($x)" }); + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "!Warlock($x), Tag($x)" }); test_assert(r != NULL); int x_var = ecs_query_find_var(r, "x"); @@ -2270,7 +2297,8 @@ void ComponentInheritance_1_var_src_not(void) { } { - ecs_query_t *r = ecs_query(world, { .expr = "!RangedUnit($x), Tag($x)" }); + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "!RangedUnit($x), Tag($x)" }); test_assert(r != NULL); int x_var = ecs_query_find_var(r, "x"); @@ -2315,7 +2343,8 @@ void ComponentInheritance_1_var_src_not(void) { } { - ecs_query_t *r = ecs_query(world, { .expr = "!Archer($x), Tag($x)" }); + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "!Archer($x), Tag($x)" }); test_assert(r != NULL); int x_var = ecs_query_find_var(r, "x"); @@ -2390,7 +2419,8 @@ void ComponentInheritance_1_var_src_not(void) { } { - ecs_query_t *r = ecs_query(world, { .expr = "!Wizard($x), Tag($x)" }); + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "!Wizard($x), Tag($x)" }); test_assert(r != NULL); int x_var = ecs_query_find_var(r, "x"); @@ -2481,7 +2511,8 @@ void ComponentInheritance_1_this_src_not_written(void) { ecs_add(world, e7, Warlock); { - ecs_query_t *r = ecs_query(world, { .expr = "Tag($this), !Unit($this)" }); + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "Tag($this), !Unit($this)" }); test_assert(r != NULL); { @@ -2493,7 +2524,8 @@ void ComponentInheritance_1_this_src_not_written(void) { } { - ecs_query_t *r = ecs_query(world, { .expr = "Tag($this), !MeleeUnit($this)" }); + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "Tag($this), !MeleeUnit($this)" }); test_assert(r != NULL); { @@ -2546,7 +2578,8 @@ void ComponentInheritance_1_this_src_not_written(void) { } { - ecs_query_t *r = ecs_query(world, { .expr = "Tag($this), !Warrior($this)" }); + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "Tag($this), !Warrior($this)" }); test_assert(r != NULL); { @@ -2608,7 +2641,8 @@ void ComponentInheritance_1_this_src_not_written(void) { } { - ecs_query_t *r = ecs_query(world, { .expr = "Tag($this), !Warlock($this)" }); + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "Tag($this), !Warlock($this)" }); test_assert(r != NULL); { @@ -2680,7 +2714,8 @@ void ComponentInheritance_1_this_src_not_written(void) { } { - ecs_query_t *r = ecs_query(world, { .expr = "Tag($this), !RangedUnit($this)" }); + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "Tag($this), !RangedUnit($this)" }); test_assert(r != NULL); { @@ -2722,7 +2757,8 @@ void ComponentInheritance_1_this_src_not_written(void) { } { - ecs_query_t *r = ecs_query(world, { .expr = "Tag($this), !Archer($this)" }); + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "Tag($this), !Archer($this)" }); test_assert(r != NULL); { @@ -2794,7 +2830,8 @@ void ComponentInheritance_1_this_src_not_written(void) { } { - ecs_query_t *r = ecs_query(world, { .expr = "Tag($this), !Wizard($this)" }); + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "Tag($this), !Wizard($this)" }); test_assert(r != NULL); { @@ -2882,7 +2919,8 @@ void ComponentInheritance_1_var_src_not_written(void) { ecs_add(world, e7, Warlock); { - ecs_query_t *r = ecs_query(world, { .expr = "Tag($x), !Unit($x)" }); + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "Tag($x), !Unit($x)" }); test_assert(r != NULL); int x_var = ecs_query_find_var(r, "x"); @@ -2897,7 +2935,8 @@ void ComponentInheritance_1_var_src_not_written(void) { } { - ecs_query_t *r = ecs_query(world, { .expr = "Tag($x), !MeleeUnit($x)" }); + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "Tag($x), !MeleeUnit($x)" }); test_assert(r != NULL); int x_var = ecs_query_find_var(r, "x"); @@ -2952,7 +2991,8 @@ void ComponentInheritance_1_var_src_not_written(void) { } { - ecs_query_t *r = ecs_query(world, { .expr = "Tag($x), !Warrior($x)" }); + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "Tag($x), !Warrior($x)" }); test_assert(r != NULL); int x_var = ecs_query_find_var(r, "x"); @@ -3017,7 +3057,8 @@ void ComponentInheritance_1_var_src_not_written(void) { } { - ecs_query_t *r = ecs_query(world, { .expr = "Tag($x), !Warlock($x)" }); + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "Tag($x), !Warlock($x)" }); test_assert(r != NULL); int x_var = ecs_query_find_var(r, "x"); @@ -3092,7 +3133,8 @@ void ComponentInheritance_1_var_src_not_written(void) { } { - ecs_query_t *r = ecs_query(world, { .expr = "Tag($x), !RangedUnit($x)" }); + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "Tag($x), !RangedUnit($x)" }); test_assert(r != NULL); int x_var = ecs_query_find_var(r, "x"); @@ -3137,7 +3179,8 @@ void ComponentInheritance_1_var_src_not_written(void) { } { - ecs_query_t *r = ecs_query(world, { .expr = "Tag($x), !Archer($x)" }); + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "Tag($x), !Archer($x)" }); test_assert(r != NULL); int x_var = ecs_query_find_var(r, "x"); @@ -3212,7 +3255,8 @@ void ComponentInheritance_1_var_src_not_written(void) { } { - ecs_query_t *r = ecs_query(world, { .expr = "Tag($x), !Wizard($x)" }); + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "Tag($x), !Wizard($x)" }); test_assert(r != NULL); int x_var = ecs_query_find_var(r, "x"); @@ -3291,6 +3335,7 @@ void ComponentInheritance_first_self(void) { { ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "Warrior|self(e1)" }); @@ -3298,6 +3343,12 @@ void ComponentInheritance_first_self(void) { { ecs_iter_t it = ecs_query_iter(world, r); + test_bool(true, ecs_query_next(&it)); + test_uint(0, it.count); + test_uint(Warlock, ecs_field_id(&it, 0)); + test_bool(true, ecs_field_is_set(&it, 0)); + test_uint(e1, ecs_field_src(&it, 0)); + test_bool(false, ecs_query_next(&it)); } @@ -3306,6 +3357,7 @@ void ComponentInheritance_first_self(void) { { ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "Warlock|self(e1)" }); @@ -3327,6 +3379,7 @@ void ComponentInheritance_first_self(void) { { ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "Warrior|self(e2)" }); @@ -3356,6 +3409,7 @@ void ComponentInheritance_inheritable_trait(void) { test_assert(ecs_has_id(world, Unit, EcsInheritable)); ecs_query_t *q = ecs_query(world, { + .cache_kind = cache_kind, .expr = "Unit" }); @@ -3405,6 +3459,7 @@ void ComponentInheritance_query_before_isa_relationship_1st_term(void) { ECS_TAG(world, Unit); ecs_query_t *q = ecs_query(world, { + .cache_kind = cache_kind, .expr = "Unit" }); @@ -3424,6 +3479,7 @@ void ComponentInheritance_query_before_isa_relationship_2nd_term(void) { ECS_TAG(world, Foo); ecs_query_t *q = ecs_query(world, { + .cache_kind = cache_kind, .expr = "Foo, Unit" }); @@ -3441,6 +3497,7 @@ void ComponentInheritance_query_before_isa_relationship_subtype(void) { ECS_ENTITY(world, MeleeUnit, (IsA, Unit)); ecs_query_t *q = ecs_query(world, { + .cache_kind = cache_kind, .expr = "MeleeUnit" }); @@ -3481,6 +3538,7 @@ void ComponentInheritance_query_before_isa_relationship_0_src(void) { ECS_TAG(world, Unit); ecs_query_t *q = ecs_query(world, { + .cache_kind = cache_kind, .expr = "Foo, Unit()" }); @@ -3507,3 +3565,1254 @@ void ComponentInheritance_query_before_isa_relationship_0_src(void) { ecs_fini(world); } + +void ComponentInheritance_populated_remove_isa_2_lvl(void) { + install_test_abort(); + + ecs_world_t *world = ecs_mini(); + + ecs_entity_t Unit = ecs_entity(world, { .name = "Unit" }); + ecs_entity_t Warrior = ecs_entity(world, { .name = "Warrior" }); + ecs_add_pair(world, Warrior, EcsIsA, Unit); + + ecs_entity_t e1 = ecs_new_w_id(world, Warrior); + ecs_entity_t e2 = ecs_new_w_id(world, Unit); + + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "Unit($this)" }); + test_assert(r != NULL); + + { + ecs_iter_t it = ecs_query_iter(world, r); + test_bool(true, ecs_query_next(&it)); + test_uint(1, it.count); + test_uint(Warrior, ecs_field_id(&it, 0)); + test_uint(e1, it.entities[0]); + + test_bool(true, ecs_query_next(&it)); + test_uint(1, it.count); + test_uint(Unit, ecs_field_id(&it, 0)); + test_uint(e2, it.entities[0]); + + test_bool(false, ecs_query_next(&it)); + } + + test_expect_abort(); + ecs_remove_pair(world, Warrior, EcsIsA, Unit); +} + +void ComponentInheritance_populated_remove_isa_3_lvl(void) { + install_test_abort(); + + ecs_world_t *world = ecs_mini(); + + ecs_entity_t Unit = ecs_entity(world, { .name = "Unit" }); + ecs_entity_t Melee = ecs_entity(world, { .name = "Melee" }); + ecs_entity_t Warrior = ecs_entity(world, { .name = "Warrior" }); + ecs_add_pair(world, Melee, EcsIsA, Unit); + ecs_add_pair(world, Warrior, EcsIsA, Melee); + + ecs_entity_t e1 = ecs_new_w_id(world, Warrior); + ecs_entity_t e2 = ecs_new_w_id(world, Melee); + ecs_entity_t e3 = ecs_new_w_id(world, Unit); + + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "Unit($this)" }); + test_assert(r != NULL); + + { + ecs_iter_t it = ecs_query_iter(world, r); + test_bool(true, ecs_query_next(&it)); + test_uint(Warrior, ecs_field_id(&it, 0)); + test_uint(e1, it.entities[0]); + test_bool(true, ecs_query_next(&it)); + test_uint(Melee, ecs_field_id(&it, 0)); + test_uint(e2, it.entities[0]); + test_bool(true, ecs_query_next(&it)); + test_uint(Unit, ecs_field_id(&it, 0)); + test_uint(e3, it.entities[0]); + test_bool(false, ecs_query_next(&it)); + } + + test_expect_abort(); + ecs_remove_pair(world, Melee, EcsIsA, Unit); +} + +void ComponentInheritance_populated_match_entities_added_after(void) { + ecs_world_t *world = ecs_mini(); + + ecs_entity_t Unit = ecs_entity(world, { .name = "Unit" }); + ecs_entity_t Warrior = ecs_entity(world, { .name = "Warrior" }); + ecs_add_pair(world, Warrior, EcsIsA, Unit); + + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "Unit($this)" }); + test_assert(r != NULL); + + { + ecs_iter_t it = ecs_query_iter(world, r); + test_bool(false, ecs_query_next(&it)); + } + + ecs_entity_t e1 = ecs_new_w_id(world, Warrior); + ecs_entity_t e2 = ecs_new_w_id(world, Unit); + + { + ecs_iter_t it = ecs_query_iter(world, r); + test_bool(true, ecs_query_next(&it)); + test_uint(1, it.count); + test_uint(Warrior, ecs_field_id(&it, 0)); + test_uint(e1, it.entities[0]); + + test_bool(true, ecs_query_next(&it)); + test_uint(1, it.count); + test_uint(Unit, ecs_field_id(&it, 0)); + test_uint(e2, it.entities[0]); + + test_bool(false, ecs_query_next(&it)); + } + + ecs_query_fini(r); + + ecs_fini(world); +} + +void ComponentInheritance_populated_remove_isa_var(void) { + install_test_abort(); + + ecs_world_t *world = ecs_mini(); + + ecs_entity_t Unit = ecs_entity(world, { .name = "Unit" }); + ecs_entity_t Warrior = ecs_entity(world, { .name = "Warrior" }); + ecs_add_pair(world, Warrior, EcsIsA, Unit); + + ecs_entity_t e1 = ecs_new_w_id(world, Warrior); + + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "Unit($x)" }); + test_assert(r != NULL); + + int x_var = ecs_query_find_var(r, "x"); + test_assert(x_var != -1); + + { + ecs_iter_t it = ecs_query_iter(world, r); + test_bool(true, ecs_query_next(&it)); + test_uint(Warrior, ecs_field_id(&it, 0)); + test_uint(e1, ecs_iter_get_var(&it, x_var)); + test_bool(false, ecs_query_next(&it)); + } + + test_expect_abort(); + ecs_remove_pair(world, Warrior, EcsIsA, Unit); +} + +void ComponentInheritance_optional_inherited(void) { + ecs_world_t *world = ecs_mini(); + + ecs_entity_t Unit = ecs_entity(world, { .name = "Unit" }); + ecs_entity_t Warrior = ecs_entity(world, { .name = "Warrior" }); + ecs_entity_t Tag = ecs_entity(world, { .name = "Tag" }); + ecs_add_pair(world, Warrior, EcsIsA, Unit); + + ecs_entity_t e1 = ecs_entity(world, { .name = "e1" }); + ecs_add_id(world, e1, Tag); + ecs_entity_t e2 = ecs_entity(world, { .name = "e2" }); + ecs_add_id(world, e2, Tag); + ecs_add_id(world, e2, Warrior); + + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "Tag, ?Unit" }); + test_assert(r != NULL); + + ecs_iter_t it = ecs_query_iter(world, r); + + test_bool(true, ecs_query_next(&it)); + test_uint(1, it.count); + test_uint(e1, it.entities[0]); + test_uint(Tag, ecs_field_id(&it, 0)); + test_bool(true, ecs_field_is_set(&it, 0)); + test_uint(Unit, ecs_field_id(&it, 1)); + test_bool(false, ecs_field_is_set(&it, 1)); + + test_bool(true, ecs_query_next(&it)); + test_uint(1, it.count); + test_uint(e2, it.entities[0]); + test_uint(Tag, ecs_field_id(&it, 0)); + test_bool(true, ecs_field_is_set(&it, 0)); + test_uint(Warrior, ecs_field_id(&it, 1)); + test_bool(true, ecs_field_is_set(&it, 1)); + + test_bool(false, ecs_query_next(&it)); + + ecs_query_fini(r); + ecs_fini(world); +} + +void ComponentInheritance_or_inherited(void) { + ecs_world_t *world = ecs_mini(); + + ecs_entity_t Unit = ecs_entity(world, { .name = "Unit" }); + ecs_entity_t Warrior = ecs_entity(world, { .name = "Warrior" }); + ecs_entity_t Other = ecs_entity(world, { .name = "Other" }); + ecs_add_pair(world, Warrior, EcsIsA, Unit); + + ecs_entity_t e1 = ecs_new_w_id(world, Warrior); + ecs_entity_t e2 = ecs_new_w_id(world, Other); + ecs_entity_t e3 = ecs_new_w_id(world, Unit); + + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "Unit || Other" }); + test_assert(r != NULL); + + int32_t count = 0; + bool found_e1 = false, found_e2 = false, found_e3 = false; + ecs_iter_t it = ecs_query_iter(world, r); + while (ecs_query_next(&it)) { + int32_t i; + for (i = 0; i < it.count; i ++) { + ecs_entity_t e = it.entities[i]; + count ++; + if (e == e1) { + found_e1 = true; + test_uint(Warrior, ecs_field_id(&it, 0)); + } + if (e == e2) { + found_e2 = true; + test_uint(Other, ecs_field_id(&it, 0)); + } + if (e == e3) { + found_e3 = true; + test_uint(Unit, ecs_field_id(&it, 0)); + } + } + } + + test_int(count, 3); + test_bool(found_e1, true); + test_bool(found_e2, true); + test_bool(found_e3, true); + + ecs_query_fini(r); + ecs_fini(world); +} + +void ComponentInheritance_multiple_inherited_terms(void) { + ecs_world_t *world = ecs_mini(); + + ecs_entity_t Unit = ecs_entity(world, { .name = "Unit" }); + ecs_entity_t Caster = ecs_entity(world, { .name = "Caster" }); + ecs_entity_t Warrior = ecs_entity(world, { .name = "Warrior" }); + ecs_entity_t Mage = ecs_entity(world, { .name = "Mage" }); + ecs_add_pair(world, Warrior, EcsIsA, Unit); + ecs_add_pair(world, Mage, EcsIsA, Caster); + + ecs_entity_t e1 = ecs_entity(world, { .name = "e1" }); + ecs_add_id(world, e1, Warrior); + ecs_add_id(world, e1, Mage); + + ecs_entity_t e2 = ecs_entity(world, { .name = "e2" }); + ecs_add_id(world, e2, Warrior); + + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "Unit, Caster" }); + test_assert(r != NULL); + + ecs_iter_t it = ecs_query_iter(world, r); + test_bool(true, ecs_query_next(&it)); + test_uint(1, it.count); + test_uint(e1, it.entities[0]); + test_uint(Warrior, ecs_field_id(&it, 0)); + test_uint(Mage, ecs_field_id(&it, 1)); + test_bool(true, ecs_field_is_set(&it, 0)); + test_bool(true, ecs_field_is_set(&it, 1)); + + test_bool(false, ecs_query_next(&it)); + + ecs_query_fini(r); + ecs_fini(world); +} + +void ComponentInheritance_up_traversal_inherited(void) { + ecs_world_t *world = ecs_mini(); + + ecs_entity_t Unit = ecs_entity(world, { .name = "Unit" }); + ecs_add_pair(world, Unit, EcsOnInstantiate, EcsInherit); + ecs_entity_t Warrior = ecs_entity(world, { .name = "Warrior" }); + ecs_add_pair(world, Warrior, EcsOnInstantiate, EcsInherit); + ecs_add_pair(world, Warrior, EcsIsA, Unit); + + ecs_entity_t prefab = ecs_entity(world, { .name = "prefab" }); + ecs_add_id(world, prefab, EcsPrefab); + ecs_add_id(world, prefab, Warrior); + + ecs_entity_t inst = ecs_entity(world, { .name = "inst" }); + ecs_add_pair(world, inst, EcsIsA, prefab); + + test_bool(ecs_owns_id(world, inst, Warrior), false); + test_bool(ecs_has_id(world, inst, Warrior), true); + test_bool(ecs_has_id(world, inst, Unit), true); + + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, + .terms[0] = { .id = Unit, .src.id = EcsSelf|EcsUp, .trav = EcsIsA } + }); + test_assert(r != NULL); + + ecs_iter_t it = ecs_query_iter(world, r); + test_bool(true, ecs_query_next(&it)); + test_uint(1, it.count); + test_uint(inst, it.entities[0]); + test_uint(Warrior, ecs_field_id(&it, 0)); + test_uint(prefab, ecs_field_src(&it, 0)); + + test_bool(false, ecs_query_next(&it)); + + ecs_query_fini(r); + ecs_fini(world); +} + +void ComponentInheritance_multi_derived_this(void) { + ecs_world_t *world = ecs_mini(); + + populate_facts(world); + + ecs_entity_t em = ecs_new_w(world, Warrior); + ecs_add(world, em, Wizard); + + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, + .expr = "Unit($this)" + }); + + test_assert(r != NULL); + + { + ecs_iter_t it = ecs_query_iter(world, r); + test_bool(true, ecs_query_next(&it)); + test_uint(1, it.count); + test_uint(em, it.entities[0]); + test_uint(Warrior, ecs_field_id(&it, 0)); + test_uint(0, ecs_field_src(&it, 0)); + test_bool(true, ecs_field_is_set(&it, 0)); + + test_bool(true, ecs_query_next(&it)); + test_uint(1, it.count); + test_uint(em, it.entities[0]); + test_uint(Wizard, ecs_field_id(&it, 0)); + test_uint(0, ecs_field_src(&it, 0)); + test_bool(true, ecs_field_is_set(&it, 0)); + + test_bool(false, ecs_query_next(&it)); + } + + ecs_query_fini(r); + + ecs_fini(world); +} + +void ComponentInheritance_multi_derived_this_written(void) { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Tag); + + populate_facts(world); + + ecs_entity_t em = ecs_new_w(world, Tag); + ecs_add(world, em, Warrior); + ecs_add(world, em, Wizard); + + ecs_new_w(world, Warrior); + ecs_new_w(world, Wizard); + + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, + .expr = "Tag($this), Unit($this)" + }); + + test_assert(r != NULL); + + { + ecs_iter_t it = ecs_query_iter(world, r); + test_bool(true, ecs_query_next(&it)); + test_uint(1, it.count); + test_uint(em, it.entities[0]); + test_uint(Tag, ecs_field_id(&it, 0)); + test_uint(Warrior, ecs_field_id(&it, 1)); + + test_bool(true, ecs_query_next(&it)); + test_uint(1, it.count); + test_uint(em, it.entities[0]); + test_uint(Tag, ecs_field_id(&it, 0)); + test_uint(Wizard, ecs_field_id(&it, 1)); + + test_bool(false, ecs_query_next(&it)); + } + + ecs_query_fini(r); + + ecs_fini(world); +} + +void ComponentInheritance_multi_derived_fixed(void) { + ecs_world_t *world = ecs_mini(); + + populate_facts(world); + + ecs_entity_t em = ecs_entity(world, { .name = "em" }); + ecs_add(world, em, Warrior); + ecs_add(world, em, Wizard); + + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, + .expr = "Unit(em)" + }); + + test_assert(r != NULL); + + { + ecs_iter_t it = ecs_query_iter(world, r); + test_bool(true, ecs_query_next(&it)); + test_uint(0, it.count); + test_uint(Warrior, ecs_field_id(&it, 0)); + test_uint(em, ecs_field_src(&it, 0)); + test_bool(true, ecs_field_is_set(&it, 0)); + + test_bool(true, ecs_query_next(&it)); + test_uint(0, it.count); + test_uint(Wizard, ecs_field_id(&it, 0)); + test_uint(em, ecs_field_src(&it, 0)); + test_bool(true, ecs_field_is_set(&it, 0)); + + test_bool(false, ecs_query_next(&it)); + } + + ecs_query_fini(r); + + ecs_fini(world); +} + +void ComponentInheritance_multi_derived_var(void) { + ecs_world_t *world = ecs_mini(); + + populate_facts(world); + + ecs_entity_t em = ecs_new_w(world, Warrior); + ecs_add(world, em, Wizard); + + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, + .expr = "Unit($x)" + }); + + test_assert(r != NULL); + + int x_var = ecs_query_find_var(r, "x"); + test_assert(x_var != -1); + + { + ecs_iter_t it = ecs_query_iter(world, r); + test_bool(true, ecs_query_next(&it)); + test_uint(0, it.count); + test_uint(Warrior, ecs_field_id(&it, 0)); + test_uint(em, ecs_field_src(&it, 0)); + test_uint(em, ecs_iter_get_var(&it, x_var)); + + test_bool(true, ecs_query_next(&it)); + test_uint(0, it.count); + test_uint(Wizard, ecs_field_id(&it, 0)); + test_uint(em, ecs_field_src(&it, 0)); + test_uint(em, ecs_iter_get_var(&it, x_var)); + + test_bool(false, ecs_query_next(&it)); + } + + ecs_query_fini(r); + + ecs_fini(world); +} + +void ComponentInheritance_multi_derived_mid_level_once(void) { + ecs_world_t *world = ecs_mini(); + + populate_facts(world); + + ecs_entity_t em = ecs_new_w(world, Warrior); + ecs_add(world, em, Wizard); + + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, + .expr = "MeleeUnit($this)" + }); + + test_assert(r != NULL); + + { + ecs_iter_t it = ecs_query_iter(world, r); + test_bool(true, ecs_query_next(&it)); + test_uint(1, it.count); + test_uint(em, it.entities[0]); + test_uint(Warrior, ecs_field_id(&it, 0)); + + test_bool(false, ecs_query_next(&it)); + } + + ecs_query_fini(r); + + ecs_fini(world); +} + +void ComponentInheritance_multi_derived_count_results(void) { + ecs_world_t *world = ecs_mini(); + + populate_facts(world); + + ecs_entity_t e_single = ecs_new_w(world, Warrior); + ecs_entity_t em = ecs_new_w(world, Warrior); + ecs_add(world, em, Wizard); + + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, + .expr = "Unit($this)" + }); + + test_assert(r != NULL); + + int32_t single_matches = 0, multi_matches = 0; + + { + ecs_iter_t it = ecs_query_iter(world, r); + while (ecs_query_next(&it)) { + int32_t i; + for (i = 0; i < it.count; i ++) { + if (it.entities[i] == e_single) single_matches ++; + if (it.entities[i] == em) multi_matches ++; + } + } + } + + test_int(single_matches, 1); + test_int(multi_matches, 2); + + ecs_query_fini(r); + + ecs_fini(world); +} + + +void ComponentInheritance_1_this_pair(void) { + ecs_world_t *world = ecs_mini(); + + ecs_entity_t Likes = ecs_entity(world, { .name = "Likes" }); + ecs_entity_t Loves = ecs_entity(world, { .name = "Loves" }); + ecs_entity_t Apples = ecs_entity(world, { .name = "Apples" }); + ecs_add_pair(world, Loves, EcsIsA, Likes); + + ecs_entity_t e = ecs_new(world); + ecs_add_pair(world, e, Loves, Apples); + + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, + .expr = "(Likes, Apples)" + }); + + test_assert(r != NULL); + + { + ecs_iter_t it = ecs_query_iter(world, r); + test_bool(true, ecs_query_next(&it)); + test_uint(1, it.count); + test_uint(e, it.entities[0]); + test_uint(ecs_pair(Loves, Apples), ecs_field_id(&it, 0)); + + test_bool(false, ecs_query_next(&it)); + } + + ecs_query_fini(r); + + ecs_fini(world); +} + +void ComponentInheritance_pair_3_lvl(void) { + ecs_world_t *world = ecs_mini(); + + ecs_entity_t Likes = ecs_entity(world, { .name = "Likes" }); + ecs_entity_t Loves = ecs_entity(world, { .name = "Loves" }); + ecs_entity_t Worships = ecs_entity(world, { .name = "Worships" }); + ecs_entity_t Apples = ecs_entity(world, { .name = "Apples" }); + ecs_add_pair(world, Loves, EcsIsA, Likes); + ecs_add_pair(world, Worships, EcsIsA, Loves); + + ecs_entity_t e = ecs_new(world); + ecs_add_pair(world, e, Worships, Apples); + + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, + .expr = "(Likes, Apples)" + }); + + test_assert(r != NULL); + + { + ecs_iter_t it = ecs_query_iter(world, r); + test_bool(true, ecs_query_next(&it)); + test_uint(1, it.count); + test_uint(e, it.entities[0]); + test_uint(ecs_pair(Worships, Apples), ecs_field_id(&it, 0)); + + test_bool(false, ecs_query_next(&it)); + } + + ecs_query_fini(r); + + ecs_fini(world); +} + +void ComponentInheritance_pair_multi(void) { + ecs_world_t *world = ecs_mini(); + + ecs_entity_t Likes = ecs_entity(world, { .name = "Likes" }); + ecs_entity_t Loves = ecs_entity(world, { .name = "Loves" }); + ecs_entity_t Adores = ecs_entity(world, { .name = "Adores" }); + ecs_entity_t Apples = ecs_entity(world, { .name = "Apples" }); + ecs_add_pair(world, Loves, EcsIsA, Likes); + ecs_add_pair(world, Adores, EcsIsA, Likes); + + ecs_entity_t e = ecs_new(world); + ecs_add_pair(world, e, Loves, Apples); + ecs_add_pair(world, e, Adores, Apples); + + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, + .expr = "(Likes, Apples)" + }); + + test_assert(r != NULL); + + { + ecs_iter_t it = ecs_query_iter(world, r); + test_bool(true, ecs_query_next(&it)); + test_uint(1, it.count); + test_uint(e, it.entities[0]); + test_uint(ecs_pair(Loves, Apples), ecs_field_id(&it, 0)); + + test_bool(true, ecs_query_next(&it)); + test_uint(1, it.count); + test_uint(e, it.entities[0]); + test_uint(ecs_pair(Adores, Apples), ecs_field_id(&it, 0)); + + test_bool(false, ecs_query_next(&it)); + } + + ecs_query_fini(r); + + ecs_fini(world); +} + +void ComponentInheritance_pair_different_target(void) { + ecs_world_t *world = ecs_mini(); + + ecs_entity_t Likes = ecs_entity(world, { .name = "Likes" }); + ecs_entity_t Loves = ecs_entity(world, { .name = "Loves" }); + ecs_entity_t Apples = ecs_entity(world, { .name = "Apples" }); + ecs_entity_t Pears = ecs_entity(world, { .name = "Pears" }); + ecs_add_pair(world, Loves, EcsIsA, Likes); + + ecs_entity_t e1 = ecs_new(world); + ecs_add_pair(world, e1, Loves, Apples); + ecs_entity_t e2 = ecs_new(world); + ecs_add_pair(world, e2, Loves, Pears); + + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, + .expr = "(Likes, Apples)" + }); + + test_assert(r != NULL); + + { + ecs_iter_t it = ecs_query_iter(world, r); + test_bool(true, ecs_query_next(&it)); + test_uint(1, it.count); + test_uint(e1, it.entities[0]); + test_uint(ecs_pair(Loves, Apples), ecs_field_id(&it, 0)); + + test_bool(false, ecs_query_next(&it)); + } + + ecs_query_fini(r); + + ecs_fini(world); +} + +void ComponentInheritance_pair_wildcard(void) { + ecs_world_t *world = ecs_mini(); + + ecs_entity_t Likes = ecs_entity(world, { .name = "Likes" }); + ecs_entity_t Loves = ecs_entity(world, { .name = "Loves" }); + ecs_entity_t Apples = ecs_entity(world, { .name = "Apples" }); + ecs_entity_t Pears = ecs_entity(world, { .name = "Pears" }); + ecs_add_pair(world, Loves, EcsIsA, Likes); + + ecs_entity_t e = ecs_new(world); + ecs_add_pair(world, e, Loves, Apples); + ecs_add_pair(world, e, Loves, Pears); + + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, + .expr = "(Likes, *)" + }); + + test_assert(r != NULL); + + { + ecs_iter_t it = ecs_query_iter(world, r); + test_bool(true, ecs_query_next(&it)); + test_uint(1, it.count); + test_uint(e, it.entities[0]); + test_uint(ecs_pair(Loves, Apples), ecs_field_id(&it, 0)); + + test_bool(true, ecs_query_next(&it)); + test_uint(1, it.count); + test_uint(e, it.entities[0]); + test_uint(ecs_pair(Loves, Pears), ecs_field_id(&it, 0)); + + test_bool(false, ecs_query_next(&it)); + } + + ecs_query_fini(r); + + ecs_fini(world); +} + +void ComponentInheritance_pair_wildcard_multi_rel(void) { + ecs_world_t *world = ecs_mini(); + + ecs_entity_t Likes = ecs_entity(world, { .name = "Likes" }); + ecs_entity_t Loves = ecs_entity(world, { .name = "Loves" }); + ecs_entity_t Adores = ecs_entity(world, { .name = "Adores" }); + ecs_entity_t Apples = ecs_entity(world, { .name = "Apples" }); + ecs_entity_t Pears = ecs_entity(world, { .name = "Pears" }); + ecs_add_pair(world, Loves, EcsIsA, Likes); + ecs_add_pair(world, Adores, EcsIsA, Likes); + + ecs_entity_t e = ecs_new(world); + ecs_add_pair(world, e, Loves, Apples); + ecs_add_pair(world, e, Adores, Pears); + + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, + .expr = "(Likes, *)" + }); + + test_assert(r != NULL); + + int32_t matched = 0; + bool found_loves = false, found_adores = false; + { + ecs_iter_t it = ecs_query_iter(world, r); + while (ecs_query_next(&it)) { + int32_t i; + for (i = 0; i < it.count; i ++) { + test_uint(e, it.entities[i]); + matched ++; + if (ecs_field_id(&it, 0) == ecs_pair(Loves, Apples)) found_loves = true; + if (ecs_field_id(&it, 0) == ecs_pair(Adores, Pears)) found_adores = true; + } + } + } + + test_int(matched, 2); + test_bool(found_loves, true); + test_bool(found_adores, true); + + ecs_query_fini(r); + + ecs_fini(world); +} + +/* --- Data component (value) inheritance, non-pair --- */ + +void ComponentInheritance_1_fixed_component(void) { + ecs_world_t *world = ecs_mini(); + + ECS_COMPONENT(world, Weight); + ECS_COMPONENT(world, HeavyWeight); + ecs_add_pair(world, ecs_id(HeavyWeight), EcsIsA, ecs_id(Weight)); + + ecs_entity_t e1 = ecs_entity(world, { .name = "e1" }); + ecs_set(world, e1, HeavyWeight, {.value = 10, .bonus = 3}); + + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "Weight(e1)" }); + test_assert(r != NULL); + + ecs_iter_t it = ecs_query_iter(world, r); + test_bool(true, ecs_query_next(&it)); + test_uint(0, it.count); + test_uint(e1, ecs_field_src(&it, 0)); + test_uint(ecs_id(HeavyWeight), ecs_field_id(&it, 0)); + const Weight *w = ecs_base_field(&it, Weight, 0); + test_assert(w != NULL); + test_int(w->value, 10); + test_bool(false, ecs_query_next(&it)); + + ecs_query_fini(r); + ecs_fini(world); +} + +void ComponentInheritance_1_this_component(void) { + ecs_world_t *world = ecs_mini(); + + ECS_COMPONENT(world, Weight); + ECS_COMPONENT(world, HeavyWeight); + ecs_add_pair(world, ecs_id(HeavyWeight), EcsIsA, ecs_id(Weight)); + + ecs_entity_t e = ecs_new(world); + ecs_set(world, e, HeavyWeight, {.value = 10, .bonus = 3}); + + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "Weight($this)" }); + test_assert(r != NULL); + + ecs_iter_t it = ecs_query_iter(world, r); + test_bool(true, ecs_query_next(&it)); + test_uint(1, it.count); + test_uint(e, it.entities[0]); + test_uint(ecs_id(HeavyWeight), ecs_field_id(&it, 0)); + const Weight *w = ecs_base_field(&it, Weight, 0); + test_assert(w != NULL); + test_int(w[0].value, 10); + test_bool(false, ecs_query_next(&it)); + + ecs_query_fini(r); + ecs_fini(world); +} + +void ComponentInheritance_1_this_component_written(void) { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Tag); + ECS_COMPONENT(world, Weight); + ECS_COMPONENT(world, HeavyWeight); + ecs_add_pair(world, ecs_id(HeavyWeight), EcsIsA, ecs_id(Weight)); + + ecs_entity_t e = ecs_new_w(world, Tag); + ecs_set(world, e, HeavyWeight, {.value = 10, .bonus = 3}); + + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "Tag($this), Weight($this)" }); + test_assert(r != NULL); + + ecs_iter_t it = ecs_query_iter(world, r); + test_bool(true, ecs_query_next(&it)); + test_uint(1, it.count); + test_uint(e, it.entities[0]); + test_uint(ecs_id(HeavyWeight), ecs_field_id(&it, 1)); + const Weight *w = ecs_base_field(&it, Weight, 1); + test_assert(w != NULL); + test_int(w[0].value, 10); + test_bool(false, ecs_query_next(&it)); + + ecs_query_fini(r); + ecs_fini(world); +} + +void ComponentInheritance_1_var_component(void) { + ecs_world_t *world = ecs_mini(); + + ECS_COMPONENT(world, Weight); + ECS_COMPONENT(world, HeavyWeight); + ecs_add_pair(world, ecs_id(HeavyWeight), EcsIsA, ecs_id(Weight)); + + ecs_entity_t e = ecs_new(world); + ecs_set(world, e, HeavyWeight, {.value = 10, .bonus = 3}); + + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "Weight($x)" }); + test_assert(r != NULL); + int x_var = ecs_query_find_var(r, "x"); + test_assert(x_var != -1); + + ecs_iter_t it = ecs_query_iter(world, r); + test_bool(true, ecs_query_next(&it)); + test_uint(0, it.count); + test_uint(e, ecs_field_src(&it, 0)); + test_uint(e, ecs_iter_get_var(&it, x_var)); + test_uint(ecs_id(HeavyWeight), ecs_field_id(&it, 0)); + const Weight *w = ecs_base_field(&it, Weight, 0); + test_assert(w != NULL); + test_int(w->value, 10); + test_bool(false, ecs_query_next(&it)); + + ecs_query_fini(r); + ecs_fini(world); +} + +void ComponentInheritance_1_var_component_written(void) { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Tag); + ECS_COMPONENT(world, Weight); + ECS_COMPONENT(world, HeavyWeight); + ecs_add_pair(world, ecs_id(HeavyWeight), EcsIsA, ecs_id(Weight)); + + ecs_entity_t e = ecs_new_w(world, Tag); + ecs_set(world, e, HeavyWeight, {.value = 10, .bonus = 3}); + + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "Tag($x), Weight($x)" }); + test_assert(r != NULL); + int x_var = ecs_query_find_var(r, "x"); + test_assert(x_var != -1); + + ecs_iter_t it = ecs_query_iter(world, r); + test_bool(true, ecs_query_next(&it)); + test_uint(0, it.count); + test_uint(e, ecs_iter_get_var(&it, x_var)); + test_uint(ecs_id(HeavyWeight), ecs_field_id(&it, 1)); + const Weight *w = ecs_base_field(&it, Weight, 1); + test_assert(w != NULL); + test_int(w->value, 10); + test_bool(false, ecs_query_next(&it)); + + ecs_query_fini(r); + ecs_fini(world); +} + +/* --- Data component (value) inheritance, pair (relationship has data) --- */ + +void ComponentInheritance_1_fixed_component_pair(void) { + ecs_world_t *world = ecs_mini(); + + ECS_COMPONENT(world, Weight); + ECS_COMPONENT(world, HeavyWeight); + ecs_add_pair(world, ecs_id(HeavyWeight), EcsIsA, ecs_id(Weight)); + ecs_entity_t Apples = ecs_entity(world, { .name = "Apples" }); + + ecs_entity_t e1 = ecs_entity(world, { .name = "e1" }); + ecs_set_pair(world, e1, HeavyWeight, Apples, {.value = 10, .bonus = 3}); + + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "Weight(e1, Apples)" }); + test_assert(r != NULL); + + ecs_iter_t it = ecs_query_iter(world, r); + test_bool(true, ecs_query_next(&it)); + test_uint(0, it.count); + test_uint(e1, ecs_field_src(&it, 0)); + test_uint(ecs_pair(ecs_id(HeavyWeight), Apples), ecs_field_id(&it, 0)); + const Weight *w = ecs_base_field(&it, Weight, 0); + test_assert(w != NULL); + test_int(w->value, 10); + test_bool(false, ecs_query_next(&it)); + + ecs_query_fini(r); + ecs_fini(world); +} + +void ComponentInheritance_1_this_component_pair(void) { + ecs_world_t *world = ecs_mini(); + + ECS_COMPONENT(world, Weight); + ECS_COMPONENT(world, HeavyWeight); + ecs_add_pair(world, ecs_id(HeavyWeight), EcsIsA, ecs_id(Weight)); + ecs_entity_t Apples = ecs_entity(world, { .name = "Apples" }); + + ecs_entity_t e = ecs_new(world); + ecs_set_pair(world, e, HeavyWeight, Apples, {.value = 10, .bonus = 3}); + + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "(Weight, Apples)" }); + test_assert(r != NULL); + + ecs_iter_t it = ecs_query_iter(world, r); + test_bool(true, ecs_query_next(&it)); + test_uint(1, it.count); + test_uint(e, it.entities[0]); + test_uint(ecs_pair(ecs_id(HeavyWeight), Apples), ecs_field_id(&it, 0)); + const Weight *w = ecs_base_field(&it, Weight, 0); + test_assert(w != NULL); + test_int(w[0].value, 10); + test_bool(false, ecs_query_next(&it)); + + ecs_query_fini(r); + ecs_fini(world); +} + +void ComponentInheritance_1_this_component_pair_written(void) { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Tag); + ECS_COMPONENT(world, Weight); + ECS_COMPONENT(world, HeavyWeight); + ecs_add_pair(world, ecs_id(HeavyWeight), EcsIsA, ecs_id(Weight)); + ecs_entity_t Apples = ecs_entity(world, { .name = "Apples" }); + + ecs_entity_t e = ecs_new_w(world, Tag); + ecs_set_pair(world, e, HeavyWeight, Apples, {.value = 10, .bonus = 3}); + + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "Tag($this), Weight($this, Apples)" }); + test_assert(r != NULL); + + ecs_iter_t it = ecs_query_iter(world, r); + test_bool(true, ecs_query_next(&it)); + test_uint(1, it.count); + test_uint(e, it.entities[0]); + test_uint(ecs_pair(ecs_id(HeavyWeight), Apples), ecs_field_id(&it, 1)); + const Weight *w = ecs_base_field(&it, Weight, 1); + test_assert(w != NULL); + test_int(w[0].value, 10); + test_bool(false, ecs_query_next(&it)); + + ecs_query_fini(r); + ecs_fini(world); +} + +void ComponentInheritance_1_var_component_pair(void) { + ecs_world_t *world = ecs_mini(); + + ECS_COMPONENT(world, Weight); + ECS_COMPONENT(world, HeavyWeight); + ecs_add_pair(world, ecs_id(HeavyWeight), EcsIsA, ecs_id(Weight)); + ecs_entity_t Apples = ecs_entity(world, { .name = "Apples" }); + + ecs_entity_t e = ecs_new(world); + ecs_set_pair(world, e, HeavyWeight, Apples, {.value = 10, .bonus = 3}); + + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "Weight($x, Apples)" }); + test_assert(r != NULL); + int x_var = ecs_query_find_var(r, "x"); + test_assert(x_var != -1); + + ecs_iter_t it = ecs_query_iter(world, r); + test_bool(true, ecs_query_next(&it)); + test_uint(0, it.count); + test_uint(e, ecs_iter_get_var(&it, x_var)); + test_uint(ecs_pair(ecs_id(HeavyWeight), Apples), ecs_field_id(&it, 0)); + const Weight *w = ecs_base_field(&it, Weight, 0); + test_assert(w != NULL); + test_int(w->value, 10); + test_bool(false, ecs_query_next(&it)); + + ecs_query_fini(r); + ecs_fini(world); +} + +void ComponentInheritance_1_var_component_pair_written(void) { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Tag); + ECS_COMPONENT(world, Weight); + ECS_COMPONENT(world, HeavyWeight); + ecs_add_pair(world, ecs_id(HeavyWeight), EcsIsA, ecs_id(Weight)); + ecs_entity_t Apples = ecs_entity(world, { .name = "Apples" }); + + ecs_entity_t e = ecs_new_w(world, Tag); + ecs_set_pair(world, e, HeavyWeight, Apples, {.value = 10, .bonus = 3}); + + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "Tag($x), Weight($x, Apples)" }); + test_assert(r != NULL); + int x_var = ecs_query_find_var(r, "x"); + test_assert(x_var != -1); + + ecs_iter_t it = ecs_query_iter(world, r); + test_bool(true, ecs_query_next(&it)); + test_uint(0, it.count); + test_uint(e, ecs_iter_get_var(&it, x_var)); + test_uint(ecs_pair(ecs_id(HeavyWeight), Apples), ecs_field_id(&it, 1)); + const Weight *w = ecs_base_field(&it, Weight, 1); + test_assert(w != NULL); + test_int(w->value, 10); + test_bool(false, ecs_query_next(&it)); + + ecs_query_fini(r); + ecs_fini(world); +} + +/* --- Pair (tag) relationship inheritance, remaining src/written --- */ + +void ComponentInheritance_1_fixed_pair(void) { + ecs_world_t *world = ecs_mini(); + + ecs_entity_t Likes = ecs_entity(world, { .name = "Likes" }); + ecs_entity_t Loves = ecs_entity(world, { .name = "Loves" }); + ecs_entity_t Apples = ecs_entity(world, { .name = "Apples" }); + ecs_add_pair(world, Loves, EcsIsA, Likes); + + ecs_entity_t e1 = ecs_entity(world, { .name = "e1" }); + ecs_add_pair(world, e1, Loves, Apples); + + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "Likes(e1, Apples)" }); + test_assert(r != NULL); + + ecs_iter_t it = ecs_query_iter(world, r); + test_bool(true, ecs_query_next(&it)); + test_uint(0, it.count); + test_uint(e1, ecs_field_src(&it, 0)); + test_uint(ecs_pair(Loves, Apples), ecs_field_id(&it, 0)); + test_bool(false, ecs_query_next(&it)); + + ecs_query_fini(r); + ecs_fini(world); +} + +void ComponentInheritance_1_var_pair(void) { + ecs_world_t *world = ecs_mini(); + + ecs_entity_t Likes = ecs_entity(world, { .name = "Likes" }); + ecs_entity_t Loves = ecs_entity(world, { .name = "Loves" }); + ecs_entity_t Apples = ecs_entity(world, { .name = "Apples" }); + ecs_add_pair(world, Loves, EcsIsA, Likes); + + ecs_entity_t e = ecs_new(world); + ecs_add_pair(world, e, Loves, Apples); + + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "Likes($x, Apples)" }); + test_assert(r != NULL); + int x_var = ecs_query_find_var(r, "x"); + test_assert(x_var != -1); + + ecs_iter_t it = ecs_query_iter(world, r); + test_bool(true, ecs_query_next(&it)); + test_uint(0, it.count); + test_uint(e, ecs_iter_get_var(&it, x_var)); + test_uint(ecs_pair(Loves, Apples), ecs_field_id(&it, 0)); + test_bool(false, ecs_query_next(&it)); + + ecs_query_fini(r); + ecs_fini(world); +} + +void ComponentInheritance_1_this_pair_written(void) { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Tag); + ecs_entity_t Likes = ecs_entity(world, { .name = "Likes" }); + ecs_entity_t Loves = ecs_entity(world, { .name = "Loves" }); + ecs_entity_t Apples = ecs_entity(world, { .name = "Apples" }); + ecs_add_pair(world, Loves, EcsIsA, Likes); + + ecs_entity_t e = ecs_new_w(world, Tag); + ecs_add_pair(world, e, Loves, Apples); + + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "Tag($this), Likes($this, Apples)" }); + test_assert(r != NULL); + + ecs_iter_t it = ecs_query_iter(world, r); + test_bool(true, ecs_query_next(&it)); + test_uint(1, it.count); + test_uint(e, it.entities[0]); + test_uint(ecs_pair(Loves, Apples), ecs_field_id(&it, 1)); + test_bool(false, ecs_query_next(&it)); + + ecs_query_fini(r); + ecs_fini(world); +} + +void ComponentInheritance_1_var_pair_written(void) { + ecs_world_t *world = ecs_mini(); + + ECS_TAG(world, Tag); + ecs_entity_t Likes = ecs_entity(world, { .name = "Likes" }); + ecs_entity_t Loves = ecs_entity(world, { .name = "Loves" }); + ecs_entity_t Apples = ecs_entity(world, { .name = "Apples" }); + ecs_add_pair(world, Loves, EcsIsA, Likes); + + ecs_entity_t e = ecs_new_w(world, Tag); + ecs_add_pair(world, e, Loves, Apples); + + ecs_query_t *r = ecs_query(world, { + .cache_kind = cache_kind, .expr = "Tag($x), Likes($x, Apples)" }); + test_assert(r != NULL); + int x_var = ecs_query_find_var(r, "x"); + test_assert(x_var != -1); + + ecs_iter_t it = ecs_query_iter(world, r); + test_bool(true, ecs_query_next(&it)); + test_uint(0, it.count); + test_uint(e, ecs_iter_get_var(&it, x_var)); + test_uint(ecs_pair(Loves, Apples), ecs_field_id(&it, 1)); + test_bool(false, ecs_query_next(&it)); + + ecs_query_fini(r); + ecs_fini(world); +} + +void ComponentInheritance_cached_match_derived_table_added_after(void) { + ecs_world_t *world = ecs_mini(); + + ecs_entity_t Unit = ecs_entity(world, { .name = "Unit" }); + ecs_entity_t Warrior = ecs_entity(world, { .name = "Warrior" }); + ecs_add_pair(world, Warrior, EcsIsA, Unit); + + ecs_query_t *q = ecs_query(world, { + .cache_kind = cache_kind, .expr = "Unit" }); + test_assert(q != NULL); + + if (cache_kind == EcsQueryCacheAuto) { + test_assert(q->flags & EcsQueryIsCacheable); + } + + ecs_entity_t e1 = ecs_new_w_id(world, Warrior); + ecs_entity_t e2 = ecs_new_w_id(world, Unit); + + ecs_iter_t it = ecs_query_iter(world, q); + test_bool(true, ecs_query_next(&it)); + test_uint(1, it.count); + test_uint(e1, it.entities[0]); + test_uint(Warrior, ecs_field_id(&it, 0)); + + test_bool(true, ecs_query_next(&it)); + test_uint(1, it.count); + test_uint(e2, it.entities[0]); + test_uint(Unit, ecs_field_id(&it, 0)); + + test_bool(false, ecs_query_next(&it)); + + ecs_query_fini(q); + ecs_fini(world); +} + +void ComponentInheritance_cached_unmatch_derived_table_on_delete(void) { + ecs_world_t *world = ecs_mini(); + + ecs_entity_t Unit = ecs_entity(world, { .name = "Unit" }); + ecs_entity_t Warrior = ecs_entity(world, { .name = "Warrior" }); + ecs_add_pair(world, Warrior, EcsIsA, Unit); + + ecs_query_t *q = ecs_query(world, { + .cache_kind = cache_kind, .expr = "Unit" }); + test_assert(q != NULL); + + ecs_entity_t e1 = ecs_new_w_id(world, Warrior); + + { + ecs_iter_t it = ecs_query_iter(world, q); + test_bool(true, ecs_query_next(&it)); + test_uint(1, it.count); + test_uint(e1, it.entities[0]); + test_uint(Warrior, ecs_field_id(&it, 0)); + test_bool(false, ecs_query_next(&it)); + } + + ecs_delete(world, e1); + + ecs_delete_empty_tables(world, &(ecs_delete_empty_tables_desc_t){ + .delete_generation = 1 + }); + + { + ecs_iter_t it = ecs_query_iter(world, q); + test_bool(false, ecs_query_next(&it)); + } + + ecs_query_fini(q); + ecs_fini(world); +} diff --git a/test/query/src/Validator.c b/test/query/src/Validator.c index 180d595e8d..9dd1a72a6e 100644 --- a/test/query/src/Validator.c +++ b/test/query/src/Validator.c @@ -3582,12 +3582,14 @@ void Validator_validate_simple_w_inherited_component(void) { test_int(q->terms[0].field_index, 0); test_uint(q->terms[0].first.id, Unit|EcsSelf|EcsIsEntity); test_uint(q->terms[0].src.id, EcsThis|EcsSelf|EcsIsVariable); - test_uint(q->terms[0].flags_, EcsTermIdInherited); + test_uint(q->terms[0].flags_, EcsTermIdInherited|EcsTermIsCacheable); test_assert(!(q->data_fields & (1 << 0))); test_query_flags(EcsQueryMatchOnlyThis|EcsQueryMatchThis| - EcsQueryMatchOnlySelf|EcsQueryHasTableThisVar, + EcsQueryMatchOnlySelf|EcsQueryHasTableThisVar| + EcsQueryHasComponentInheritance|EcsQueryHasCacheable| + EcsQueryIsCacheable, q->flags); ecs_query_fini(q); diff --git a/test/query/src/main.c b/test/query/src/main.c index d16704403c..c33c603804 100644 --- a/test/query/src/main.c +++ b/test/query/src/main.c @@ -1301,10 +1301,11 @@ void Transitive_isa_disabled_match_disabled_flag(void); void Transitive_isa_disabled_match_disabled_term(void); // Testsuite 'ComponentInheritance' -void ComponentInheritance_1_ent_0_lvl(void); -void ComponentInheritance_1_ent_1_lvl(void); -void ComponentInheritance_1_ent_2_lvl(void); -void ComponentInheritance_1_ent_3_lvl(void); +void ComponentInheritance_setup(void); +void ComponentInheritance_1_fixed_0_lvl(void); +void ComponentInheritance_1_fixed_1_lvl(void); +void ComponentInheritance_1_fixed_2_lvl(void); +void ComponentInheritance_1_fixed_3_lvl(void); void ComponentInheritance_1_this_0_lvl(void); void ComponentInheritance_1_this_1_lvl(void); void ComponentInheritance_1_this_2_lvl(void); @@ -1321,12 +1322,27 @@ void ComponentInheritance_1_var_0_lvl_written(void); void ComponentInheritance_1_var_1_lvl_written(void); void ComponentInheritance_1_var_2_lvl_written(void); void ComponentInheritance_1_var_3_lvl_written(void); -void ComponentInheritance_1_ent_1_lvl_self(void); +void ComponentInheritance_1_fixed_component(void); +void ComponentInheritance_1_this_component(void); +void ComponentInheritance_1_this_component_written(void); +void ComponentInheritance_1_var_component(void); +void ComponentInheritance_1_var_component_written(void); +void ComponentInheritance_1_fixed_pair(void); +void ComponentInheritance_1_this_pair(void); +void ComponentInheritance_1_this_pair_written(void); +void ComponentInheritance_1_var_pair(void); +void ComponentInheritance_1_var_pair_written(void); +void ComponentInheritance_1_fixed_component_pair(void); +void ComponentInheritance_1_this_component_pair(void); +void ComponentInheritance_1_this_component_pair_written(void); +void ComponentInheritance_1_var_component_pair(void); +void ComponentInheritance_1_var_component_pair_written(void); +void ComponentInheritance_1_fixed_1_lvl_self(void); void ComponentInheritance_1_this_1_lvl_self(void); void ComponentInheritance_1_this_1_lvl_written_self(void); void ComponentInheritance_1_var_1_lvl_self(void); void ComponentInheritance_1_var_1_lvl_written_self(void); -void ComponentInheritance_1_ent_src_not(void); +void ComponentInheritance_1_fixed_src_not(void); void ComponentInheritance_1_this_src_not(void); void ComponentInheritance_1_var_src_not(void); void ComponentInheritance_1_this_src_not_written(void); @@ -1337,6 +1353,27 @@ void ComponentInheritance_query_before_isa_relationship_1st_term(void); void ComponentInheritance_query_before_isa_relationship_2nd_term(void); void ComponentInheritance_query_before_isa_relationship_subtype(void); void ComponentInheritance_query_before_isa_relationship_0_src(void); +void ComponentInheritance_populated_remove_isa_2_lvl(void); +void ComponentInheritance_populated_remove_isa_3_lvl(void); +void ComponentInheritance_populated_match_entities_added_after(void); +void ComponentInheritance_populated_remove_isa_var(void); +void ComponentInheritance_optional_inherited(void); +void ComponentInheritance_or_inherited(void); +void ComponentInheritance_multiple_inherited_terms(void); +void ComponentInheritance_up_traversal_inherited(void); +void ComponentInheritance_multi_derived_fixed(void); +void ComponentInheritance_multi_derived_this(void); +void ComponentInheritance_multi_derived_this_written(void); +void ComponentInheritance_multi_derived_var(void); +void ComponentInheritance_multi_derived_mid_level_once(void); +void ComponentInheritance_multi_derived_count_results(void); +void ComponentInheritance_pair_3_lvl(void); +void ComponentInheritance_pair_multi(void); +void ComponentInheritance_pair_different_target(void); +void ComponentInheritance_pair_wildcard(void); +void ComponentInheritance_pair_wildcard_multi_rel(void); +void ComponentInheritance_cached_match_derived_table_added_after(void); +void ComponentInheritance_cached_unmatch_derived_table_on_delete(void); // Testsuite 'BuiltinPredicates' void BuiltinPredicates_setup(void); @@ -7907,20 +7944,20 @@ bake_test_case Transitive_testcases[] = { bake_test_case ComponentInheritance_testcases[] = { { - "1_ent_0_lvl", - ComponentInheritance_1_ent_0_lvl + "1_fixed_0_lvl", + ComponentInheritance_1_fixed_0_lvl }, { - "1_ent_1_lvl", - ComponentInheritance_1_ent_1_lvl + "1_fixed_1_lvl", + ComponentInheritance_1_fixed_1_lvl }, { - "1_ent_2_lvl", - ComponentInheritance_1_ent_2_lvl + "1_fixed_2_lvl", + ComponentInheritance_1_fixed_2_lvl }, { - "1_ent_3_lvl", - ComponentInheritance_1_ent_3_lvl + "1_fixed_3_lvl", + ComponentInheritance_1_fixed_3_lvl }, { "1_this_0_lvl", @@ -7987,8 +8024,68 @@ bake_test_case ComponentInheritance_testcases[] = { ComponentInheritance_1_var_3_lvl_written }, { - "1_ent_1_lvl_self", - ComponentInheritance_1_ent_1_lvl_self + "1_fixed_component", + ComponentInheritance_1_fixed_component + }, + { + "1_this_component", + ComponentInheritance_1_this_component + }, + { + "1_this_component_written", + ComponentInheritance_1_this_component_written + }, + { + "1_var_component", + ComponentInheritance_1_var_component + }, + { + "1_var_component_written", + ComponentInheritance_1_var_component_written + }, + { + "1_fixed_pair", + ComponentInheritance_1_fixed_pair + }, + { + "1_this_pair", + ComponentInheritance_1_this_pair + }, + { + "1_this_pair_written", + ComponentInheritance_1_this_pair_written + }, + { + "1_var_pair", + ComponentInheritance_1_var_pair + }, + { + "1_var_pair_written", + ComponentInheritance_1_var_pair_written + }, + { + "1_fixed_component_pair", + ComponentInheritance_1_fixed_component_pair + }, + { + "1_this_component_pair", + ComponentInheritance_1_this_component_pair + }, + { + "1_this_component_pair_written", + ComponentInheritance_1_this_component_pair_written + }, + { + "1_var_component_pair", + ComponentInheritance_1_var_component_pair + }, + { + "1_var_component_pair_written", + ComponentInheritance_1_var_component_pair_written + }, + { + "1_fixed_1_lvl_self", + ComponentInheritance_1_fixed_1_lvl_self }, { "1_this_1_lvl_self", @@ -8007,8 +8104,8 @@ bake_test_case ComponentInheritance_testcases[] = { ComponentInheritance_1_var_1_lvl_written_self }, { - "1_ent_src_not", - ComponentInheritance_1_ent_src_not + "1_fixed_src_not", + ComponentInheritance_1_fixed_src_not }, { "1_this_src_not", @@ -8049,6 +8146,90 @@ bake_test_case ComponentInheritance_testcases[] = { { "query_before_isa_relationship_0_src", ComponentInheritance_query_before_isa_relationship_0_src + }, + { + "populated_remove_isa_2_lvl", + ComponentInheritance_populated_remove_isa_2_lvl + }, + { + "populated_remove_isa_3_lvl", + ComponentInheritance_populated_remove_isa_3_lvl + }, + { + "populated_match_entities_added_after", + ComponentInheritance_populated_match_entities_added_after + }, + { + "populated_remove_isa_var", + ComponentInheritance_populated_remove_isa_var + }, + { + "optional_inherited", + ComponentInheritance_optional_inherited + }, + { + "or_inherited", + ComponentInheritance_or_inherited + }, + { + "multiple_inherited_terms", + ComponentInheritance_multiple_inherited_terms + }, + { + "up_traversal_inherited", + ComponentInheritance_up_traversal_inherited + }, + { + "multi_derived_fixed", + ComponentInheritance_multi_derived_fixed + }, + { + "multi_derived_this", + ComponentInheritance_multi_derived_this + }, + { + "multi_derived_this_written", + ComponentInheritance_multi_derived_this_written + }, + { + "multi_derived_var", + ComponentInheritance_multi_derived_var + }, + { + "multi_derived_mid_level_once", + ComponentInheritance_multi_derived_mid_level_once + }, + { + "multi_derived_count_results", + ComponentInheritance_multi_derived_count_results + }, + { + "pair_3_lvl", + ComponentInheritance_pair_3_lvl + }, + { + "pair_multi", + ComponentInheritance_pair_multi + }, + { + "pair_different_target", + ComponentInheritance_pair_different_target + }, + { + "pair_wildcard", + ComponentInheritance_pair_wildcard + }, + { + "pair_wildcard_multi_rel", + ComponentInheritance_pair_wildcard_multi_rel + }, + { + "cached_match_derived_table_added_after", + ComponentInheritance_cached_match_derived_table_added_after + }, + { + "cached_unmatch_derived_table_on_delete", + ComponentInheritance_cached_unmatch_derived_table_on_delete } }; @@ -13823,6 +14004,11 @@ bake_test_param Operators_params[] = { {"cache_kind", (char**)Operators_cache_kind_param, 2} }; +const char* ComponentInheritance_cache_kind_param[] = {"default", "auto"}; +bake_test_param ComponentInheritance_params[] = { + {"cache_kind", (char**)ComponentInheritance_cache_kind_param, 2} +}; + const char* BuiltinPredicates_cache_kind_param[] = {"default", "auto"}; bake_test_param BuiltinPredicates_params[] = { {"cache_kind", (char**)BuiltinPredicates_cache_kind_param, 2} @@ -13941,10 +14127,12 @@ static bake_test_suite suites[] = { }, { "ComponentInheritance", + ComponentInheritance_setup, NULL, - NULL, - 36, - ComponentInheritance_testcases + 72, + ComponentInheritance_testcases, + 1, + ComponentInheritance_params }, { "BuiltinPredicates", diff --git a/test/script/project.json b/test/script/project.json index fc1617b049..2538e71149 100644 --- a/test/script/project.json +++ b/test/script/project.json @@ -494,7 +494,10 @@ "component_expr_swizzle_var_subset", "component_expr_swizzle_var_rgb", "component_expr_swizzle_var_no_target_type", - "component_expr_member_no_var" + "component_expr_member_no_var", + "struct_inheritance", + "struct_inheritance_empty_derived", + "struct_inheritance_assign" ] }, { "id": "Function", diff --git a/test/script/src/Eval.c b/test/script/src/Eval.c index ea7ea48498..602182e12b 100644 --- a/test/script/src/Eval.c +++ b/test/script/src/Eval.c @@ -15949,3 +15949,113 @@ void Eval_component_expr_member_no_var(void) { ecs_fini(world); } +void Eval_struct_inheritance(void) { + ecs_world_t *world = ecs_init(); + + const char *expr = + HEAD "using flecs.meta" + LINE + LINE "struct Foo {" + LINE " x { member: {i32} }" + LINE " y { member: {i32} }" + LINE "}" + LINE + LINE "struct Bar : Foo {" + LINE " z { member: {i32} }" + LINE "}"; + + test_assert(ecs_script_run(world, NULL, expr, NULL) == 0); + + ecs_entity_t foo = ecs_lookup(world, "Foo"); + ecs_entity_t bar = ecs_lookup(world, "Bar"); + test_assert(foo != 0); + test_assert(bar != 0); + test_assert(ecs_has_pair(world, bar, EcsIsA, foo)); + + test_assert(ecs_struct_get_nth_member(world, bar, 3) == NULL); + test_str(ecs_struct_get_nth_member(world, bar, 0)->name, "x"); + test_int(ecs_struct_get_nth_member(world, bar, 0)->offset, 0); + test_str(ecs_struct_get_nth_member(world, bar, 1)->name, "y"); + test_int(ecs_struct_get_nth_member(world, bar, 1)->offset, 4); + test_str(ecs_struct_get_nth_member(world, bar, 2)->name, "z"); + test_int(ecs_struct_get_nth_member(world, bar, 2)->offset, 8); + + const EcsComponent *c = ecs_get(world, bar, EcsComponent); + test_assert(c != NULL); + test_int(c->size, 12); + + ecs_fini(world); +} + +void Eval_struct_inheritance_empty_derived(void) { + ecs_world_t *world = ecs_init(); + + const char *expr = + HEAD "using flecs.meta" + LINE + LINE "struct Foo {" + LINE " x { member: {i32} }" + LINE " y { member: {i32} }" + LINE "}" + LINE + LINE "struct Bar : Foo {}"; + + test_assert(ecs_script_run(world, NULL, expr, NULL) == 0); + + ecs_entity_t bar = ecs_lookup(world, "Bar"); + test_assert(bar != 0); + + test_assert(ecs_has(world, bar, EcsType)); + + test_assert(ecs_struct_get_nth_member(world, bar, 2) == NULL); + test_str(ecs_struct_get_nth_member(world, bar, 0)->name, "x"); + test_int(ecs_struct_get_nth_member(world, bar, 0)->offset, 0); + test_str(ecs_struct_get_nth_member(world, bar, 1)->name, "y"); + test_int(ecs_struct_get_nth_member(world, bar, 1)->offset, 4); + + const EcsComponent *c = ecs_get(world, bar, EcsComponent); + test_assert(c != NULL); + test_int(c->size, 8); + + ecs_fini(world); +} + +void Eval_struct_inheritance_assign(void) { + typedef struct { + ecs_i32_t x; + ecs_i32_t y; + ecs_i32_t z; + } Bar; + + ecs_world_t *world = ecs_init(); + + const char *expr = + HEAD "using flecs.meta" + LINE + LINE "struct Foo {" + LINE " x { member: {i32} }" + LINE " y { member: {i32} }" + LINE "}" + LINE + LINE "struct Bar : Foo {" + LINE " z { member: {i32} }" + LINE "}" + LINE + LINE "e { Bar: {x: 10, y: 20, z: 30} }"; + + test_assert(ecs_script_run(world, NULL, expr, NULL) == 0); + + ecs_entity_t ecs_id(Bar) = ecs_lookup(world, "Bar"); + ecs_entity_t e = ecs_lookup(world, "e"); + test_assert(ecs_id(Bar) != 0); + test_assert(e != 0); + test_assert(ecs_has(world, e, Bar)); + + const Bar *ptr = ecs_get(world, e, Bar); + test_assert(ptr != NULL); + test_int(ptr->x, 10); + test_int(ptr->y, 20); + test_int(ptr->z, 30); + + ecs_fini(world); +} diff --git a/test/script/src/main.c b/test/script/src/main.c index 2c87710a4e..947323b1a5 100644 --- a/test/script/src/main.c +++ b/test/script/src/main.c @@ -491,6 +491,9 @@ void Eval_component_expr_swizzle_var_subset(void); void Eval_component_expr_swizzle_var_rgb(void); void Eval_component_expr_swizzle_var_no_target_type(void); void Eval_component_expr_member_no_var(void); +void Eval_struct_inheritance(void); +void Eval_struct_inheritance_empty_derived(void); +void Eval_struct_inheritance_assign(void); // Testsuite 'Function' void Function_simple(void); @@ -3422,6 +3425,18 @@ bake_test_case Eval_testcases[] = { { "component_expr_member_no_var", Eval_component_expr_member_no_var + }, + { + "struct_inheritance", + Eval_struct_inheritance + }, + { + "struct_inheritance_empty_derived", + Eval_struct_inheritance_empty_derived + }, + { + "struct_inheritance_assign", + Eval_struct_inheritance_assign } }; @@ -7390,7 +7405,7 @@ static bake_test_suite suites[] = { "Eval", NULL, NULL, - 482, + 485, Eval_testcases }, {