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