Skip to content
Flecs v4.1
enum.hpp
Go to the documentation of this file.
1/**
2 * @file addons/cpp/utils/enum.hpp
3 * @brief Compile-time enum reflection utilities.
4 *
5 * Discover at compile time the valid enumeration constants for an enumeration type
6 * and their names. This is used to automatically register enum constants.
7 */
8
9#include <utility>
10
11// 126, so that FLECS_ENUM_MAX_COUNT is 127, which is the largest value
12// representable by an int8_t.
13#define FLECS_ENUM_MAX(T) _::to_constant<T, 126>::value
14#define FLECS_ENUM_MAX_COUNT (FLECS_ENUM_MAX(int) + 1)
15
16// Flag to turn off enum reflection.
17#ifdef FLECS_CPP_NO_ENUM_REFLECTION
18#define FLECS_CPP_ENUM_REFLECTION_SUPPORT 0
19#endif
20
21// Test if we're using a compiler that supports the required features.
22#ifndef FLECS_CPP_ENUM_REFLECTION_SUPPORT
23#if !defined(__clang__) && defined(__GNUC__)
24#if __GNUC__ > 7 || (__GNUC__ == 7 && __GNUC_MINOR__ >= 5)
25#define FLECS_CPP_ENUM_REFLECTION_SUPPORT 1
26#else
27#define FLECS_CPP_ENUM_REFLECTION_SUPPORT 0
28#endif
29#else
30#define FLECS_CPP_ENUM_REFLECTION_SUPPORT 1
31#endif
32#endif
33
34#if defined(__clang__) && __clang_major__ >= 16
35// https://reviews.llvm.org/D130058, https://reviews.llvm.org/D131307
36#define flecs_enum_cast(T, v) __builtin_bit_cast(T, v)
37#elif defined(__GNUC__) && __GNUC__ > 10
38#define flecs_enum_cast(T, v) __builtin_bit_cast(T, v)
39#else
40#define flecs_enum_cast(T, v) static_cast<T>(v)
41#endif
42
43namespace flecs {
44
45/** Int to enum. */
46namespace _ {
47/** @private Convert an integral value to an enum constant. */
48template <typename E, underlying_type_t<E> Value>
49struct to_constant {
50 static constexpr E value = flecs_enum_cast(E, Value);
51};
52
53template <typename E, underlying_type_t<E> Value>
54constexpr E to_constant<E, Value>::value;
55}
56
57/** Convenience type with enum reflection data. */
58template <typename E>
59struct enum_data;
60
61/** Get enum reflection data for an enum type.
62 * @tparam E The enum type.
63 * @param world The world.
64 * @return Enum data wrapper.
65 */
66template <typename E>
67static enum_data<E> enum_type(flecs::world_t *world);
68
69/** Trait to define the last valid enum value for reflection.
70 * @tparam E The enum type.
71 */
72template <typename E>
73struct enum_last {
74 static constexpr E value = FLECS_ENUM_MAX(E);
75};
76
77/* Utility macro to override the enum_last trait. */
78#define FLECS_ENUM_LAST(T, Last)\
79 namespace flecs {\
80 template<>\
81 struct enum_last<T> {\
82 static constexpr T value = Last;\
83 };\
84 }
85
86namespace _ {
87
88#if defined(ECS_TARGET_CLANG) || defined(ECS_TARGET_GNU)
89template <typename E, E C>
90constexpr bool enum_constant_is_valid() {
91 const char *name = ECS_FUNC_NAME;
92 size_t pos = sizeof(ECS_FUNC_NAME) - 1;
93 while (pos && name[pos - 1] != '=') {
94 pos --;
95 }
96 while (name[pos] == ' ') {
97 pos ++;
98 }
99 return name[pos] != '(' && name[pos] != '-' &&
100 !(name[pos] >= '0' && name[pos] <= '9');
101}
102#else
103template <size_t N>
104constexpr size_t enum_template_arg_separator(
105 const char (&func_name)[N],
106 size_t pos,
107 size_t end,
108 size_t depth = 0)
109{
110 return pos >= end
111 ? end
112 : func_name[pos] == '<'
113 ? enum_template_arg_separator(func_name, pos + 1, end, depth + 1)
114 : func_name[pos] == '>'
115 ? enum_template_arg_separator(
116 func_name, pos + 1, end, depth ? depth - 1 : 0)
117 : (func_name[pos] == ',' && !depth)
118 ? pos
119 : enum_template_arg_separator(
120 func_name, pos + 1, end, depth);
121}
122
123/* Use a different trick on MSVC, since it uses a hexadecimal representation for
124 * invalid enum constants. We can leverage that MSVC inserts a C-style cast
125 * into the name. Find the template argument separator structurally instead of
126 * relying on the exact spelling of __FUNCSIG__ for the enum type. */
127template <typename E, E C>
128constexpr bool enum_constant_is_valid() {
129 return enum_template_arg_separator(
130 ECS_FUNC_NAME,
131 ECS_FUNC_NAME_FRONT(bool, enum_constant_is_valid),
132 sizeof(ECS_FUNC_NAME) - ECS_FUNC_NAME_BACK - 1u) <
133 (sizeof(ECS_FUNC_NAME) - ECS_FUNC_NAME_BACK - 1u) &&
134 ECS_FUNC_NAME[enum_template_arg_separator(
135 ECS_FUNC_NAME,
136 ECS_FUNC_NAME_FRONT(bool, enum_constant_is_valid),
137 sizeof(ECS_FUNC_NAME) - ECS_FUNC_NAME_BACK - 1u) + 1u] != '(';
138}
139#endif
140
141/** @private Wrapper for enum_constant_is_valid() using the underlying type. */
142template <typename E, underlying_type_t<E> C>
143constexpr bool enum_constant_is_valid_wrap() {
144 return enum_constant_is_valid<E, flecs_enum_cast(E, C)>();
145}
146
147/** @private Check if an enum constant is valid (value trait). */
148template <typename E, E C>
149struct enum_is_valid {
150 static constexpr bool value = enum_constant_is_valid<E, C>();
151};
152
153/** @private Extract the name of a constant from the compiler string. */
154template <typename E, E C>
155static const char* enum_constant_to_name() {
156 static const size_t len = ECS_FUNC_TYPE_LEN(
157 const char*, enum_constant_to_name, ECS_FUNC_NAME);
158 static char result[len + 1] = {};
160 result, ECS_FUNC_NAME, string::length(ECS_FUNC_NAME),
161 ECS_FUNC_NAME_BACK);
162}
163
164/** Enumeration constant data.
165 * @tparam T The underlying type of the enum.
166 */
167template<typename T>
169#ifdef FLECS_MULTI_WORLD
170 /** Global index used to obtain a world-local entity ID. */
171 int32_t index;
172#else
173 /** Entity ID for the constant. */
175#endif
176 /** The constant value. */
178 /** The constant name. */
179 const char *name;
180
181 bool discovered() const {
182#ifdef FLECS_MULTI_WORLD
183 return index != 0;
184#else
185 return name != nullptr || id != 0;
186#endif
187 }
188};
189
190/** @private Class that scans an enum for constants, extracts names, and creates entities. */
191template <typename E>
192struct enum_type {
193private:
194 using U = underlying_type_t<E>;
195 using UU = typename std::make_unsigned<U>::type;
196 static constexpr size_t linear_count =
197 static_cast<size_t>(enum_last<E>::value) + 1;
198
199 static constexpr size_t mask_start() {
200 size_t result = 0;
201 for (UU v = static_cast<UU>(enum_last<E>::value); v; v >>= 1) {
202 result ++;
203 }
204 return result;
205 }
206
207 using candidates = std::make_index_sequence<
208 linear_count + sizeof(U) * 8 - mask_start()>;
209
210 template <size_t I>
211 static constexpr U candidate() {
212 if constexpr (I < linear_count) {
213 return static_cast<U>(I);
214 } else {
215 return static_cast<U>(UU(1) << (I - linear_count + mask_start()));
216 }
217 }
218
219 template <size_t... I>
220 static constexpr unsigned int count_constants(std::index_sequence<I...>) {
221 bool valid[] = {enum_constant_is_valid_wrap<E, candidate<I>()>()...};
222 unsigned int result = 0;
223 for (bool value : valid) {
224 result += value;
225 }
226 return result;
227 }
228
229 template <U Value>
230 void add_constant() {
231 if constexpr (enum_constant_is_valid_wrap<E, Value>()) {
232 auto& constant = constants[++max];
233 if (static_cast<U>(max) == Value && contiguous_until == max) {
234 contiguous_until ++;
235 }
236 constant.value = Value;
237 constant.name = enum_constant_to_name<E, flecs_enum_cast(E, Value)>();
238#ifdef FLECS_MULTI_WORLD
239 constant.index = flecs_component_ids_index_get();
240#endif
241 }
242 }
243
244 template <size_t... I>
245 void init(std::index_sequence<I...>) {
246 int result[] = {(add_constant<candidate<I>()>(), 0)...};
247 (void)result;
248 }
249
250public:
251 enum_type() {
252#if FLECS_CPP_ENUM_REFLECTION_SUPPORT
253 init(candidates{});
254#endif
255 }
256
257 int index_by_value(U value) const {
258#ifdef FLECS_CPP_NO_ENUM_REFLECTION
259 return value >= 0 && static_cast<UU>(value) < static_cast<UU>(contiguous_until)
260 ? static_cast<int>(value) : -1;
261#else
262 if (value >= 0 && static_cast<UU>(value) < static_cast<UU>(contiguous_until)) {
263 return static_cast<int>(value);
264 }
265 for (int i = contiguous_until; i <= max; i ++) {
266 if (constants[i].value == value) {
267 return i;
268 }
269 }
270 return -1;
271#endif
272 }
273
274 /** Get the singleton instance of enum_type for the given enum. */
275 static enum_type<E>& get() {
276 static _::enum_type<E> instance;
277 return instance;
278 }
279
280#ifndef FLECS_MULTI_WORLD
281 /** Get entity for a given enum value. */
282 flecs::entity_t entity(E value) const {
283 int index = index_by_value(static_cast<U>(value));
284 if (index >= 0) {
285 return constants[index].id;
286 }
287 return 0;
288 }
289#endif
290
291 /** Register enum constants for a world. */
292 void register_for_world(flecs::world_t *world, flecs::entity_t id) {
293#if !FLECS_CPP_ENUM_REFLECTION_SUPPORT
294 ecs_abort(ECS_UNSUPPORTED, "enum reflection requires gcc 7.5 or higher")
295#endif
296
297 ecs_log_push();
298 ecs_cpp_enum_init(world, id, type<U>::id(world));
299
300 for (int v = 0; v <= max; v ++) {
301 if (constants[v].discovered()) {
303 type<E>::id(world), 0, constants[v].name, &constants[v].value,
304 type<U>::id(world), sizeof(U));
305
306#ifdef FLECS_MULTI_WORLD
307 flecs_component_ids_set(world, constants[v].index, constant);
308#else
309 constants[v].id = constant;
310#endif
311 }
312 }
313
314 ecs_log_pop();
315 }
316
317 int min = 0;
318 int max = -1;
319 int contiguous_until = 0;
320
321#if FLECS_CPP_ENUM_REFLECTION_SUPPORT
322 static constexpr unsigned int constants_size = count_constants(candidates{});
323 enum_constant<U> constants[constants_size ? constants_size : 1] = {};
324#else
325 static constexpr unsigned int constants_size = 0;
326 enum_constant<U> constants[128] = {};
327#endif
328};
329
330/** @private Initialize enum reflection for a world. */
331template <typename E>
332inline static void init_enum(flecs::world_t *world, flecs::entity_t id) {
333 (void)world; (void)id;
334 if constexpr (is_enum_v<E>) {
335 _::enum_type<E>::get().register_for_world(world, id);
336 }
337}
338
339} // namespace _
340
341/** Enumeration type data wrapper with world pointer. */
342template <typename E>
343struct enum_data {
344 using U = underlying_type_t<E>;
345
346 /** Construct enum_data from a world and an enum_type implementation. */
347 enum_data(flecs::world_t *world, _::enum_type<E>& impl)
348 : world_(world)
349 , impl_(impl) { }
350
351 /**
352 * @brief Check if a given integral value is a valid enum value.
353 *
354 * @param value The integral value.
355 * @return true If the value is a valid enum value.
356 * @return false If the value is not a valid enum value.
357 */
358 bool is_valid(U value) {
359 int index = index_by_value(value);
360 if (index < 0) {
361 return false;
362 }
363 return impl_.constants[index].discovered();
364 }
365
366 /**
367 * @brief Check if a given enum value is valid.
368 *
369 * @param value The enum value.
370 * @return true If the value is valid.
371 * @return false If the value is not valid.
372 */
373 bool is_valid(E value) {
374 return is_valid(static_cast<U>(value));
375 }
376
377 /**
378 * @brief Find the index into the constants array for a value, if one exists.
379 *
380 * @param value The underlying integral value.
381 * @return int The index of the enum value.
382 */
383 int index_by_value(U value) const {
384 return impl_.index_by_value(value);
385 }
386
387 /**
388 * @brief Find the index into the constants array for an enum value, if one exists.
389 *
390 * @param value The enum value.
391 * @return int The index of the enum value.
392 */
393 int index_by_value(E value) const {
394 return index_by_value(static_cast<U>(value));
395 }
396
397 /** Return the index of the first constant. */
398 int first() const {
399 return impl_.min;
400 }
401
402 /** Return the index of the last constant. */
403 int last() const {
404 return impl_.max;
405 }
406
407 /** Return the next constant index after the given one. */
408 int next(int cur) const {
409 return cur + 1;
410 }
411
412 /** Get entity for the enum type. */
413 flecs::entity entity() const;
414 /** Get entity for a given underlying enum value. */
415 flecs::entity entity(U value) const;
416 /** Get entity for a given enum value. */
417 flecs::entity entity(E value) const;
418
419 /**
420 * @brief Manually register a constant for an enum.
421 *
422 * If automatic enum reflection is not supported, provide a method for
423 * manually registering a constant.
424 */
425 #ifdef FLECS_CPP_NO_ENUM_REFLECTION
426 void register_constant(flecs::world_t *world, U v, flecs::entity_t e) {
427 if (v >= 0 && v < 128) {
428 int index = static_cast<int>(v);
429#ifdef FLECS_MULTI_WORLD
430 if (!impl_.constants[index].index) {
431 impl_.constants[index].index = flecs_component_ids_index_get();
432 }
433#endif
434
435#ifdef FLECS_MULTI_WORLD
436 flecs_component_ids_set(world, impl_.constants[index].index, e);
437#else
438 (void)world;
439 impl_.constants[index].id = e;
440#endif
441
442 impl_.max ++;
443
444 if (impl_.contiguous_until <= index) {
445 impl_.contiguous_until = index + 1;
446 }
447 }
448 }
449 #endif
450
452 _::enum_type<E>& impl_;
453};
454
455/** Convenience function for getting enum reflection data. */
456template <typename E>
458 _::type<E>::id(world); // Ensure the enum is registered.
459 auto& ref = _::enum_type<E>::get();
460 return enum_data<E>(world, ref);
461}
462
463} // namespace flecs
FLECS_API void ecs_cpp_enum_init(ecs_world_t *world, ecs_entity_t id, ecs_entity_t underlying_type)
Initialize a C++ enum type.
FLECS_API ecs_entity_t ecs_cpp_enum_constant_register(ecs_world_t *world, ecs_entity_t parent, ecs_entity_t id, const char *name, void *value, ecs_entity_t value_type, size_t value_size)
Register a C++ enum constant.
FLECS_API char * ecs_cpp_get_constant_name(char *constant_name, const char *func_name, size_t len, size_t back_len)
Get constant name from compiler-generated function name.
#define ECS_UNSUPPORTED
Unsupported error code.
Definition log.h:679
#define ecs_log_push()
Push log indentation at the default level.
Definition log.h:458
#define ecs_abort(error_code,...)
Abort.
Definition log.h:464
#define ecs_log_pop()
Pop log indentation at the default level.
Definition log.h:460
untyped_component & constant(const char *name, T value)
Add a constant.
ecs_entity_t entity_t
Entity type.
Definition c_types.hpp:21
ecs_world_t world_t
World type.
Definition c_types.hpp:18
Int to enum.
Definition component.hpp:18
Enumeration constant data.
Definition enum.hpp:168
const char * name
The constant name.
Definition enum.hpp:179
int32_t index
Global index used to obtain a world-local entity ID.
Definition enum.hpp:171
T value
The constant value.
Definition enum.hpp:177
flecs::string_view name() const
Return the entity name.
Entity.
Definition entity.hpp:30
entity()
Default constructor.
Definition entity.hpp:32
Convenience type with enum reflection data.
Definition enum.hpp:343
int last() const
Return the index of the last constant.
Definition enum.hpp:403
int next(int cur) const
Return the next constant index after the given one.
Definition enum.hpp:408
int index_by_value(E value) const
Find the index into the constants array for an enum value, if one exists.
Definition enum.hpp:393
bool is_valid(U value)
Check if a given integral value is a valid enum value.
Definition enum.hpp:358
enum_data(flecs::world_t *world, _::enum_type< E > &impl)
Construct enum_data from a world and an enum_type implementation.
Definition enum.hpp:347
flecs::world_t * world_
Manually register a constant for an enum.
Definition enum.hpp:451
int index_by_value(U value) const
Find the index into the constants array for a value, if one exists.
Definition enum.hpp:383
bool is_valid(E value)
Check if a given enum value is valid.
Definition enum.hpp:373
int first() const
Return the index of the first constant.
Definition enum.hpp:398
Trait to define the last valid enum value for reflection.
Definition enum.hpp:73
Class that wraps around a flecs::id_t.
Definition decl.hpp:27
Component reference.
Definition ref.hpp:109
std::size_t length() const
Return the string length.
Definition string.hpp:114
The world.
Definition world.hpp:129