Skip to content

Commit eb7d54f

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 4673dc2 commit eb7d54f

File tree

8 files changed

+206
-150
lines changed

8 files changed

+206
-150
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

+71-132
Original file line numberDiff line numberDiff line change
@@ -125,61 +125,20 @@ void register_lifecycle_actions(
125125
// will register it as a component, and verify whether the input is consistent.
126126
template <typename T>
127127
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-
162128
// Obtain a component identifier for explicit component registration.
163-
static entity_t id_explicit(world_t *world = nullptr,
129+
static entity_t id_explicit(world_t *world,
164130
const char *name = nullptr, bool allow_tag = true, flecs::id_t id = 0,
165131
bool is_component = true, bool *existing = nullptr)
166132
{
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-
}
173-
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);
133+
ecs_assert(world != nullptr, ECS_INTERNAL_ERROR, name);
182134

135+
if (const ecs_cached_component_info_t* info = ecs_lookup_cached_component_info(world, index)) {
136+
return info->component;
137+
} else {
138+
// If no id has been registered yet for the component (indicating the
139+
// component has not yet been registered), or if the id does not
140+
// exists in the world (indicating a multi-world application),
141+
// register it. */
183142
const char *symbol = nullptr;
184143
if (id) {
185144
symbol = ecs_get_symbol(world, id);
@@ -188,23 +147,37 @@ struct cpp_type_impl {
188147
symbol = symbol_name<T>();
189148
}
190149

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);
150+
const bool is_tag = is_empty<T>::value && allow_tag;
151+
152+
const size_t component_size = is_tag ? 0U : size();
153+
const size_t component_alignment = is_tag ? 0U : alignment();
154+
155+
const entity_t entity = ecs_cpp_component_register_explicit(
156+
world, 0, id, name, type_name<T>(), symbol,
157+
component_size, component_alignment, is_component, existing);
158+
159+
// Component wasn't registered yet, set the values. Register component
160+
// name as the fully qualified flecs path.
161+
ecs_cached_component_info_t* inserted =
162+
ecs_get_or_create_cached_component_info(world, index);
194163

195-
s_id = entity;
164+
ecs_assert(!!inserted, ECS_INTERNAL_ERROR, NULL);
165+
ecs_assert(!ecs_is_cached_component_info_valid(inserted), ECS_INTERNAL_ERROR,
166+
NULL);
167+
168+
inserted->component = entity;
169+
inserted->size = static_cast<ecs_size_t>(component_size);
170+
inserted->alignment = static_cast<ecs_size_t>(component_alignment);
171+
172+
ecs_assert(ecs_is_cached_component_info_valid(inserted), ECS_INTERNAL_ERROR, NULL);
196173

197174
// If component is enum type, register constants
198175
#if FLECS_CPP_ENUM_REFLECTION_SUPPORT
199176
_::init_enum<T>(world, entity);
200177
#endif
201-
}
202178

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);
206-
207-
return s_id;
179+
return entity;
180+
}
208181
}
209182

210183
// Obtain a component identifier for implicit component registration. This
@@ -213,29 +186,28 @@ struct cpp_type_impl {
213186
// Additionally, implicit registration temporarily resets the scope & with
214187
// state of the world, so that the component is not implicitly created with
215188
// 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,
189+
static id_t id(world_t *world, const char *name = nullptr,
217190
bool allow_tag = true)
218191
{
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-
}
192+
ecs_assert(world != nullptr, ECS_INTERNAL_ERROR, name);
193+
194+
if (const ecs_cached_component_info_t* info = ecs_lookup_cached_component_info(world, index)) {
195+
return info->component;
196+
} else {
197+
// If no id has been registered yet, do it now.
198+
const ecs_entity_t prev_scope = ecs_set_scope(world, 0);
199+
const ecs_id_t prev_with = ecs_set_with(world, 0);
228200

229201
// This will register a component id, but will not register
230202
// lifecycle callbacks.
231203
bool existing;
232-
id_explicit(world, name, allow_tag, 0, true, &existing);
204+
const entity_t id = id_explicit(world, name, allow_tag, 0, true, &existing);
233205

234206
// Register lifecycle callbacks, but only if the component has a
235207
// size. Components that don't have a size are tags, and tags don't
236208
// require construction/destruction/copy/move's. */
237209
if (size() && !existing) {
238-
register_lifecycle_actions<T>(world, s_id);
210+
register_lifecycle_actions<T>(world, id);
239211
}
240212

241213
if (prev_with) {
@@ -244,62 +216,41 @@ struct cpp_type_impl {
244216
if (prev_scope) {
245217
ecs_set_scope(world, prev_scope);
246218
}
247-
}
248-
249-
// By now we should have a valid identifier
250-
ecs_assert(s_id != 0, ECS_INTERNAL_ERROR, NULL);
251219

252-
return s_id;
220+
return id;
221+
}
253222
}
254223

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;
224+
/// Looks the assigned component up in the provided world.
225+
/// It can happen that the component has not been initialized yet.
226+
static entity_t lookup(const world_t* world) {
227+
const ecs_cached_component_info_t* info = ecs_lookup_cached_component_info(world, index);
228+
return info ? info->component : 0;
259229
}
260230

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;
231+
// Was the component already registered.
232+
static bool registered(const world_t* world) {
233+
return !!lookup(world);
265234
}
266235

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;
236+
// Return the size of this component.
237+
static size_t size() {
238+
return sizeof(T);
279239
}
280240

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;
241+
// Return the alignment of this component.
242+
static size_t alignment() {
243+
return alignof(T);
288244
}
289245

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;
246+
// Acquire a per instance incremental index for a world-local component id cache.
247+
static ecs_component_cache_index_t index;
295248
};
296249

