Skip to content

Commit 1f78c8c

Browse files
committed
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 SanderMertens#1032
1 parent 7e05ef3 commit 1f78c8c

File tree

15 files changed

+276
-299
lines changed

15 files changed

+276
-299
lines changed

include/flecs.h

+52
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,16 @@ typedef struct ecs_type_hooks_t ecs_type_hooks_t;
432432
* alignment and type hooks. */
433433
typedef struct ecs_type_info_t ecs_type_info_t;
434434

435+
/** Cached Type information.
436+
* Contains information about a component type, such as its id, size and alignment */
437+
typedef struct ecs_cached_component_info_t ecs_cached_component_info_t;
438+
439+
/** An index to a cached component id information.
440+
* Can be used to map a typed component from object-oriented language
441+
* fast to a dynamically-per-world generated component id.
442+
* Components are resolved by name lookup and subsequently cached. */
443+
typedef int32_t ecs_component_cache_index_t;
444+
435445
/** Information about an entity, like its table and row. */
436446
typedef struct ecs_record_t ecs_record_t;
437447

@@ -864,6 +874,16 @@ struct ecs_type_info_t {
864874
const char *name; /**< Type name. */
865875
};
866876

877+
/** Type that contains cache component information
878+
*
879+
* \ingroup components
880+
*/
881+
struct ecs_cached_component_info_t {
882+
ecs_entity_t component; /**< Handle to component */
883+
ecs_size_t size; /**< Size of type */
884+
ecs_size_t alignment; /**< Alignment of type */
885+
};
886+
867887
#include "flecs/private/api_types.h" /* Supporting API types */
868888
#include "flecs/private/api_support.h" /* Supporting API functions */
869889
#include "flecs/private/vec.h" /* Vector */
@@ -3690,6 +3710,38 @@ const ecs_type_hooks_t* ecs_get_hooks_id(
36903710
ecs_world_t *world,
36913711
ecs_entity_t id);
36923712

3713+
/** Get the cached information for a specific component cache index.
3714+
*
3715+
* @param world The world.
3716+
* @param component_cache_index The component cache index to lookup.
3717+
* @return The cached component info for the specific component, always returns a present entry.
3718+
*/
3719+
FLECS_API
3720+
ecs_cached_component_info_t* ecs_get_or_create_cached_component_info(
3721+
ecs_world_t* world,
3722+
ecs_component_cache_index_t component_cache_index);
3723+
3724+
/** Get the valid cached information for a specific component cache index.
3725+
*
3726+
* @param world The world.
3727+
* @param component_cache_index The component cache index to lookup.
3728+
* @return The valid cached component info for the specific component or NULL if invalid.
3729+
*/
3730+
FLECS_API
3731+
const ecs_cached_component_info_t* ecs_lookup_cached_component_info(
3732+
const ecs_world_t* world,
3733+
ecs_component_cache_index_t component_cache_index);
3734+
3735+
3736+
/** Test if the cached component info is valid (set)
3737+
*
3738+
* @param component_info The component cache index to lookup.
3739+
* @return True if the info is valid.
3740+
*/
3741+
FLECS_API
3742+
bool ecs_is_cached_component_info_valid(
3743+
const ecs_cached_component_info_t* component_info);
3744+
36933745
/** @} */
36943746

