/** * @file addons/cpp/component.hpp * @brief Registering/obtaining info from components. */ #pragma once #include #include /** * @defgroup cpp_components Components * @brief Registering and working with components. * * \ingroup cpp_core * @{ */ namespace flecs { namespace _ { // Trick to obtain typename from type, as described here // https://blog.molecular-matters.com/2015/12/11/getting-the-type-of-a-template-argument-as-string-without-rtti/ // // The code from the link has been modified to work with more types, and across // multiple compilers. The resulting string should be the same on all platforms // for all compilers. // #if defined(__GNUC__) || defined(_WIN32) template inline static const char* type_name() { static const size_t len = ECS_FUNC_TYPE_LEN(const char*, type_name, ECS_FUNC_NAME); static char result[len + 1] = {}; static const size_t front_len = ECS_FUNC_NAME_FRONT(const char*, type_name); return ecs_cpp_get_type_name(result, ECS_FUNC_NAME, len, front_len); } #else #error "implicit component registration not supported" #endif // Translate a typename into a language-agnostic identifier. This allows for // registration of components/modules across language boundaries. template inline static const char* symbol_name() { static const size_t len = ECS_FUNC_TYPE_LEN(const char*, symbol_name, ECS_FUNC_NAME); static char result[len + 1] = {}; return ecs_cpp_get_symbol_name(result, type_name(), len); } template <> inline const char* symbol_name() { return "u8"; } template <> inline const char* symbol_name() { return "u16"; } template <> inline const char* symbol_name() { return "u32"; } template <> inline const char* symbol_name() { return "u64"; } template <> inline const char* symbol_name() { return "i8"; } template <> inline const char* symbol_name() { return "i16"; } template <> inline const char* symbol_name() { return "i32"; } template <> inline const char* symbol_name() { return "i64"; } template <> inline const char* symbol_name() { return "f32"; } template <> inline const char* symbol_name() { return "f64"; } // If type is trivial, don't register lifecycle actions. While the functions // that obtain the lifecycle callback do detect whether the callback is required // adding a special case for trivial types eases the burden a bit on the // compiler as it reduces the number of templates to evaluate. template::value == true >* = nullptr> void register_lifecycle_actions(ecs_world_t*, ecs_entity_t) { } // If the component is non-trivial, register component lifecycle actions. // Depending on the type not all callbacks may be available. template::value == false >* = nullptr> void register_lifecycle_actions( ecs_world_t *world, ecs_entity_t component) { ecs_type_hooks_t cl{}; cl.ctor = ctor(); cl.dtor = dtor(); cl.copy = copy(); cl.copy_ctor = copy_ctor(); cl.move = move(); cl.move_ctor = move_ctor(); cl.ctor_move_dtor = ctor_move_dtor(); cl.move_dtor = move_dtor(); ecs_set_hooks_id( world, component, &cl); } // Class that manages component ids across worlds & binaries. // The cpp_type class stores the component id for a C++ type in a static global // variable that is shared between worlds. Whenever a component is used this // class will check if it already has been registered (has the global id been // set), and if not, register the component with the world. // // If the id has been set, the class will ensure it is known by the world. If it // is not known the component has been registered by another world and will be // registered with the world using the same id. If the id does exist, the class // will register it as a component, and verify whether the input is consistent. template struct cpp_type_impl { // Initialize component identifier static void init( entity_t entity, bool allow_tag = true) { if (s_reset_count != ecs_cpp_reset_count_get()) { reset(); } // If an identifier was already set, check for consistency if (s_id) { ecs_assert(s_id == entity, ECS_INCONSISTENT_COMPONENT_ID, type_name()); ecs_assert(allow_tag == s_allow_tag, ECS_INVALID_PARAMETER, NULL); // Component was already registered and data is consistent with new // identifier, so nothing else to be done. return; } // Component wasn't registered yet, set the values. Register component // name as the fully qualified flecs path. s_id = entity; s_allow_tag = allow_tag; s_size = sizeof(T); s_alignment = alignof(T); if (is_empty::value && allow_tag) { s_size = 0; s_alignment = 0; } s_reset_count = ecs_cpp_reset_count_get(); } // Obtain a component identifier for explicit component registration. static entity_t id_explicit(world_t *world = nullptr, const char *name = nullptr, bool allow_tag = true, flecs::id_t id = 0, bool is_component = true, bool *existing = nullptr) { if (!s_id) { // If no world was provided the component cannot be registered ecs_assert(world != nullptr, ECS_COMPONENT_NOT_REGISTERED, name); } else { ecs_assert(!id || s_id == id, ECS_INCONSISTENT_COMPONENT_ID, NULL); } // If no id has been registered yet for the component (indicating the // component has not yet been registered, or the component is used // across more than one binary), or if the id does not exists in the // world (indicating a multi-world application), register it. */ if (!s_id || (world && !ecs_exists(world, s_id))) { init(s_id ? s_id : id, allow_tag); ecs_assert(!id || s_id == id, ECS_INTERNAL_ERROR, NULL); const char *symbol = nullptr; if (id) { symbol = ecs_get_symbol(world, id); } if (!symbol) { symbol = symbol_name(); } entity_t entity = ecs_cpp_component_register_explicit( world, s_id, id, name, type_name(), symbol, s_size, s_alignment, is_component, existing); s_id = entity; // If component is enum type, register constants #if FLECS_CPP_ENUM_REFLECTION_SUPPORT _::init_enum(world, entity); #endif } // By now the identifier must be valid and known with the world. ecs_assert(s_id != 0 && ecs_exists(world, s_id), ECS_INTERNAL_ERROR, NULL); return s_id; } // Obtain a component identifier for implicit component registration. This // is almost the same as id_explicit, except that this operation // automatically registers lifecycle callbacks. // Additionally, implicit registration temporarily resets the scope & with // state of the world, so that the component is not implicitly created with // the scope/with of the code it happens to be first used by. static id_t id(world_t *world = nullptr, const char *name = nullptr, bool allow_tag = true) { // If no id has been registered yet, do it now. if (!registered(world)) { ecs_entity_t prev_scope = 0; ecs_id_t prev_with = 0; if (world) { prev_scope = ecs_set_scope(world, 0); prev_with = ecs_set_with(world, 0); } // This will register a component id, but will not register // lifecycle callbacks. bool existing; id_explicit(world, name, allow_tag, 0, true, &existing); // Register lifecycle callbacks, but only if the component has a // size. Components that don't have a size are tags, and tags don't // require construction/destruction/copy/move's. */ if (size() && !existing) { register_lifecycle_actions(world, s_id); } if (prev_with) { ecs_set_with(world, prev_with); } if (prev_scope) { ecs_set_scope(world, prev_scope); } } // By now we should have a valid identifier ecs_assert(s_id != 0, ECS_INTERNAL_ERROR, NULL); return s_id; } // Return the size of a component. static size_t size() { ecs_assert(s_id != 0, ECS_INTERNAL_ERROR, NULL); return s_size; } // Return the alignment of a component. static size_t alignment() { ecs_assert(s_id != 0, ECS_INTERNAL_ERROR, NULL); return s_alignment; } // Was the component already registered. static bool registered(flecs::world_t *world) { if (s_reset_count != ecs_cpp_reset_count_get()) { reset(); } if (s_id == 0) { return false; } if (world && !ecs_exists(world, s_id)) { return false; } return true; } // This function is only used to test cross-translation unit features. No // code other than test cases should invoke this function. static void reset() { s_id = 0; s_size = 0; s_alignment = 0; s_allow_tag = true; } static entity_t s_id; static size_t s_size; static size_t s_alignment; static bool s_allow_tag; static int32_t s_reset_count; }; // Global templated variables that hold component identifier and other info template entity_t cpp_type_impl::s_id; template size_t cpp_type_impl::s_size; template size_t cpp_type_impl::s_alignment; template bool cpp_type_impl::s_allow_tag( true ); template int32_t cpp_type_impl::s_reset_count; // Front facing class for implicitly registering a component & obtaining // static component data // Regular type template struct cpp_type::value >> : cpp_type_impl> { }; // Pair type template struct cpp_type::value >> { // Override id method to return id of pair static id_t id(world_t *world = nullptr) { return ecs_pair( cpp_type< pair_first_t >::id(world), cpp_type< pair_second_t >::id(world)); } }; } // namespace _ /** Untyped component class. * Generic base class for flecs::component. * * \ingroup cpp_components */ struct untyped_component : entity { using entity::entity; # ifdef FLECS_META # include "mixins/meta/untyped_component.inl" # endif # ifdef FLECS_METRICS # include "mixins/metrics/untyped_component.inl" # endif }; /** Component class. * Class used to register components and component metadata. * * \ingroup cpp_components */ template struct component : untyped_component { /** Register a component. * If the component was already registered, this operation will return a handle * to the existing component. * * @param world The world for which to register the component. * @param name Optional name (overrides typename). * @param allow_tag If true, empty types will be registered with size 0. * @param id Optional id to register component with. */ component( flecs::world_t *world, const char *name = nullptr, bool allow_tag = true, flecs::id_t id = 0) { const char *n = name; bool implicit_name = false; if (!n) { n = _::type_name(); /* Keep track of whether name was explicitly set. If not, and the * component was already registered, just use the registered name. * * The registered name may differ from the typename as the registered * name includes the flecs scope. This can in theory be different from * the C++ namespace though it is good practice to keep them the same */ implicit_name = true; } if (_::cpp_type::registered(world)) { /* Obtain component id. Because the component is already registered, * this operation does nothing besides returning the existing id */ id = _::cpp_type::id_explicit(world, name, allow_tag, id); ecs_cpp_component_validate(world, id, n, _::symbol_name(), _::cpp_type::size(), _::cpp_type::alignment(), implicit_name); } else { /* If component is registered from an existing scope, ignore the * namespace in the name of the component. */ if (implicit_name && (ecs_get_scope(world) != 0)) { /* If the type is a template type, make sure to ignore ':' * inside the template parameter list. */ const char *start = strchr(n, '<'), *last_elem = NULL; if (start) { const char *ptr = start; while (ptr[0] && (ptr[0] != ':') && (ptr > n)) { ptr --; } if (ptr[0] == ':') { last_elem = ptr; } } else { last_elem = strrchr(n, ':'); } if (last_elem) { name = last_elem + 1; } } /* Find or register component */ bool existing; id = ecs_cpp_component_register(world, id, n, _::symbol_name(), ECS_SIZEOF(T), ECS_ALIGNOF(T), implicit_name, &existing); /* Initialize static component data */ id = _::cpp_type::id_explicit(world, name, allow_tag, id); /* Initialize lifecycle actions (ctor, dtor, copy, move) */ if (_::cpp_type::size() && !existing) { _::register_lifecycle_actions(world, id); } } m_world = world; m_id = id; } /** Register on_add hook. */ template component& on_add(Func&& func) { using Delegate = typename _::each_delegate< typename std::decay::type, T>; flecs::type_hooks_t h = get_hooks(); ecs_assert(h.on_add == nullptr, ECS_INVALID_OPERATION, "on_add hook is already set"); BindingCtx *ctx = get_binding_ctx(h); h.on_add = Delegate::run_add; ctx->on_add = FLECS_NEW(Delegate)(FLECS_FWD(func)); ctx->free_on_add = reinterpret_cast( _::free_obj); ecs_set_hooks_id(m_world, m_id, &h); return *this; } /** Register on_remove hook. */ template component& on_remove(Func&& func) { using Delegate = typename _::each_delegate< typename std::decay::type, T>; flecs::type_hooks_t h = get_hooks(); ecs_assert(h.on_remove == nullptr, ECS_INVALID_OPERATION, "on_remove hook is already set"); BindingCtx *ctx = get_binding_ctx(h); h.on_remove = Delegate::run_remove; ctx->on_remove = FLECS_NEW(Delegate)(FLECS_FWD(func)); ctx->free_on_remove = reinterpret_cast( _::free_obj); ecs_set_hooks_id(m_world, m_id, &h); return *this; } /** Register on_set hook. */ template component& on_set(Func&& func) { using Delegate = typename _::each_delegate< typename std::decay::type, T>; flecs::type_hooks_t h = get_hooks(); ecs_assert(h.on_set == nullptr, ECS_INVALID_OPERATION, "on_set hook is already set"); BindingCtx *ctx = get_binding_ctx(h); h.on_set = Delegate::run_set; ctx->on_set = FLECS_NEW(Delegate)(FLECS_FWD(func)); ctx->free_on_set = reinterpret_cast( _::free_obj); ecs_set_hooks_id(m_world, m_id, &h); return *this; } # ifdef FLECS_META # include "mixins/meta/component.inl" # endif private: using BindingCtx = _::component_binding_ctx; BindingCtx* get_binding_ctx(flecs::type_hooks_t& h){ BindingCtx *result = static_cast(h.binding_ctx); if (!result) { result = FLECS_NEW(BindingCtx); h.binding_ctx = result; h.binding_ctx_free = reinterpret_cast( _::free_obj); } return result; } flecs::type_hooks_t get_hooks() { const flecs::type_hooks_t* h = ecs_get_hooks_id(m_world, m_id); if (h) { return *h; } else { return {}; } } }; /** Get id currently assigned to component. If no world has registered the * component yet, this operation will return 0. */ template flecs::entity_t type_id() { if (_::cpp_type::s_reset_count == ecs_cpp_reset_count_get()) { return _::cpp_type::s_id; } else { return 0; } } /** Reset static component ids. * When components are registered their component ids are stored in a static * type specific variable. This stored id is passed into component registration * functions to ensure consistent ids across worlds. * * In some cases this can be undesirable, like when a process repeatedly creates * worlds with different components. A typical example where this can happen is * when running multiple tests in a single process, where each test registers * its own set of components. * * This operation can be used to prevent reusing of component ids and force * generating a new ids upon registration. * * Note that this operation should *never* be called while there are still * alive worlds in a process. Doing so results in undefined behavior. * * Also note that this operation does not actually change the static component * variables. It only ensures that the next time a component id is requested, a * new id will be generated. * * \ingroup cpp_components */ inline void reset() { ecs_cpp_reset_count_inc(); } } /** @} */