297250
// 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;
251+
template <typename T>
252+
ecs_component_cache_index_t cpp_type_impl<T>::index{
253+
ecs_cpp_component_id_storage_add()};
303254

304255
// Front facing class for implicitly registering a component & obtaining
305256
// static component data
@@ -375,10 +326,9 @@ struct component : untyped_component {
375326
implicit_name = true;
376327
}
377328

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);
329+
/* Obtain a registered component id. */
330+
if (const entity_t registered = _::cpp_type<T>::lookup(world)) {
331+
id = registered;
382332

383333
ecs_cpp_component_validate(world, id, n, _::symbol_name<T>(),
384334
_::cpp_type<T>::size(),
@@ -504,17 +454,6 @@ struct component : untyped_component {
504454
}
505455
};
506456

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-
518457
/** Reset static component ids.
519458
* When components are registered their component ids are stored in a static
520459
* type specific variable. This stored id is passed into component registration
@@ -537,9 +476,9 @@ flecs::entity_t type_id() {
537476
*
538477
* \ingroup cpp_components
539478
*/
540-
inline void reset() {
541-
ecs_cpp_reset_count_inc();
542-
}
479+
ECS_DEPRECATED("ecs_cpp_reset_count_inc was deprecated, world-local component "
480+
"ids made it obsolete")
481+
inline void reset() {}
543482

544483
}
545484

include/flecs/addons/cpp/mixins/meta/impl.hpp

+8-6
Original file line numberDiff line numberDiff line change
@@ -64,18 +64,20 @@ inline void init(flecs::world& world) {
6464
// specific types.
6565

6666
if (!flecs::is_same<i32_t, iptr_t>() && !flecs::is_same<i64_t, iptr_t>()) {
67-
flecs::_::cpp_type<iptr_t>::init(flecs::Iptr, true);
68-
ecs_assert(flecs::type_id<iptr_t>() == flecs::Iptr,
69-
ECS_INTERNAL_ERROR, NULL);
67+
const entity_t id = flecs::_::cpp_type<iptr_t>::id_explicit(world, nullptr, flecs::Iptr, true);
68+
(void)id;
69+
70+
ecs_assert(id == flecs::Iptr, ECS_INTERNAL_ERROR, NULL);
7071
// Remove symbol to prevent validation errors, as it doesn't match with
7172
// the typename
7273
ecs_remove_pair(world, flecs::Iptr, ecs_id(EcsIdentifier), EcsSymbol);
7374
}
7475

7576
if (!flecs::is_same<u32_t, uptr_t>() && !flecs::is_same<u64_t, uptr_t>()) {
76-
flecs::_::cpp_type<uptr_t>::init(flecs::Uptr, true);
77-
ecs_assert(flecs::type_id<uptr_t>() == flecs::Uptr,
78-
ECS_INTERNAL_ERROR, NULL);
77+
const entity_t id = flecs::_::cpp_type<uptr_t>::id_explicit(world, nullptr, flecs::Uptr, true);
78+
(void)id;
79+
80+
ecs_assert(id == flecs::Uptr, ECS_INTERNAL_ERROR, NULL);
7981
// Remove symbol to prevent validation errors, as it doesn't match with
8082
// the typename
8183
ecs_remove_pair(world, flecs::Uptr, ecs_id(EcsIdentifier), EcsSymbol);

0 commit comments

Comments
 (0)