36953747
/**

include/flecs/addons/cpp/component.hpp

+89-134
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,14 @@ void register_lifecycle_actions(
113113
ecs_set_hooks_id( world, component, &cl);
114114
}
115115

116+
// Instantiates a per-instance global component cache index
117+
struct cpp_type_component_cache_index {
118+
cpp_type_component_cache_index()
119+
: index(ecs_cpp_component_id_storage_add()) {}
120+
121+
ecs_component_cache_index_t const index;
122+
};
123+
116124
// Class that manages component ids across worlds & binaries.
117125
// The cpp_type class stores the component id for a C++ type in a static global
118126
// variable that is shared between worlds. Whenever a component is used this
@@ -125,61 +133,22 @@ void register_lifecycle_actions(
125133
// will register it as a component, and verify whether the input is consistent.
126134
template <typename T>
127135
struct cpp_type_impl {
128-
// Initialize component identifier
129-
static void init(
130-
entity_t entity,
131-
bool allow_tag = true)
132-
{
133-
if (s_reset_count != ecs_cpp_reset_count_get()) {
134-
reset();
135-
}
136-
137-
// If an identifier was already set, check for consistency
138-
if (s_id) {
139-
ecs_assert(s_id == entity, ECS_INCONSISTENT_COMPONENT_ID,
140-
type_name<T>());
141-
ecs_assert(allow_tag == s_allow_tag, ECS_INVALID_PARAMETER, NULL);
142-
143-
// Component was already registered and data is consistent with new
144-
// identifier, so nothing else to be done.
145-
return;
146-
}
147-
148-
// Component wasn't registered yet, set the values. Register component
149-
// name as the fully qualified flecs path.
150-
s_id = entity;
151-
s_allow_tag = allow_tag;
152-
s_size = sizeof(T);
153-
s_alignment = alignof(T);
154-
if (is_empty<T>::value && allow_tag) {
155-
s_size = 0;
156-
s_alignment = 0;
157-
}
158-
159-
s_reset_count = ecs_cpp_reset_count_get();
160-
}
161-
162136
// Obtain a component identifier for explicit component registration.
163-
static entity_t id_explicit(world_t *world = nullptr,
137+
static entity_t id_explicit(world_t *world,
164138
const char *name = nullptr, bool allow_tag = true, flecs::id_t id = 0,
165-
bool is_component = true, bool *existing = nullptr)
139+
bool is_component = true, bool *existing = nullptr, flecs::id_t s_id = 0)
166140
{
167-
if (!s_id) {
168-
// If no world was provided the component cannot be registered
169-
ecs_assert(world != nullptr, ECS_COMPONENT_NOT_REGISTERED, name);
170-
} else {
171-
ecs_assert(!id || s_id == id, ECS_INCONSISTENT_COMPONENT_ID, NULL);
172-
}
141+
ecs_assert(world != nullptr, ECS_INTERNAL_ERROR, name);
173142

174-
// If no id has been registered yet for the component (indicating the
175-
// component has not yet been registered, or the component is used
176-
// across more than one binary), or if the id does not exists in the
177-
// world (indicating a multi-world application), register it. */
178-
if (!s_id || (world && !ecs_exists(world, s_id))) {
179-
init(s_id ? s_id : id, allow_tag);
180-
181-
ecs_assert(!id || s_id == id, ECS_INTERNAL_ERROR, NULL);
143+
if (const ecs_cached_component_info_t* info = ecs_lookup_cached_component_info(world, cached_component_index.index)) {
144+
return info->component;
145+
} else {
146+
ecs_assert(!ecs_stage_is_readonly(ecs_get_world(world)), ECS_INVALID_WHILE_READONLY, name);
182147

148+
// If no id has been registered yet for the component (indicating the
149+
// component has not yet been registered), or if the id does not
150+
// exists in the world (indicating a multi-world application),
151+
// register it. */
183152
const char *symbol = nullptr;
184153
if (id) {
185154
symbol = ecs_get_symbol(world, id);
@@ -188,23 +157,37 @@ struct cpp_type_impl {
188157
symbol = symbol_name<T>();
189158
}
190159

191-
entity_t entity = ecs_cpp_component_register_explicit(
192-
world, s_id, id, name, type_name<T>(), symbol,
193-
s_size, s_alignment, is_component, existing);
160+
const bool is_tag = is_empty<T>::value && allow_tag;
161+
162+
const size_t component_size = is_tag ? 0U : size();
163+
const size_t component_alignment = is_tag ? 0U : alignment();
194164

195-
s_id = entity;
165+
const entity_t entity = ecs_cpp_component_register_explicit(
166+
world, s_id, id, name, type_name<T>(), symbol,
167+
component_size, component_alignment, is_component, existing);
168+
169+
// Component wasn't registered yet, set the values. Register component
170+
// name as the fully qualified flecs path.
171+
ecs_cached_component_info_t* inserted =
172+
ecs_get_or_create_cached_component_info(world, cached_component_index.index);
173+
174+
ecs_assert(!!inserted, ECS_INTERNAL_ERROR, NULL);
175+
ecs_assert(!ecs_is_cached_component_info_valid(inserted), ECS_INTERNAL_ERROR,
176+
NULL);
177+
178+
inserted->component = entity;
179+
inserted->size = static_cast<ecs_size_t>(component_size);
180+
inserted->alignment = static_cast<ecs_size_t>(component_alignment);
181+
182+
ecs_assert(ecs_is_cached_component_info_valid(inserted), ECS_INTERNAL_ERROR, NULL);
196183

197184
// If component is enum type, register constants
198185
#if FLECS_CPP_ENUM_REFLECTION_SUPPORT
199186
_::init_enum<T>(world, entity);
200187
#endif
201-
}
202-
203-
// By now the identifier must be valid and known with the world.
204-
ecs_assert(s_id != 0 && ecs_exists(world, s_id),
205-
ECS_INTERNAL_ERROR, NULL);
206188

207-
return s_id;
189+
return entity;
190+
}
208191
}
209192

