From 2ecb8d599d0c027c6c7a57f0bf4c768665275a30 Mon Sep 17 00:00:00 2001 From: Denis Blank Date: Sun, 3 Sep 2023 16:30:47 +0200 Subject: [PATCH] Implement a world-local component id caching API and make use of it in cpp components * Fixes potential conflicting component id issues when initializing different worlds with a different order. * Closes #1032 --- include/flecs.h | 52 +++++ include/flecs/addons/cpp/component.hpp | 203 ++++++------------ .../addons/cpp/mixins/entity/builder.hpp | 12 +- .../flecs/addons/cpp/mixins/filter/impl.hpp | 2 +- include/flecs/addons/cpp/mixins/meta/impl.hpp | 14 +- .../flecs/addons/cpp/mixins/module/impl.hpp | 6 +- include/flecs/addons/flecs_cpp.h | 14 +- src/addons/flecs_cpp.c | 18 +- src/private_types.h | 3 + src/world.c | 55 +++++ 10 files changed, 217 insertions(+), 162 deletions(-) diff --git a/include/flecs.h b/include/flecs.h index 9ebf225a91..0038a2d176 100644 --- a/include/flecs.h +++ b/include/flecs.h @@ -432,6 +432,16 @@ typedef struct ecs_type_hooks_t ecs_type_hooks_t; * alignment and type hooks. */ typedef struct ecs_type_info_t ecs_type_info_t; +/** Cached Type information. + * Contains information about a component type, such as its id, size and alignment */ +typedef struct ecs_cached_component_info_t ecs_cached_component_info_t; + +/** An index to a cached component id information. + * Can be used to map a typed component from object-oriented language + * fast to a dynamically-per-world generated component id. + * Components are resolved by name lookup and subsequently cached. */ +typedef int32_t ecs_component_cache_index_t; + /** Information about an entity, like its table and row. */ typedef struct ecs_record_t ecs_record_t; @@ -864,6 +874,16 @@ struct ecs_type_info_t { const char *name; /**< Type name. */ }; +/** Type that contains cache component information + * + * \ingroup components + */ +struct ecs_cached_component_info_t { + ecs_entity_t component; /**< Handle to component */ + ecs_size_t size; /**< Size of type */ + ecs_size_t alignment; /**< Alignment of type */ +}; + #include "flecs/private/api_types.h" /* Supporting API types */ #include "flecs/private/api_support.h" /* Supporting API functions */ #include "flecs/private/vec.h" /* Vector */ @@ -3690,6 +3710,38 @@ const ecs_type_hooks_t* ecs_get_hooks_id( ecs_world_t *world, ecs_entity_t id); +/** Get the cached information for a specific component cache index. + * + * @param world The world. + * @param component_cache_index The component cache index to lookup. + * @return The cached component info for the specific component, always returns a present entry. + */ +FLECS_API +ecs_cached_component_info_t* ecs_get_or_create_cached_component_info( + ecs_world_t* world, + ecs_component_cache_index_t component_cache_index); + +/** Get the valid cached information for a specific component cache index. + * + * @param world The world. + * @param component_cache_index The component cache index to lookup. + * @return The valid cached component info for the specific component or NULL if invalid. + */ +FLECS_API +const ecs_cached_component_info_t* ecs_lookup_cached_component_info( + const ecs_world_t* world, + ecs_component_cache_index_t component_cache_index); + + +/** Test if the cached component info is valid (set) + * + * @param component_info The component cache index to lookup. + * @return True if the info is valid. + */ +FLECS_API +bool ecs_is_cached_component_info_valid( + const ecs_cached_component_info_t* component_info); + /** @} */ /** diff --git a/include/flecs/addons/cpp/component.hpp b/include/flecs/addons/cpp/component.hpp index 2cec47ed10..30f1423b5f 100644 --- a/include/flecs/addons/cpp/component.hpp +++ b/include/flecs/addons/cpp/component.hpp @@ -125,61 +125,20 @@ void register_lifecycle_actions( // will register it as a component, and verify whether the input is consistent. template struct cpp_type_impl { - // Initialize component identifier - static void init( - entity_t entity, - bool allow_tag = true) - { - if (s_reset_count != ecs_cpp_reset_count_get()) { - reset(); - } - - // If an identifier was already set, check for consistency - if (s_id) { - ecs_assert(s_id == entity, ECS_INCONSISTENT_COMPONENT_ID, - type_name()); - ecs_assert(allow_tag == s_allow_tag, ECS_INVALID_PARAMETER, NULL); - - // Component was already registered and data is consistent with new - // identifier, so nothing else to be done. - return; - } - - // Component wasn't registered yet, set the values. Register component - // name as the fully qualified flecs path. - s_id = entity; - s_allow_tag = allow_tag; - s_size = sizeof(T); - s_alignment = alignof(T); - if (is_empty::value && allow_tag) { - s_size = 0; - s_alignment = 0; - } - - s_reset_count = ecs_cpp_reset_count_get(); - } - // Obtain a component identifier for explicit component registration. - static entity_t id_explicit(world_t *world = nullptr, + static entity_t id_explicit(world_t *world, const char *name = nullptr, bool allow_tag = true, flecs::id_t id = 0, bool is_component = true, bool *existing = nullptr) { - if (!s_id) { - // If no world was provided the component cannot be registered - ecs_assert(world != nullptr, ECS_COMPONENT_NOT_REGISTERED, name); - } else { - ecs_assert(!id || s_id == id, ECS_INCONSISTENT_COMPONENT_ID, NULL); - } - - // If no id has been registered yet for the component (indicating the - // component has not yet been registered, or the component is used - // across more than one binary), or if the id does not exists in the - // world (indicating a multi-world application), register it. */ - if (!s_id || (world && !ecs_exists(world, s_id))) { - init(s_id ? s_id : id, allow_tag); - - ecs_assert(!id || s_id == id, ECS_INTERNAL_ERROR, NULL); + ecs_assert(world != nullptr, ECS_INTERNAL_ERROR, name); + if (const ecs_cached_component_info_t* info = ecs_lookup_cached_component_info(world, index)) { + return info->component; + } else { + // If no id has been registered yet for the component (indicating the + // component has not yet been registered), or if the id does not + // exists in the world (indicating a multi-world application), + // register it. */ const char *symbol = nullptr; if (id) { symbol = ecs_get_symbol(world, id); @@ -188,23 +147,37 @@ struct cpp_type_impl { symbol = symbol_name(); } - entity_t entity = ecs_cpp_component_register_explicit( - world, s_id, id, name, type_name(), symbol, - s_size, s_alignment, is_component, existing); + const bool is_tag = is_empty::value && allow_tag; + + const size_t component_size = is_tag ? 0U : size(); + const size_t component_alignment = is_tag ? 0U : alignment(); + + const entity_t entity = ecs_cpp_component_register_explicit( + world, id, name, type_name(), symbol, + component_size, component_alignment, is_component, existing); + + // Component wasn't registered yet, set the values. Register component + // name as the fully qualified flecs path. + ecs_cached_component_info_t* inserted = + ecs_get_or_create_cached_component_info(world, index); - s_id = entity; + ecs_assert(!!inserted, ECS_INTERNAL_ERROR, NULL); + ecs_assert(!ecs_is_cached_component_info_valid(inserted), ECS_INTERNAL_ERROR, + NULL); + + inserted->component = entity; + inserted->size = static_cast(component_size); + inserted->alignment = static_cast(component_alignment); + + ecs_assert(ecs_is_cached_component_info_valid(inserted), ECS_INTERNAL_ERROR, NULL); // If component is enum type, register constants #if FLECS_CPP_ENUM_REFLECTION_SUPPORT _::init_enum(world, entity); #endif - } - // By now the identifier must be valid and known with the world. - ecs_assert(s_id != 0 && ecs_exists(world, s_id), - ECS_INTERNAL_ERROR, NULL); - - return s_id; + return entity; + } } // Obtain a component identifier for implicit component registration. This @@ -213,29 +186,28 @@ struct cpp_type_impl { // Additionally, implicit registration temporarily resets the scope & with // state of the world, so that the component is not implicitly created with // the scope/with of the code it happens to be first used by. - static id_t id(world_t *world = nullptr, const char *name = nullptr, + static id_t id(world_t *world, const char *name = nullptr, bool allow_tag = true) { - // If no id has been registered yet, do it now. - if (!registered(world)) { - ecs_entity_t prev_scope = 0; - ecs_id_t prev_with = 0; - - if (world) { - prev_scope = ecs_set_scope(world, 0); - prev_with = ecs_set_with(world, 0); - } + ecs_assert(world != nullptr, ECS_INTERNAL_ERROR, name); + + if (const ecs_cached_component_info_t* info = ecs_lookup_cached_component_info(world, index)) { + return info->component; + } else { + // If no id has been registered yet, do it now. + const ecs_entity_t prev_scope = ecs_set_scope(world, 0); + const ecs_id_t prev_with = ecs_set_with(world, 0); // This will register a component id, but will not register // lifecycle callbacks. bool existing; - id_explicit(world, name, allow_tag, 0, true, &existing); + const entity_t id = id_explicit(world, name, allow_tag, 0, true, &existing); // Register lifecycle callbacks, but only if the component has a // size. Components that don't have a size are tags, and tags don't // require construction/destruction/copy/move's. */ if (size() && !existing) { - register_lifecycle_actions(world, s_id); + register_lifecycle_actions(world, id); } if (prev_with) { @@ -244,62 +216,41 @@ struct cpp_type_impl { if (prev_scope) { ecs_set_scope(world, prev_scope); } - } - - // By now we should have a valid identifier - ecs_assert(s_id != 0, ECS_INTERNAL_ERROR, NULL); - return s_id; + return id; + } } - // Return the size of a component. - static size_t size() { - ecs_assert(s_id != 0, ECS_INTERNAL_ERROR, NULL); - return s_size; + /// Looks the assigned component up in the provided world. + /// It can happen that the component has not been initialized yet. + static entity_t lookup(const world_t* world) { + const ecs_cached_component_info_t* info = ecs_lookup_cached_component_info(world, index); + return info ? info->component : 0; } - // Return the alignment of a component. - static size_t alignment() { - ecs_assert(s_id != 0, ECS_INTERNAL_ERROR, NULL); - return s_alignment; + // Was the component already registered. + static bool registered(const world_t* world) { + return !!lookup(world); } - // Was the component already registered. - static bool registered(flecs::world_t *world) { - if (s_reset_count != ecs_cpp_reset_count_get()) { - reset(); - } - if (s_id == 0) { - return false; - } - if (world && !ecs_exists(world, s_id)) { - return false; - } - return true; + // Return the size of this component. + static size_t size() { + return sizeof(T); } - // This function is only used to test cross-translation unit features. No - // code other than test cases should invoke this function. - static void reset() { - s_id = 0; - s_size = 0; - s_alignment = 0; - s_allow_tag = true; + // Return the alignment of this component. + static size_t alignment() { + return alignof(T); } - static entity_t s_id; - static size_t s_size; - static size_t s_alignment; - static bool s_allow_tag; - static int32_t s_reset_count; + // Acquire a per instance incremental index for a world-local component id cache. + static ecs_component_cache_index_t index; }; // Global templated variables that hold component identifier and other info -template entity_t cpp_type_impl::s_id; -template size_t cpp_type_impl::s_size; -template size_t cpp_type_impl::s_alignment; -template bool cpp_type_impl::s_allow_tag( true ); -template int32_t cpp_type_impl::s_reset_count; +template +ecs_component_cache_index_t cpp_type_impl::index{ + ecs_cpp_component_id_storage_add()}; // Front facing class for implicitly registering a component & obtaining // static component data @@ -375,10 +326,9 @@ struct component : untyped_component { implicit_name = true; } - if (_::cpp_type::registered(world)) { - /* Obtain component id. Because the component is already registered, - * this operation does nothing besides returning the existing id */ - id = _::cpp_type::id_explicit(world, name, allow_tag, id); + /* Obtain a registered component id. */ + if (const entity_t registered = _::cpp_type::lookup(world)) { + id = registered; ecs_cpp_component_validate(world, id, n, _::symbol_name(), _::cpp_type::size(), @@ -504,17 +454,6 @@ struct component : untyped_component { } }; -/** Get id currently assigned to component. If no world has registered the - * component yet, this operation will return 0. */ -template -flecs::entity_t type_id() { - if (_::cpp_type::s_reset_count == ecs_cpp_reset_count_get()) { - return _::cpp_type::s_id; - } else { - return 0; - } -} - /** Reset static component ids. * When components are registered their component ids are stored in a static * type specific variable. This stored id is passed into component registration @@ -537,9 +476,9 @@ flecs::entity_t type_id() { * * \ingroup cpp_components */ -inline void reset() { - ecs_cpp_reset_count_inc(); -} +ECS_DEPRECATED("reset was deprecated, world-local component ids " + "are supported by default now.") +inline void reset() {} } diff --git a/include/flecs/addons/cpp/mixins/entity/builder.hpp b/include/flecs/addons/cpp/mixins/entity/builder.hpp index 31963b563c..02f7f88721 100644 --- a/include/flecs/addons/cpp/mixins/entity/builder.hpp +++ b/include/flecs/addons/cpp/mixins/entity/builder.hpp @@ -566,7 +566,7 @@ struct entity_builder : entity_view { */ template Self& enable() { - return this->enable(_::cpp_type::id()); + return this->enable(_::cpp_type::id(this->m_world)); } /** Enable a pair. @@ -587,7 +587,7 @@ struct entity_builder : entity_view { */ template Self& enable(flecs::id_t second) { - return this->enable(_::cpp_type::id(), second); + return this->enable(_::cpp_type::id(this->m_world), second); } /** Enable a pair. @@ -598,7 +598,7 @@ struct entity_builder : entity_view { */ template Self& enable() { - return this->enable(_::cpp_type::id()); + return this->template enable(_::cpp_type::id(this->m_world)); } /** Disable an id. @@ -618,7 +618,7 @@ struct entity_builder : entity_view { */ template Self& disable() { - return this->disable(_::cpp_type::id()); + return this->disable(_::cpp_type::id(this->m_world)); } /** Disable a pair. @@ -639,7 +639,7 @@ struct entity_builder : entity_view { */ template Self& disable(flecs::id_t second) { - return this->disable(_::cpp_type::id(), second); + return this->disable(_::cpp_type::id(this->m_world), second); } /** Disable a pair. @@ -650,7 +650,7 @@ struct entity_builder : entity_view { */ template Self& disable() { - return this->disable(_::cpp_type::id()); + return this->disable(_::cpp_type::id(this->m_world)); } Self& set_ptr(entity_t comp, size_t size, const void *ptr) { diff --git a/include/flecs/addons/cpp/mixins/filter/impl.hpp b/include/flecs/addons/cpp/mixins/filter/impl.hpp index 1b768710c0..32909555d9 100644 --- a/include/flecs/addons/cpp/mixins/filter/impl.hpp +++ b/include/flecs/addons/cpp/mixins/filter/impl.hpp @@ -240,7 +240,7 @@ inline void world::each(Func&& func) const { template inline void world::each(Func&& func) const { ecs_term_t t = {}; - t.id = _::cpp_type::id(); + t.id = _::cpp_type::id(this->m_world); ecs_iter_t it = ecs_term_iter(m_world, &t); while (ecs_term_next(&it)) { diff --git a/include/flecs/addons/cpp/mixins/meta/impl.hpp b/include/flecs/addons/cpp/mixins/meta/impl.hpp index 31cb533953..f70dc8086d 100644 --- a/include/flecs/addons/cpp/mixins/meta/impl.hpp +++ b/include/flecs/addons/cpp/mixins/meta/impl.hpp @@ -64,18 +64,20 @@ inline void init(flecs::world& world) { // specific types. if (!flecs::is_same() && !flecs::is_same()) { - flecs::_::cpp_type::init(flecs::Iptr, true); - ecs_assert(flecs::type_id() == flecs::Iptr, - ECS_INTERNAL_ERROR, NULL); + const entity_t id = flecs::_::cpp_type::id_explicit(world, nullptr, flecs::Iptr, true); + (void)id; + + ecs_assert(id == flecs::Iptr, ECS_INTERNAL_ERROR, NULL); // Remove symbol to prevent validation errors, as it doesn't match with // the typename ecs_remove_pair(world, flecs::Iptr, ecs_id(EcsIdentifier), EcsSymbol); } if (!flecs::is_same() && !flecs::is_same()) { - flecs::_::cpp_type::init(flecs::Uptr, true); - ecs_assert(flecs::type_id() == flecs::Uptr, - ECS_INTERNAL_ERROR, NULL); + const entity_t id = flecs::_::cpp_type::id_explicit(world, nullptr, flecs::Uptr, true); + (void)id; + + ecs_assert(id == flecs::Uptr, ECS_INTERNAL_ERROR, NULL); // Remove symbol to prevent validation errors, as it doesn't match with // the typename ecs_remove_pair(world, flecs::Uptr, ecs_id(EcsIdentifier), EcsSymbol); diff --git a/include/flecs/addons/cpp/mixins/module/impl.hpp b/include/flecs/addons/cpp/mixins/module/impl.hpp index d99c2e7d29..ca22d280ca 100644 --- a/include/flecs/addons/cpp/mixins/module/impl.hpp +++ b/include/flecs/addons/cpp/mixins/module/impl.hpp @@ -43,12 +43,8 @@ flecs::entity import(world& world) { if (!_::cpp_type::registered(world)) { - /* Module is registered with world, initialize static data */ - if (m) { - _::cpp_type::init(m, false); - /* Module is not yet registered, register it now */ - } else { + if (!m) { m = _::do_import(world, symbol); } diff --git a/include/flecs/addons/flecs_cpp.h b/include/flecs/addons/flecs_cpp.h index 2ee2642f79..e29c1eb64b 100644 --- a/include/flecs/addons/flecs_cpp.h +++ b/include/flecs/addons/flecs_cpp.h @@ -89,7 +89,6 @@ ecs_entity_t ecs_cpp_component_register( FLECS_API ecs_entity_t ecs_cpp_component_register_explicit( ecs_world_t *world, - ecs_entity_t s_id, ecs_entity_t id, const char *name, const char *type_name, @@ -112,11 +111,18 @@ ecs_entity_t ecs_cpp_enum_constant_register( const char *name, int value); -FLECS_API -int32_t ecs_cpp_reset_count_get(void); +ECS_DEPRECATED( + "ecs_cpp_reset_count_get was deprecated, world-local component ids " + "are supported by default now.") +FLECS_API int32_t ecs_cpp_reset_count_get(void); + +ECS_DEPRECATED( + "ecs_cpp_reset_count_inc was deprecated, world-local component ids " + "are supported by default now.") +FLECS_API int32_t ecs_cpp_reset_count_inc(void); FLECS_API -int32_t ecs_cpp_reset_count_inc(void); +ecs_size_t ecs_cpp_component_id_storage_add(void); #ifdef FLECS_META FLECS_API diff --git a/src/addons/flecs_cpp.c b/src/addons/flecs_cpp.c index c62b7bb5e0..2fccff0bd7 100644 --- a/src/addons/flecs_cpp.c +++ b/src/addons/flecs_cpp.c @@ -347,7 +347,6 @@ ecs_entity_t ecs_cpp_component_register( ecs_entity_t ecs_cpp_component_register_explicit( ecs_world_t *world, - ecs_entity_t s_id, ecs_entity_t id, const char *name, const char *type_name, @@ -388,7 +387,7 @@ ecs_entity_t ecs_cpp_component_register_explicit( ecs_entity_t entity; if (is_component || size != 0) { entity = ecs_entity(world, { - .id = s_id, + .id = 0, .name = name, .sep = "::", .root_sep = "::", @@ -405,7 +404,7 @@ ecs_entity_t ecs_cpp_component_register_explicit( ecs_assert(entity != 0, ECS_INVALID_OPERATION, NULL); } else { entity = ecs_entity(world, { - .id = s_id, + .id = 0, .name = name, .sep = "::", .root_sep = "::", @@ -415,7 +414,6 @@ ecs_entity_t ecs_cpp_component_register_explicit( } ecs_assert(entity != 0, ECS_INTERNAL_ERROR, NULL); - ecs_assert(!s_id || s_id == entity, ECS_INTERNAL_ERROR, NULL); ecs_os_free(existing_name); return entity; @@ -482,14 +480,18 @@ ecs_entity_t ecs_cpp_enum_constant_register( return id; } -static int32_t flecs_reset_count = 0; - int32_t ecs_cpp_reset_count_get(void) { - return flecs_reset_count; + return 0; // Deprecated } int32_t ecs_cpp_reset_count_inc(void) { - return ++flecs_reset_count; + return 0; // Deprecated +} + +static ecs_size_t flecs_component_storage_count = 0; + +ecs_size_t ecs_cpp_component_id_storage_add(void) { + return flecs_component_storage_count++; } #ifdef FLECS_META diff --git a/src/private_types.h b/src/private_types.h index cd55322db2..fe4cb05164 100644 --- a/src/private_types.h +++ b/src/private_types.h @@ -545,6 +545,9 @@ struct ecs_world_t { ecs_ctx_free_t binding_ctx_free; /**< Callback to free binding_ctx */ ecs_vec_t fini_actions; /* Callbacks to execute when world exits */ + + /* -- Caches -- */ + ecs_vec_t component_id_cache; /* World local component id cache */ }; #endif diff --git a/src/world.c b/src/world.c index 9a3751e4ae..0b3a61cdce 100644 --- a/src/world.c +++ b/src/world.c @@ -893,6 +893,8 @@ ecs_world_t *ecs_mini(void) { flecs_sparse_init_t(world->pending_buffer, a, &world->allocators.sparse_chunk, ecs_table_t*); + ecs_vec_init_t(a, &world->component_id_cache, ecs_cached_component_info_t, 0); + flecs_name_index_init(&world->aliases, a); flecs_name_index_init(&world->symbols, a); ecs_vec_init_t(a, &world->fini_actions, ecs_action_elem_t, 0); @@ -1240,6 +1242,57 @@ const ecs_type_hooks_t* ecs_get_hooks_id( return NULL; } +ecs_cached_component_info_t* ecs_get_or_create_cached_component_info( + ecs_world_t* world, + ecs_component_cache_index_t component_cache_index) { + + ecs_poly_assert(world, ecs_world_t); + + const int32_t old_count = ecs_vec_count(&world->component_id_cache); + if (component_cache_index >= old_count) { + const int32_t new_count = component_cache_index + 1; + + ecs_vec_set_count_t(&world->allocator, &world->component_id_cache, + ecs_cached_component_info_t, new_count); + + ecs_cached_component_info_t *added = + ecs_vec_get_t(&world->component_id_cache, + ecs_cached_component_info_t, old_count); + + /* Array may have holes, so initialize with 0 */ + ecs_os_memset_n(added, 0, ecs_cached_component_info_t, + new_count - old_count); + } + + return ecs_vec_get_t(&world->component_id_cache, + ecs_cached_component_info_t, component_cache_index); +} + +const ecs_cached_component_info_t* ecs_lookup_cached_component_info( + const ecs_world_t* world, + ecs_component_cache_index_t component_cache_index) { + + ecs_poly_assert(world, ecs_world_t); + + if (component_cache_index >= ecs_vec_count(&world->component_id_cache)) { + return NULL; + } + + const ecs_cached_component_info_t *info = + ecs_vec_get_t(&world->component_id_cache, ecs_cached_component_info_t, + component_cache_index); + + if (!ecs_is_cached_component_info_valid(info)) { + return NULL; + } + + return info; +} + +bool ecs_is_cached_component_info_valid(const ecs_cached_component_info_t* component_info) { + return component_info->component != 0; +} + void ecs_atfini( ecs_world_t *world, ecs_fini_action_t action, @@ -1398,10 +1451,12 @@ int ecs_fini( } /* After this point no more user code is invoked */ + ecs_allocator_t* a = &world->allocator; ecs_dbg_1("#[bold]cleanup world datastructures"); ecs_log_push_1(); flecs_entities_fini(world); + ecs_vec_fini_t(a, &world->component_id_cache, ecs_cached_component_info_t); flecs_sparse_fini(world->pending_tables); flecs_sparse_fini(world->pending_buffer); ecs_os_free(world->pending_tables);