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>(cl.flags);
105 cl.dtor = dtor<T>(cl.flags);
106
107 cl.copy = copy<T>(cl.flags);
108 cl.copy_ctor = copy_ctor<T>(cl.flags);
109 cl.move = move<T>(cl.flags);
110 cl.move_ctor = move_ctor<T>(cl.flags);
111
112 cl.ctor_move_dtor = ctor_move_dtor<T>(cl.flags);
113 cl.move_dtor = move_dtor<T>(cl.flags);
114
116
117 if (cl.flags & (ECS_TYPE_HOOK_MOVE_ILLEGAL|ECS_TYPE_HOOK_MOVE_CTOR_ILLEGAL))
118 {
119 ecs_add_id(world, component, flecs::Sparse);
120 }
121}
122
123template <typename T>
124struct type_impl {
125 static_assert(is_pointer<T>::value == false,
126 "pointer types are not allowed for components");
127
128 // Initialize component identifier
129 static void init(
130 bool allow_tag = true)
131 {
132 s_index = flecs_component_ids_index_get();
133 s_allow_tag = allow_tag;
134 s_size = sizeof(T);
135 s_alignment = alignof(T);
136 if (is_empty<T>::value && allow_tag) {
137 s_size = 0;
138 s_alignment = 0;
139 }
140 }
141
142 static void init_builtin(
143 flecs::world_t *world,
144 flecs::entity_t id,
145 bool allow_tag = true)
146 {
147 init(allow_tag);
148 flecs_component_ids_set(world, s_index, id);
149 }
150
151 // Register component id.
152 static entity_t register_id(world_t *world,
153 const char *name = nullptr, bool allow_tag = true, flecs::id_t id = 0,
154 bool is_component = false, bool implicit_name = true, const char *n = nullptr,
155 flecs::entity_t module = 0)
156 {
157 if (!s_index) {
158 // This is the first time (in this binary image) that this type is
159 // being used. Generate a static index that will identify the type
160 // across worlds.
161 init(allow_tag);
162 ecs_assert(s_index != 0, ECS_INTERNAL_ERROR, NULL);
163 }
164
165 flecs::entity_t c = flecs_component_ids_get(world, s_index);
166
167 if (!c || !ecs_is_alive(world, c)) {
168 // When a component is implicitly registered, ensure that it's not
169 // registered in the current scope of the application/that "with"
170 // components get added to the component entity.
171 ecs_entity_t prev_scope = ecs_set_scope(world, module);
172 ecs_entity_t prev_with = ecs_set_with(world, 0);
173
174 // At this point it is possible that the type was already registered
175 // with the world, just not for this binary. The registration code
176 // uses the type symbol to check if it was already registered. Note
177 // that the symbol is separate from the typename, as an application
178 // can override a component name when registering a type.
179 bool existing = false;
180 c = ecs_cpp_component_find(
181 world, id, n, symbol_name<T>(), size(), alignment(),
182 implicit_name, &existing);
183
184 const char *symbol = nullptr;
185 if (c) {
186 symbol = ecs_get_symbol(world, c);
187 }
188 if (!symbol) {
189 symbol = symbol_name<T>();
190 }
191
192 c = ecs_cpp_component_register(world, c, c, name, type_name<T>(),
193 symbol, size(), alignment(), is_component, &existing);
194
195 ecs_set_with(world, prev_with);
196 ecs_set_scope(world, prev_scope);
197
198 // Register lifecycle callbacks, but only if the component has a
199 // size. Components that don't have a size are tags, and tags don't
200 // require construction/destruction/copy/move's.
201 if (size() && !existing) {
202 register_lifecycle_actions<T>(world, c);
203 }
204
205 // Set world local component id
206 flecs_component_ids_set(world, s_index, c);
207
208 // If component is enum type, register constants. Make sure to do
209 // this after setting the component id, because the enum code will
210 // be calling type<T>::id().
211 #if FLECS_CPP_ENUM_REFLECTION_SUPPORT
212 _::init_enum<T>(world, c);
213 #endif
214 }
215
216 ecs_assert(c != 0, ECS_INTERNAL_ERROR, NULL);
217
218 return c;
219 }
220
221 // Get type (component) id.
222 // If type was not yet registered and automatic registration is allowed,
223 // this function will also register the type.
224 static entity_t id(world_t *world)
225 {
226#ifdef FLECS_CPP_NO_AUTO_REGISTRATION
227 ecs_assert(registered(world), ECS_INVALID_OPERATION,
228 "component '%s' must be registered before use",
229 type_name<T>());
230
231 flecs::entity_t c = flecs_component_ids_get(world, s_index);
232 ecs_assert(c != 0, ECS_INTERNAL_ERROR, NULL);
233 ecs_assert(ecs_is_alive(world, c), ECS_INVALID_OPERATION,
234 "component '%s' was deleted, reregister before using",
235 type_name<T>());
236#else
237 flecs::entity_t c = flecs_component_ids_get_alive(world, s_index);
238 if (!c) {
239 c = register_id(world);
240 }
241#endif
242 return c;
243 }
244
245 // Return the size of a component.
246 static size_t size() {
247 ecs_assert(s_index != 0, ECS_INTERNAL_ERROR, NULL);
248 return s_size;
249 }
250
251 // Return the alignment of a component.
252 static size_t alignment() {
253 ecs_assert(s_index != 0, ECS_INTERNAL_ERROR, NULL);
254 return s_alignment;
255 }
256
257 // Was the component already registered.
258 static bool registered(flecs::world_t *world) {
259 ecs_assert(world != nullptr, ECS_INVALID_PARAMETER, NULL);
260
261 if (s_index == 0) {
262 return false;
263 }
264
265 if (!flecs_component_ids_get(world, s_index)) {
266 return false;
267 }
268
269 return true;
270 }
271
272 // This function is only used to test cross-translation unit features. No
273 // code other than test cases should invoke this function.
274 static void reset() {
275 s_index = 0;
276 s_size = 0;
277 s_alignment = 0;
278 s_allow_tag = true;
279 }
280
281 static int32_t s_index;
282 static size_t s_size;
283 static size_t s_alignment;
284 static bool s_allow_tag;
285};
286
287// Global templated variables that hold component identifier and other info
288template <typename T> int32_t type_impl<T>::s_index;
289template <typename T> size_t type_impl<T>::s_size;
290template <typename T> size_t type_impl<T>::s_alignment;
291template <typename T> bool type_impl<T>::s_allow_tag( true );
292
293// Front facing class for implicitly registering a component & obtaining
294// static component data
295
296// Regular type
297template <typename T>
298struct type<T, if_not_t< is_pair<T>::value >>
299 : type_impl<base_type_t<T>> { };
300
301// Pair type
302template <typename T>
303struct type<T, if_t< is_pair<T>::value >>
304{
305 // Override id method to return id of pair
306 static id_t id(world_t *world = nullptr) {
307 return ecs_pair(
308 type< pair_first_t<T> >::id(world),
309 type< pair_second_t<T> >::id(world));
310 }
311};
312
313} // namespace _
314
321 using entity::entity;
322
323# ifdef FLECS_META
325# endif
326# ifdef FLECS_METRICS
327# include "mixins/metrics/untyped_component.inl"
328# endif
329};
330
336template <typename T>
348 flecs::world_t *world,
349 const char *name = nullptr,
350 bool allow_tag = true,
351 flecs::id_t id = 0)
352 {
353 const char *n = name;
354 bool implicit_name = false;
355 if (!n) {
356 n = _::type_name<T>();
357
358 // Keep track of whether name was explicitly set. If not, and the
359 // component was already registered, just use the registered name.
360 // The registered name may differ from the typename as the registered
361 // name includes the flecs scope. This can in theory be different from
362 // the C++ namespace though it is good practice to keep them the same */
363 implicit_name = true;
364 }
365
366 // If component is registered by module, ensure it's registered in the
367 // scope of the module.
368 flecs::entity_t module = ecs_get_scope(world);
369
370 // Strip off the namespace part of the component name, unless a name was
371 // explicitly provided by the application.
372 if (module && implicit_name) {
373 // If the type is a template type, make sure to ignore
374 // inside the template parameter list.
375 const char *start = strchr(n, '<'), *last_elem = NULL;
376 if (start) {
377 const char *ptr = start;
378 while (ptr[0] && (ptr[0] != ':') && (ptr > n)) {
379 ptr --;
380 }
381 if (ptr[0] == ':') {
382 last_elem = ptr;
383 }
384 }
385
386 if (last_elem) {
387 name = last_elem + 1;
388 }
389 }
390
391 world_ = world;
393 world, name, allow_tag, id, true, implicit_name, n, module);
394 }
395
397 template <typename Func>
398 component<T>& on_add(Func&& func) {
399 using Delegate = typename _::each_delegate<typename std::decay<Func>::type, T>;
400 flecs::type_hooks_t h = get_hooks();
401 ecs_assert(h.on_add == nullptr, ECS_INVALID_OPERATION,
402 "on_add hook is already set");
403 BindingCtx *ctx = get_binding_ctx(h);
404 h.on_add = Delegate::run_add;
405 ctx->on_add = FLECS_NEW(Delegate)(FLECS_FWD(func));
406 ctx->free_on_add = _::free_obj<Delegate>;
407 ecs_set_hooks_id(world_, id_, &h);
408 return *this;
409 }
410
412 template <typename Func>
413 component<T>& on_remove(Func&& func) {
414 using Delegate = typename _::each_delegate<
415 typename std::decay<Func>::type, T>;
416 flecs::type_hooks_t h = get_hooks();
417 ecs_assert(h.on_remove == nullptr, ECS_INVALID_OPERATION,
418 "on_remove hook is already set");
419 BindingCtx *ctx = get_binding_ctx(h);
420 h.on_remove = Delegate::run_remove;
421 ctx->on_remove = FLECS_NEW(Delegate)(FLECS_FWD(func));
422 ctx->free_on_remove = _::free_obj<Delegate>;
423 ecs_set_hooks_id(world_, id_, &h);
424 return *this;
425 }
426
428 template <typename Func>
429 component<T>& on_set(Func&& func) {
430 using Delegate = typename _::each_delegate<
431 typename std::decay<Func>::type, T>;
432 flecs::type_hooks_t h = get_hooks();
433 ecs_assert(h.on_set == nullptr, ECS_INVALID_OPERATION,
434 "on_set hook is already set");
435 BindingCtx *ctx = get_binding_ctx(h);
436 h.on_set = Delegate::run_set;
437 ctx->on_set = FLECS_NEW(Delegate)(FLECS_FWD(func));
438 ctx->free_on_set = _::free_obj<Delegate>;
439 ecs_set_hooks_id(world_, id_, &h);
440 return *this;
441 }
442
443# ifdef FLECS_META
444# include "mixins/meta/component.inl"
445# endif
446
447private:
448 using BindingCtx = _::component_binding_ctx;
449
450 BindingCtx* get_binding_ctx(flecs::type_hooks_t& h){
451 BindingCtx *result = static_cast<BindingCtx*>(h.binding_ctx);
452 if (!result) {
453 result = FLECS_NEW(BindingCtx);
454 h.binding_ctx = result;
455 h.binding_ctx_free = _::free_obj<BindingCtx>;
456 }
457 return result;
458 }
459
460 flecs::type_hooks_t get_hooks() {
461 const flecs::type_hooks_t* h = ecs_get_hooks_id(world_, id_);
462 if (h) {
463 return *h;
464 } else {
465 return {};
466 }
467 }
468};
469
470}
471
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:346
struct ecs_world_t ecs_world_t
A world is the container for all ECS data and supporting features.
Definition flecs.h:390
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_is_alive(const ecs_world_t *world, ecs_entity_t e)
Test whether an entity is alive.
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.
ecs_iter_action_t on_remove
Callback that is invoked when an instance of the component is removed.
Definition flecs.h:939
void * binding_ctx
Language binding context.
Definition flecs.h:942
ecs_iter_action_t on_set
Callback that is invoked when an instance of the component is set.
Definition flecs.h:934
ecs_xtor_t ctor
ctor
Definition flecs.h:898
ecs_iter_action_t on_add
Callback that is invoked when an instance of a component is added.
Definition flecs.h:929
ecs_ctx_free_t binding_ctx_free
Callback to free binding_ctx.
Definition flecs.h:946
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.
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