Flecs v3.2
A fast entity component system (ECS) for C & C++
Loading...
Searching...
No Matches
component.hpp
Go to the documentation of this file.
1
6#pragma once
7
8#include <ctype.h>
9#include <stdio.h>
10
19namespace flecs {
20
21namespace _ {
22
23// Trick to obtain typename from type, as described here
24// https://blog.molecular-matters.com/2015/12/11/getting-the-type-of-a-template-argument-as-string-without-rtti/
25//
26// The code from the link has been modified to work with more types, and across
27// multiple compilers. The resulting string should be the same on all platforms
28// for all compilers.
29//
30
31#if defined(__GNUC__) || defined(_WIN32)
32template <typename T>
33inline const char* type_name() {
34 static const size_t len = ECS_FUNC_TYPE_LEN(const char*, type_name, ECS_FUNC_NAME);
35 static char result[len + 1] = {};
36 static const size_t front_len = ECS_FUNC_NAME_FRONT(const char*, type_name);
37 return ecs_cpp_get_type_name(result, ECS_FUNC_NAME, len, front_len);
38}
39#else
40#error "implicit component registration not supported"
41#endif
42
43// Translate a typename into a language-agnostic identifier. This allows for
44// registration of components/modules across language boundaries.
45template <typename T>
46inline const char* symbol_name() {
47 static const size_t len = ECS_FUNC_TYPE_LEN(const char*, symbol_name, ECS_FUNC_NAME);
48 static char result[len + 1] = {};
49 return ecs_cpp_get_symbol_name(result, type_name<T>(), len);
50}
51
52template <> inline const char* symbol_name<uint8_t>() {
53 return "u8";
54}
55template <> inline const char* symbol_name<uint16_t>() {
56 return "u16";
57}
58template <> inline const char* symbol_name<uint32_t>() {
59 return "u32";
60}
61template <> inline const char* symbol_name<uint64_t>() {
62 return "u64";
63}
64template <> inline const char* symbol_name<int8_t>() {
65 return "i8";
66}
67template <> inline const char* symbol_name<int16_t>() {
68 return "i16";
69}
70template <> inline const char* symbol_name<int32_t>() {
71 return "i32";
72}
73template <> inline const char* symbol_name<int64_t>() {
74 return "i64";
75}
76template <> inline const char* symbol_name<float>() {
77 return "f32";
78}
79template <> inline const char* symbol_name<double>() {
80 return "f64";
81}
82
83// If type is trivial, don't register lifecycle actions. While the functions
84// that obtain the lifecycle callback do detect whether the callback is required
85// adding a special case for trivial types eases the burden a bit on the
86// compiler as it reduces the number of templates to evaluate.
87template<typename T, enable_if_t<
88 std::is_trivial<T>::value == true
89 >* = nullptr>
90void register_lifecycle_actions(ecs_world_t*, ecs_entity_t) { }
91
92// If the component is non-trivial, register component lifecycle actions.
93// Depending on the type not all callbacks may be available.
94template<typename T, enable_if_t<
95 std::is_trivial<T>::value == false
96 >* = nullptr>
97void register_lifecycle_actions(
100{
101 ecs_type_hooks_t cl{};
102 cl.ctor = ctor<T>();
103 cl.dtor = dtor<T>();
104
105 cl.copy = copy<T>();
106 cl.copy_ctor = copy_ctor<T>();
107 cl.move = move<T>();
108 cl.move_ctor = move_ctor<T>();
109
110 cl.ctor_move_dtor = ctor_move_dtor<T>();
111 cl.move_dtor = move_dtor<T>();
112
114}
115
116// Class that manages component ids across worlds & binaries.
117// The cpp_type class stores the component id for a C++ type in a static global
118// variable that is shared between worlds. Whenever a component is used this
119// class will check if it already has been registered (has the global id been
120// set), and if not, register the component with the world.
121//
122// If the id has been set, the class will ensure it is known by the world. If it
123// is not known the component has been registered by another world and will be
124// registered with the world using the same id. If the id does exist, the class
125// will register it as a component, and verify whether the input is consistent.
126template <typename T>
128 static_assert(is_pointer<T>::value == false,
129 "pointer types are not allowed for components");
130 // Initialize component identifier
131 static void init(
132 entity_t entity,
133 bool allow_tag = true)
134 {
135 if (s_reset_count != ecs_cpp_reset_count_get()) {
136 reset();
137 }
138
139 // If an identifier was already set, check for consistency
140 if (s_id) {
141 ecs_assert(s_id == entity, ECS_INCONSISTENT_COMPONENT_ID,
142 type_name<T>());
143 ecs_assert(allow_tag == s_allow_tag, ECS_INVALID_PARAMETER, NULL);
144
145 // Component was already registered and data is consistent with new
146 // identifier, so nothing else to be done.
147 return;
148 }
149
150 // Component wasn't registered yet, set the values. Register component
151 // name as the fully qualified flecs path.
152 s_id = entity;
153 s_allow_tag = allow_tag;
154 s_size = sizeof(T);
155 s_alignment = alignof(T);
156 if (is_empty<T>::value && allow_tag) {
157 s_size = 0;
158 s_alignment = 0;
159 }
160
161 s_reset_count = ecs_cpp_reset_count_get();
162 }
163
164 // Obtain a component identifier for explicit component registration.
165 static entity_t id_explicit(world_t *world = nullptr,
166 const char *name = nullptr, bool allow_tag = true, flecs::id_t id = 0,
167 bool is_component = true, bool *existing = nullptr)
168 {
169 if (!s_id) {
170 // If no world was provided the component cannot be registered
171 ecs_assert(world != nullptr, ECS_COMPONENT_NOT_REGISTERED, name);
172 } else {
173 ecs_assert(!id || s_id == id, ECS_INCONSISTENT_COMPONENT_ID, NULL);
174 }
175
176 // If no id has been registered yet for the component (indicating the
177 // component has not yet been registered, or the component is used
178 // across more than one binary), or if the id does not exists in the
179 // world (indicating a multi-world application), register it.
180 if (!s_id || (world && !ecs_exists(world, s_id))) {
181 init(s_id ? s_id : id, allow_tag);
182
183 ecs_assert(!id || s_id == id, ECS_INTERNAL_ERROR, NULL);
184
185 const char *symbol = nullptr;
186 if (id) {
187 symbol = ecs_get_symbol(world, id);
188 }
189 if (!symbol) {
190 symbol = symbol_name<T>();
191 }
192
193 entity_t entity = ecs_cpp_component_register_explicit(
194 world, s_id, id, name, type_name<T>(), symbol,
195 s_size, s_alignment, is_component, existing);
196
197 s_id = entity;
198
199 // If component is enum type, register constants
200 #if FLECS_CPP_ENUM_REFLECTION_SUPPORT
201 _::init_enum<T>(world, entity);
202 #endif
203 }
204
205 // By now the identifier must be valid and known with the world.
206 ecs_assert(s_id != 0 && ecs_exists(world, s_id),
207 ECS_INTERNAL_ERROR, NULL);
208
209 return s_id;
210 }
211
212 // Obtain a component identifier for implicit component registration. This
213 // is almost the same as id_explicit, except that this operation
214 // automatically registers lifecycle callbacks.
215 // Additionally, implicit registration temporarily resets the scope & with
216 // state of the world, so that the component is not implicitly created with
217 // the scope/with of the code it happens to be first used by.
218 static id_t id(world_t *world = nullptr, const char *name = nullptr,
219 bool allow_tag = true)
220 {
221 // If no id has been registered yet, do it now.
222#ifndef FLECS_CPP_NO_AUTO_REGISTRATION
223 if (!registered(world)) {
224 ecs_entity_t prev_scope = 0;
225 ecs_id_t prev_with = 0;
226
227 if (world) {
228 prev_scope = ecs_set_scope(world, 0);
229 prev_with = ecs_set_with(world, 0);
230 }
231
232 // This will register a component id, but will not register
233 // lifecycle callbacks.
234 bool existing;
235 id_explicit(world, name, allow_tag, 0, true, &existing);
236
237 // Register lifecycle callbacks, but only if the component has a
238 // size. Components that don't have a size are tags, and tags don't
239 // require construction/destruction/copy/move's.
240 if (size() && !existing) {
241 register_lifecycle_actions<T>(world, s_id);
242 }
243
244 if (prev_with) {
245 ecs_set_with(world, prev_with);
246 }
247 if (prev_scope) {
248 ecs_set_scope(world, prev_scope);
249 }
250 }
251#else
252 (void)world;
253 (void)name;
254 (void)allow_tag;
255
256 ecs_assert(registered(world), ECS_INVALID_OPERATION,
257 "component '%s' was not registered before use",
258 type_name<T>());
259#endif
260
261 // By now we should have a valid identifier
262 ecs_assert(s_id != 0, ECS_INTERNAL_ERROR, NULL);
263
264 return s_id;
265 }
266
267 // Return the size of a component.
268 static size_t size() {
269 ecs_assert(s_id != 0, ECS_INTERNAL_ERROR, NULL);
270 return s_size;
271 }
272
273 // Return the alignment of a component.
274 static size_t alignment() {
275 ecs_assert(s_id != 0, ECS_INTERNAL_ERROR, NULL);
276 return s_alignment;
277 }
278
279 // Was the component already registered.
280 static bool registered(flecs::world_t *world) {
281 if (s_reset_count != ecs_cpp_reset_count_get()) {
282 reset();
283 }
284 if (s_id == 0) {
285 return false;
286 }
287 if (world && !ecs_exists(world, s_id)) {
288 return false;
289 }
290 return true;
291 }
292
293 // This function is only used to test cross-translation unit features. No
294 // code other than test cases should invoke this function.
295 static void reset() {
296 s_id = 0;
297 s_size = 0;
298 s_alignment = 0;
299 s_allow_tag = true;
300 }
301
302 static entity_t s_id;
303 static size_t s_size;
304 static size_t s_alignment;
305 static bool s_allow_tag;
306 static int32_t s_reset_count;
307};
308
309// Global templated variables that hold component identifier and other info
310template <typename T> entity_t cpp_type_impl<T>::s_id;
311template <typename T> size_t cpp_type_impl<T>::s_size;
312template <typename T> size_t cpp_type_impl<T>::s_alignment;
313template <typename T> bool cpp_type_impl<T>::s_allow_tag( true );
314template <typename T> int32_t cpp_type_impl<T>::s_reset_count;
315
316// Front facing class for implicitly registering a component & obtaining
317// static component data
318
319// Regular type
320template <typename T>
321struct cpp_type<T, if_not_t< is_pair<T>::value >>
322 : cpp_type_impl<base_type_t<T>> { };
323
324// Pair type
325template <typename T>
326struct cpp_type<T, if_t< is_pair<T>::value >>
327{
328 // Override id method to return id of pair
329 static id_t id(world_t *world = nullptr) {
330 return ecs_pair(
333 }
334};
335
336} // namespace _
337
344 using entity::entity;
345
346# ifdef FLECS_META
348# endif
349# ifdef FLECS_METRICS
350# include "mixins/metrics/untyped_component.inl"
351# endif
352};
353
359template <typename T>
371 flecs::world_t *world,
372 const char *name = nullptr,
373 bool allow_tag = true,
374 flecs::id_t id = 0)
375 {
376 const char *n = name;
377 bool implicit_name = false;
378 if (!n) {
379 n = _::type_name<T>();
380
381 /* Keep track of whether name was explicitly set. If not, and the
382 * component was already registered, just use the registered name.
383 *
384 * The registered name may differ from the typename as the registered
385 * name includes the flecs scope. This can in theory be different from
386 * the C++ namespace though it is good practice to keep them the same */
387 implicit_name = true;
388 }
389
391 /* Obtain component id. Because the component is already registered,
392 * this operation does nothing besides returning the existing id */
393 id = _::cpp_type<T>::id_explicit(world, name, allow_tag, id);
394
395 ecs_cpp_component_validate(world, id, n, _::symbol_name<T>(),
398 implicit_name);
399 } else {
400 /* If component is registered from an existing scope, ignore the
401 * namespace in the name of the component. */
402 if (implicit_name && (ecs_get_scope(world) != 0)) {
403 /* If the type is a template type, make sure to ignore ':'
404 * inside the template parameter list. */
405 const char *start = strchr(n, '<'), *last_elem = NULL;
406 if (start) {
407 const char *ptr = start;
408 while (ptr[0] && (ptr[0] != ':') && (ptr > n)) {
409 ptr --;
410 }
411 if (ptr[0] == ':') {
412 last_elem = ptr;
413 }
414 }
415 if (last_elem) {
416 name = last_elem + 1;
417 }
418 }
419
420 /* Find or register component */
421 bool existing;
422 id = ecs_cpp_component_register(world, id, n, _::symbol_name<T>(),
423 ECS_SIZEOF(T), ECS_ALIGNOF(T), implicit_name, &existing);
424
425 /* Initialize static component data */
426 id = _::cpp_type<T>::id_explicit(world, name, allow_tag, id);
427
428 /* Initialize lifecycle actions (ctor, dtor, copy, move) */
429 if (_::cpp_type<T>::size() && !existing) {
430 _::register_lifecycle_actions<T>(world, id);
431 }
432 }
433
434 m_world = world;
435 m_id = id;
436 }
437
439 template <typename Func>
440 component<T>& on_add(Func&& func) {
441 using Delegate = typename _::each_delegate<typename std::decay<Func>::type, T>;
442 flecs::type_hooks_t h = get_hooks();
443 ecs_assert(h.on_add == nullptr, ECS_INVALID_OPERATION,
444 "on_add hook is already set");
445 BindingCtx *ctx = get_binding_ctx(h);
446 h.on_add = Delegate::run_add;
447 ctx->on_add = FLECS_NEW(Delegate)(FLECS_FWD(func));
448 ctx->free_on_add = reinterpret_cast<ecs_ctx_free_t>(
449 _::free_obj<Delegate>);
450 ecs_set_hooks_id(m_world, m_id, &h);
451 return *this;
452 }
453
455 template <typename Func>
456 component<T>& on_remove(Func&& func) {
457 using Delegate = typename _::each_delegate<
458 typename std::decay<Func>::type, T>;
459 flecs::type_hooks_t h = get_hooks();
460 ecs_assert(h.on_remove == nullptr, ECS_INVALID_OPERATION,
461 "on_remove hook is already set");
462 BindingCtx *ctx = get_binding_ctx(h);
463 h.on_remove = Delegate::run_remove;
464 ctx->on_remove = FLECS_NEW(Delegate)(FLECS_FWD(func));
465 ctx->free_on_remove = reinterpret_cast<ecs_ctx_free_t>(
466 _::free_obj<Delegate>);
467 ecs_set_hooks_id(m_world, m_id, &h);
468 return *this;
469 }
470
472 template <typename Func>
473 component<T>& on_set(Func&& func) {
474 using Delegate = typename _::each_delegate<
475 typename std::decay<Func>::type, T>;
476 flecs::type_hooks_t h = get_hooks();
477 ecs_assert(h.on_set == nullptr, ECS_INVALID_OPERATION,
478 "on_set hook is already set");
479 BindingCtx *ctx = get_binding_ctx(h);
480 h.on_set = Delegate::run_set;
481 ctx->on_set = FLECS_NEW(Delegate)(FLECS_FWD(func));
482 ctx->free_on_set = reinterpret_cast<ecs_ctx_free_t>(
483 _::free_obj<Delegate>);
484 ecs_set_hooks_id(m_world, m_id, &h);
485 return *this;
486 }
487
488# ifdef FLECS_META
489# include "mixins/meta/component.inl"
490# endif
491
492private:
493 using BindingCtx = _::component_binding_ctx;
494
495 BindingCtx* get_binding_ctx(flecs::type_hooks_t& h){
496 BindingCtx *result = static_cast<BindingCtx*>(h.binding_ctx);
497 if (!result) {
498 result = FLECS_NEW(BindingCtx);
499 h.binding_ctx = result;
500 h.binding_ctx_free = reinterpret_cast<ecs_ctx_free_t>(
501 _::free_obj<BindingCtx>);
502 }
503 return result;
504 }
505
506 flecs::type_hooks_t get_hooks() {
507 const flecs::type_hooks_t* h = ecs_get_hooks_id(m_world, m_id);
508 if (h) {
509 return *h;
510 } else {
511 return {};
512 }
513 }
514};
515
518template <typename T>
519flecs::entity_t type_id() {
520 if (_::cpp_type<T>::s_reset_count == ecs_cpp_reset_count_get()) {
522 } else {
523 return 0;
524 }
525}
526
549inline void reset() {
550 ecs_cpp_reset_count_inc();
551}
552
553}
554
flecs::entity_t type_id()
Get id currently assigned to component.
ecs_entity_t ecs_set_with(ecs_world_t *world, ecs_id_t id)
Set current with id.
#define ecs_assert(condition, error_code,...)
Assert.
Definition log.h:351
const ecs_type_hooks_t * ecs_get_hooks_id(ecs_world_t *world, ecs_entity_t id)
Get hooks for component.
void ecs_set_hooks_id(ecs_world_t *world, ecs_entity_t id, const ecs_type_hooks_t *hooks)
Register hooks for component.
ecs_id_t ecs_entity_t
An entity identifier.
Definition flecs.h:318
struct ecs_world_t ecs_world_t
A world is the container for all ECS data and supporting features.
Definition flecs.h:362
uint64_t ecs_id_t
Ids are the things that can be added to an entity.
Definition flecs.h:311
void reset()
Reset static component ids.
transcribe_cv_t< remove_reference_t< P >, typename raw_type_t< P >::second > pair_second_t
Get pair::second from pair while preserving cv qualifiers.
Definition pair.hpp:92
transcribe_cv_t< remove_reference_t< P >, typename raw_type_t< P >::first > pair_first_t
Get pair::first from pair while preserving cv qualifiers.
Definition pair.hpp:88
void(* ecs_ctx_free_t)(void *ctx)
Function to cleanup context data.
Definition flecs.h:626
bool ecs_exists(const ecs_world_t *world, ecs_entity_t entity)
Test whether an entity exists.
ecs_entity_t ecs_get_scope(const ecs_world_t *world)
Get the current scope.
const char * ecs_get_symbol(const ecs_world_t *world, ecs_entity_t entity)
Get the symbol of an entity.
ecs_entity_t ecs_set_scope(ecs_world_t *world, ecs_entity_t scope)
Set the current scope.
Meta component mixin.
Type that contains component lifecycle callbacks.
Definition flecs.h:847
ecs_iter_action_t on_remove
Callback that is invoked when an instance of the component is removed.
Definition flecs.h:883
void * binding_ctx
Language binding context.
Definition flecs.h:886
ecs_iter_action_t on_set
Callback that is invoked when an instance of the component is set.
Definition flecs.h:878
ecs_xtor_t ctor
ctor
Definition flecs.h:848
ecs_iter_action_t on_add
Callback that is invoked when an instance of a component is added.
Definition flecs.h:873
ecs_ctx_free_t binding_ctx_free
Callback to free binding_ctx.
Definition flecs.h:889
Component class.
component< T > & on_remove(Func &&func)
Register on_remove hook.
component(flecs::world_t *world, const char *name=nullptr, bool allow_tag=true, flecs::id_t id=0)
Register a component.
component< T > & on_add(Func &&func)
Register on_add hook.
component< T > & on_set(Func &&func)
Register on_set hook.
flecs::string_view name() const
Return the entity name.
entity_t id() const
Get entity id.
Entity.
Definition entity.hpp:30
Class that wraps around a flecs::id_t.
Definition decl.hpp:27
Test if type is a pair.
Definition pair.hpp:82
Untyped component class.
The world.
Definition world.hpp:132