547 lines
18 KiB
C++
547 lines
18 KiB
C++
/**
|
|
* @file addons/cpp/component.hpp
|
|
* @brief Registering/obtaining info from components.
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <ctype.h>
|
|
#include <stdio.h>
|
|
|
|
/**
|
|
* @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 <typename T>
|
|
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 <typename T>
|
|
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<T>(), len);
|
|
}
|
|
|
|
template <> inline const char* symbol_name<uint8_t>() {
|
|
return "u8";
|
|
}
|
|
template <> inline const char* symbol_name<uint16_t>() {
|
|
return "u16";
|
|
}
|
|
template <> inline const char* symbol_name<uint32_t>() {
|
|
return "u32";
|
|
}
|
|
template <> inline const char* symbol_name<uint64_t>() {
|
|
return "u64";
|
|
}
|
|
template <> inline const char* symbol_name<int8_t>() {
|
|
return "i8";
|
|
}
|
|
template <> inline const char* symbol_name<int16_t>() {
|
|
return "i16";
|
|
}
|
|
template <> inline const char* symbol_name<int32_t>() {
|
|
return "i32";
|
|
}
|
|
template <> inline const char* symbol_name<int64_t>() {
|
|
return "i64";
|
|
}
|
|
template <> inline const char* symbol_name<float>() {
|
|
return "f32";
|
|
}
|
|
template <> inline const char* symbol_name<double>() {
|
|
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<typename T, enable_if_t<
|
|
std::is_trivial<T>::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<typename T, enable_if_t<
|
|
std::is_trivial<T>::value == false
|
|
>* = nullptr>
|
|
void register_lifecycle_actions(
|
|
ecs_world_t *world,
|
|
ecs_entity_t component)
|
|
{
|
|
ecs_type_hooks_t cl{};
|
|
cl.ctor = ctor<T>();
|
|
cl.dtor = dtor<T>();
|
|
|
|
cl.copy = copy<T>();
|
|
cl.copy_ctor = copy_ctor<T>();
|
|
cl.move = move<T>();
|
|
cl.move_ctor = move_ctor<T>();
|
|
|
|
cl.ctor_move_dtor = ctor_move_dtor<T>();
|
|
cl.move_dtor = move_dtor<T>();
|
|
|
|
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 <typename T>
|
|
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<T>());
|
|
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<T>::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<T>();
|
|
}
|
|
|
|
entity_t entity = ecs_cpp_component_register_explicit(
|
|
world, s_id, id, name, type_name<T>(), 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<T>(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<T>(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 <typename T> entity_t cpp_type_impl<T>::s_id;
|
|
template <typename T> size_t cpp_type_impl<T>::s_size;
|
|
template <typename T> size_t cpp_type_impl<T>::s_alignment;
|
|
template <typename T> bool cpp_type_impl<T>::s_allow_tag( true );
|
|
template <typename T> int32_t cpp_type_impl<T>::s_reset_count;
|
|
|
|
// Front facing class for implicitly registering a component & obtaining
|
|
// static component data
|
|
|
|
// Regular type
|
|
template <typename T>
|
|
struct cpp_type<T, if_not_t< is_pair<T>::value >>
|
|
: cpp_type_impl<base_type_t<T>> { };
|
|
|
|
// Pair type
|
|
template <typename T>
|
|
struct cpp_type<T, if_t< is_pair<T>::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<T> >::id(world),
|
|
cpp_type< pair_second_t<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 <typename T>
|
|
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<T>();
|
|
|
|
/* 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<T>::registered(world)) {
|
|
/* Obtain component id. Because the component is already registered,
|
|
* this operation does nothing besides returning the existing id */
|
|
id = _::cpp_type<T>::id_explicit(world, name, allow_tag, id);
|
|
|
|
ecs_cpp_component_validate(world, id, n, _::symbol_name<T>(),
|
|
_::cpp_type<T>::size(),
|
|
_::cpp_type<T>::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<T>(),
|
|
ECS_SIZEOF(T), ECS_ALIGNOF(T), implicit_name, &existing);
|
|
|
|
/* Initialize static component data */
|
|
id = _::cpp_type<T>::id_explicit(world, name, allow_tag, id);
|
|
|
|
/* Initialize lifecycle actions (ctor, dtor, copy, move) */
|
|
if (_::cpp_type<T>::size() && !existing) {
|
|
_::register_lifecycle_actions<T>(world, id);
|
|
}
|
|
}
|
|
|
|
m_world = world;
|
|
m_id = id;
|
|
}
|
|
|
|
/** Register on_add hook. */
|
|
template <typename Func>
|
|
component<T>& on_add(Func&& func) {
|
|
using Delegate = typename _::each_delegate<
|
|
typename std::decay<Func>::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<ecs_ctx_free_t>(
|
|
_::free_obj<Delegate>);
|
|
ecs_set_hooks_id(m_world, m_id, &h);
|
|
return *this;
|
|
}
|
|
|
|
/** Register on_remove hook. */
|
|
template <typename Func>
|
|
component<T>& on_remove(Func&& func) {
|
|
using Delegate = typename _::each_delegate<
|
|
typename std::decay<Func>::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<ecs_ctx_free_t>(
|
|
_::free_obj<Delegate>);
|
|
ecs_set_hooks_id(m_world, m_id, &h);
|
|
return *this;
|
|
}
|
|
|
|
/** Register on_set hook. */
|
|
template <typename Func>
|
|
component<T>& on_set(Func&& func) {
|
|
using Delegate = typename _::each_delegate<
|
|
typename std::decay<Func>::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<ecs_ctx_free_t>(
|
|
_::free_obj<Delegate>);
|
|
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<BindingCtx*>(h.binding_ctx);
|
|
if (!result) {
|
|
result = FLECS_NEW(BindingCtx);
|
|
h.binding_ctx = result;
|
|
h.binding_ctx_free = reinterpret_cast<ecs_ctx_free_t>(
|
|
_::free_obj<BindingCtx>);
|
|
}
|
|
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 <typename T>
|
|
flecs::entity_t type_id() {
|
|
if (_::cpp_type<T>::s_reset_count == ecs_cpp_reset_count_get()) {
|
|
return _::cpp_type<T>::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();
|
|
}
|
|
|
|
}
|
|
|
|
/** @} */
|