210193
// Obtain a component identifier for implicit component registration. This
@@ -213,29 +196,31 @@ struct cpp_type_impl {
213196
// Additionally, implicit registration temporarily resets the scope & with
214197
// state of the world, so that the component is not implicitly created with
215198
// the scope/with of the code it happens to be first used by.
216-
static id_t id(world_t *world = nullptr, const char *name = nullptr,
199+
static id_t id(world_t *world, const char *name = nullptr,
217200
bool allow_tag = true)
218201
{
219-
// If no id has been registered yet, do it now.
220-
if (!registered(world)) {
221-
ecs_entity_t prev_scope = 0;
222-
ecs_id_t prev_with = 0;
223-
224-
if (world) {
225-
prev_scope = ecs_set_scope(world, 0);
226-
prev_with = ecs_set_with(world, 0);
227-
}
202+
ecs_assert(world != nullptr, ECS_INTERNAL_ERROR, name);
203+
204+
if (const ecs_cached_component_info_t* info = ecs_lookup_cached_component_info(world, cached_component_index.index)) {
205+
return info->component;
206+
} else {
207+
// If no id has been registered yet, do it now.
208+
const ecs_entity_t prev_scope = ecs_set_scope(world, 0);
209+
const ecs_id_t prev_with = ecs_set_with(world, 0);
228210

229211
// This will register a component id, but will not register
230212
// lifecycle callbacks.
231213
bool existing;
232-
id_explicit(world, name, allow_tag, 0, true, &existing);
214+
const entity_t id = id_explicit(world, name, allow_tag, 0, true, &existing);
215+
ecs_assert(id != 0, ECS_INTERNAL_ERROR, NULL);
216+
ecs_assert(ecs_lookup_cached_component_info(world, cached_component_index.index) != NULL,
217+
ECS_INTERNAL_ERROR, NULL);
233218

234219
// Register lifecycle callbacks, but only if the component has a
235220
// size. Components that don't have a size are tags, and tags don't
236221
// require construction/destruction/copy/move's. */
237222
if (size() && !existing) {
238-
register_lifecycle_actions<T>(world, s_id);
223+
register_lifecycle_actions<T>(world, id);
239224
}
240225

241226
if (prev_with) {
@@ -244,62 +229,40 @@ struct cpp_type_impl {
244229
if (prev_scope) {
245230
ecs_set_scope(world, prev_scope);
246231
}
247-
}
248-
249-
// By now we should have a valid identifier
250-
ecs_assert(s_id != 0, ECS_INTERNAL_ERROR, NULL);
251232

252-
return s_id;
233+
return id;
234+
}
253235
}
254236

255-
// Return the size of a component.
256-
static size_t size() {
257-
ecs_assert(s_id != 0, ECS_INTERNAL_ERROR, NULL);
258-
return s_size;
237+
/// Looks the assigned component up in the provided world.
238+
/// It can happen that the component has not been initialized yet.
239+
static entity_t lookup(const world_t* world) {
240+
const ecs_cached_component_info_t* info = ecs_lookup_cached_component_info(world, cached_component_index.index);
241+
return info ? info->component : 0;
259242
}
260243

261-
// Return the alignment of a component.
262-
static size_t alignment() {
263-
ecs_assert(s_id != 0, ECS_INTERNAL_ERROR, NULL);
264-
return s_alignment;
244+
// Was the component already registered.
245+
static bool registered(const world_t* world) {
246+
return !!lookup(world);
265247
}
266248

267-
// Was the component already registered.
268-
static bool registered(flecs::world_t *world) {
269-
if (s_reset_count != ecs_cpp_reset_count_get()) {
270-
reset();
271-
}
272-
if (s_id == 0) {
273-
return false;
274-
}
275-
if (world && !ecs_exists(world, s_id)) {
276-
return false;
277-
}
278-
return true;
249+
// Return the size of this component.
250+
static size_t size() {
251+
return sizeof(T);
279252
}
280253

281-
// This function is only used to test cross-translation unit features. No
282-
// code other than test cases should invoke this function.
283-
static void reset() {
284-
s_id = 0;
285-
s_size = 0;
286-
s_alignment = 0;
287-
s_allow_tag = true;
254+
// Return the alignment of this component.
255+
static size_t alignment() {
256+
return alignof(T);
288257
}
289258

290-
static entity_t s_id;
291-
static size_t s_size;
292-
static size_t s_alignment;
293-
static bool s_allow_tag;
294-
static int32_t s_reset_count;
259+
// Acquire a per instance incremental index for a world-local component index cache.
260+
static cpp_type_component_cache_index cached_component_index;
295261
};
296262

297263
// Global templated variables that hold component identifier and other info
298-
template <typename T> entity_t cpp_type_impl<T>::s_id;
299-
template <typename T> size_t cpp_type_impl<T>::s_size;
300-
template <typename T> size_t cpp_type_impl<T>::s_alignment;
301-
template <typename T> bool cpp_type_impl<T>::s_allow_tag( true );
302-
template <typename T> int32_t cpp_type_impl<T>::s_reset_count;
264+
template <typename T>
265+
cpp_type_component_cache_index cpp_type_impl<T>::cached_component_index;
303266

304267
// Front facing class for implicitly registering a component & obtaining
305268
// static component data
@@ -375,10 +338,9 @@ struct component : untyped_component {
375338
implicit_name = true;
376339
}
377340

378-
if (_::cpp_type<T>::registered(world)) {
379-
/* Obtain component id. Because the component is already registered,
380-
* this operation does nothing besides returning the existing id */
381-
id = _::cpp_type<T>::id_explicit(world, name, allow_tag, id);
341+
/* Obtain a registered component id. */
342+
if (const entity_t registered = _::cpp_type<T>::lookup(world)) {
343+
id = registered;
382344

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

415-
/* Initialize static component data */
416-
id = _::cpp_type<T>::id_explicit(world, name, allow_tag, id);
377+
if (!existing) {
378+
/* Initialize static component data */
379+
id = _::cpp_type<T>::id_explicit(world, name, allow_tag, id);
380+
} else {
381+
ecs_assert(ecs_is_valid(world, id), ECS_INTERNAL_ERROR, NULL);
382+
}
417383

418384
/* Initialize lifecycle actions (ctor, dtor, copy, move) */
419385
if (_::cpp_type<T>::size() && !existing) {
@@ -504,17 +470,6 @@ struct component : untyped_component {
504470
}
505471
};
506472

507-
/** Get id currently assigned to component. If no world has registered the
508-
* component yet, this operation will return 0. */
509-
template <typename T>
510-
flecs::entity_t type_id() {
511-
if (_::cpp_type<T>::s_reset_count == ecs_cpp_reset_count_get()) {
512-
return _::cpp_type<T>::s_id;
513-
} else {
514-
return 0;
515-
}
516-
}
517-
518473
/** Reset static component ids.
519474
* When components are registered their component ids are stored in a static
520475
* type specific variable. This stored id is passed into component registration
@@ -537,9 +492,9 @@ flecs::entity_t type_id() {
537492
*
538493
* \ingroup cpp_components
539494
*/
540-
inline void reset() {
541-
ecs_cpp_reset_count_inc();
542-
}
495+
ECS_DEPRECATED("reset was deprecated, world-local component ids "
496+
"are supported by default now.")
497+
inline void reset() {}
543498

544499
}
545500

0 commit comments

Comments
 (0)