Skip to content

Commit

Permalink
Implement a world-local component id caching API and make use of it i…
Browse files Browse the repository at this point in the history
…n cpp components

* Fixes potential conflicting component id issues when initializing
  different worlds with a different order.
* Closes SanderMertens#1032
  • Loading branch information
Naios committed Oct 3, 2023
1 parent 808e510 commit a0fa136
Show file tree
Hide file tree
Showing 15 changed files with 276 additions and 299 deletions.
52 changes: 52 additions & 0 deletions include/flecs.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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);

/** @} */

/**
Expand Down
223 changes: 89 additions & 134 deletions include/flecs/addons/cpp/component.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,14 @@ void register_lifecycle_actions(
ecs_set_hooks_id( world, component, &cl);
}

// Instantiates a per-instance global component cache index
struct cpp_type_component_cache_index {
cpp_type_component_cache_index()
: index(ecs_cpp_component_id_storage_add()) {}

ecs_component_cache_index_t const index;
};

// Class that manages component ids across worlds & binaries.
// The cpp_type class stores the component id for a C++ type in a static global
// variable that is shared between worlds. Whenever a component is used this
Expand All @@ -125,61 +133,22 @@ void register_lifecycle_actions(
// will register it as a component, and verify whether the input is consistent.
template <typename T>
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<T>());
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<T>::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)
bool is_component = true, bool *existing = nullptr, flecs::id_t s_id = 0)
{
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);
}
ecs_assert(world != nullptr, ECS_INTERNAL_ERROR, name);

// 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);
if (const ecs_cached_component_info_t* info = ecs_lookup_cached_component_info(world, cached_component_index.index)) {
return info->component;
} else {
ecs_assert(!ecs_stage_is_readonly(ecs_get_world(world)), ECS_INVALID_WHILE_READONLY, name);

// 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);
Expand All @@ -188,23 +157,37 @@ struct cpp_type_impl {
symbol = symbol_name<T>();
}

entity_t entity = ecs_cpp_component_register_explicit(
world, s_id, id, name, type_name<T>(), symbol,
s_size, s_alignment, is_component, existing);
const bool is_tag = is_empty<T>::value && allow_tag;

const size_t component_size = is_tag ? 0U : size();
const size_t component_alignment = is_tag ? 0U : alignment();

s_id = entity;
const entity_t entity = ecs_cpp_component_register_explicit(
world, s_id, id, name, type_name<T>(), 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, cached_component_index.index);

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<ecs_size_t>(component_size);
inserted->alignment = static_cast<ecs_size_t>(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<T>(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
Expand All @@ -213,29 +196,31 @@ 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, cached_component_index.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);
ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL);
ecs_assert(ecs_lookup_cached_component_info(world, cached_component_index.index) != NULL,
ECS_INTERNAL_ERROR, NULL);

// 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<T>(world, s_id);
register_lifecycle_actions<T>(world, id);
}

if (prev_with) {
Expand All @@ -244,62 +229,40 @@ 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, cached_component_index.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 index cache.
static cpp_type_component_cache_index cached_component_index;
};

// Global templated variables that hold component identifier and other info
template <typename T> entity_t cpp_type_impl<T>::s_id;
template <typename T> size_t cpp_type_impl<T>::s_size;
template <typename T> size_t cpp_type_impl<T>::s_alignment;
template <typename T> bool cpp_type_impl<T>::s_allow_tag( true );
template <typename T> int32_t cpp_type_impl<T>::s_reset_count;
template <typename T>
cpp_type_component_cache_index cpp_type_impl<T>::cached_component_index;

// Front facing class for implicitly registering a component & obtaining
// static component data
Expand Down Expand Up @@ -375,10 +338,9 @@ struct component : untyped_component {
implicit_name = true;
}

if (_::cpp_type<T>::registered(world)) {
/* Obtain component id. Because the component is already registered,
* this operation does nothing besides returning the existing id */
id = _::cpp_type<T>::id_explicit(world, name, allow_tag, id);
/* Obtain a registered component id. */
if (const entity_t registered = _::cpp_type<T>::lookup(world)) {
id = registered;

ecs_cpp_component_validate(world, id, n, _::symbol_name<T>(),
_::cpp_type<T>::size(),
Expand Down Expand Up @@ -412,8 +374,12 @@ struct component : untyped_component {
id = ecs_cpp_component_register(world, id, n, _::symbol_name<T>(),
ECS_SIZEOF(T), ECS_ALIGNOF(T), implicit_name, &existing);

/* Initialize static component data */
id = _::cpp_type<T>::id_explicit(world, name, allow_tag, id);
if (!existing) {
/* Initialize static component data */
id = _::cpp_type<T>::id_explicit(world, name, allow_tag, id);
} else {
ecs_assert(ecs_is_valid(world, id), ECS_INTERNAL_ERROR, NULL);
}

/* Initialize lifecycle actions (ctor, dtor, copy, move) */
if (_::cpp_type<T>::size() && !existing) {
Expand Down Expand Up @@ -504,17 +470,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 <typename T>
flecs::entity_t type_id() {
if (_::cpp_type<T>::s_reset_count == ecs_cpp_reset_count_get()) {
return _::cpp_type<T>::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
Expand All @@ -537,9 +492,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() {}

}

Expand Down
Loading

0 comments on commit a0fa136

Please sign in to comment.