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