component: automatic traits deduction
This commit is contained in:
@@ -27,6 +27,7 @@
|
||||
* [Organizer](#organizer)
|
||||
* [Context variables](#context-variables)
|
||||
* [Aliased properties](#aliased-properties)
|
||||
* [Component traits](#component-traits)
|
||||
* [Pointer stability](#pointer-stability)
|
||||
* [In-place delete](#in-place-delete)
|
||||
* [Hierarchies and the like](#hierarchies-and-the-like)
|
||||
@@ -942,6 +943,38 @@ const my_type &var = registry.ctx().at<const my_type>();
|
||||
Aliased properties can be erased as it happens with any other variable.
|
||||
Similarly, they can also be associated with user-generated _names_ (or ids).
|
||||
|
||||
## Component traits
|
||||
|
||||
In `EnTT`, almost everything is customizable. Components are no exception.<br/>
|
||||
In this case, the _standardized_ way to access all component properties is the
|
||||
`component_traits` class.
|
||||
|
||||
Various parts of the library access component properties through this class. It
|
||||
makes it possible to use any type as a component, as long as its specialization
|
||||
of `component_traits` implements all the required functionalities.<br/>
|
||||
The non-specialized version of this class contains the following members:
|
||||
|
||||
* `in_place_delete`: `Type::in_place_delete` if present, false otherwise.
|
||||
* `ignore_if_empty`: `Type::ignore_if_empty` if present, `ENTT_IGNORE_IF_EMPTY`
|
||||
otherwise.
|
||||
* `page_size`: `Type::ignore_if_empty` if present, `ENTT_PACKED_PAGE` otherwise.
|
||||
|
||||
Where `Type` is any type of component. All properties can be customized by
|
||||
specializing the above class and defining all its members, or by adding only
|
||||
those of interest to a component definition:
|
||||
|
||||
```cpp
|
||||
struct transform {
|
||||
static constexpr auto in_place_delete = true;
|
||||
// ... other data members ...
|
||||
};
|
||||
```
|
||||
|
||||
The `component_traits` class template will take care of correctly extracting the
|
||||
properties from the supplied type to pass them to the rest of the library.<br/>
|
||||
In the case of a direct specialization, the class is also _sfinae-friendly_. It
|
||||
supports single and multi type specializations as well as feature-based ones.
|
||||
|
||||
## Pointer stability
|
||||
|
||||
The ability to achieve pointer stability for one, several or all components is a
|
||||
@@ -962,33 +995,9 @@ In other words, pointer stability is not automatic but is enabled on request.
|
||||
### In-place delete
|
||||
|
||||
The library offers out of the box support for in-place deletion, thus offering
|
||||
storage with completely stable pointers.<br/>
|
||||
This is achieved by specializing the `component_traits` class. The compile-time
|
||||
definition common to all components is the following:
|
||||
|
||||
```cpp
|
||||
struct basic_component_traits {
|
||||
static constexpr auto in_place_delete = false;
|
||||
static constexpr auto ignore_if_empty = ENTT_IGNORE_IF_EMPTY;
|
||||
static constexpr auto page_size = ENTT_PACKED_PAGE;
|
||||
};
|
||||
```
|
||||
|
||||
Where `in_place_delete` instructs the library on the deletion policy for a given
|
||||
type while `ignore_if_empty` selectively disables empty type optimization and
|
||||
`page_size` dictates the storage behavior for non-empty types.<br/>
|
||||
The `component_traits` class template is _sfinae-friendly_, it supports single
|
||||
and multi type specializations as well as feature-based ones:
|
||||
|
||||
```cpp
|
||||
template<>
|
||||
struct entt::component_traits<position>: basic_component_traits {
|
||||
static constexpr auto in_place_delete = true;
|
||||
};
|
||||
```
|
||||
|
||||
This will ensure in-place deletion for the `position` component without further
|
||||
user intervention.<br/>
|
||||
storage with completely stable pointers. This is achieved by specializing the
|
||||
`component_traits` class or by adding the required properties to the component
|
||||
definition when needed.<br/>
|
||||
Views and groups adapt accordingly when they detect a storage with a different
|
||||
deletion policy than the default. In particular:
|
||||
|
||||
@@ -1028,14 +1037,11 @@ advantages:
|
||||
|
||||
```cpp
|
||||
struct transform {
|
||||
static constexpr auto in_place_delete = true;
|
||||
|
||||
transform *parent;
|
||||
// ... other data members ...
|
||||
};
|
||||
|
||||
template<>
|
||||
struct entt::component_traits<transform>: basic_component_traits {
|
||||
static constexpr auto in_place_delete = true;
|
||||
};
|
||||
```
|
||||
|
||||
Furthermore, it's quite common for a group of elements to be created close in
|
||||
|
||||
@@ -1,28 +1,61 @@
|
||||
#ifndef ENTT_ENTITY_COMPONENT_HPP
|
||||
#define ENTT_ENTITY_COMPONENT_HPP
|
||||
|
||||
#include <cstddef>
|
||||
#include <type_traits>
|
||||
#include "../config/config.h"
|
||||
|
||||
namespace entt {
|
||||
|
||||
/*! @brief Commonly used default traits for all types. */
|
||||
struct basic_component_traits {
|
||||
/*! @brief Pointer stability, default is `false`. */
|
||||
static constexpr auto in_place_delete = false;
|
||||
/*! @brief Empty type optimization, default is `ENTT_IGNORE_IF_EMPTY`. */
|
||||
static constexpr auto ignore_if_empty = ENTT_IGNORE_IF_EMPTY;
|
||||
/*! @brief Page size, default is `ENTT_PACKED_PAGE`. */
|
||||
static constexpr auto page_size = ENTT_PACKED_PAGE;
|
||||
};
|
||||
/**
|
||||
* @cond TURN_OFF_DOXYGEN
|
||||
* Internal details not to be documented.
|
||||
*/
|
||||
|
||||
namespace internal {
|
||||
|
||||
template<typename, typename = void>
|
||||
struct in_place_delete: std::false_type {};
|
||||
|
||||
template<typename Type>
|
||||
struct in_place_delete<Type, std::enable_if_t<Type::in_place_delete>>
|
||||
: std::true_type {};
|
||||
|
||||
template<typename, typename = void>
|
||||
struct ignore_if_empty: std::bool_constant<ENTT_IGNORE_IF_EMPTY> {};
|
||||
|
||||
template<typename Type>
|
||||
struct ignore_if_empty<Type, std::enable_if_t<Type::ignore_if_empty>>
|
||||
: std::true_type {};
|
||||
|
||||
template<typename, typename = void>
|
||||
struct page_size: std::integral_constant<std::size_t, ENTT_PACKED_PAGE> {};
|
||||
|
||||
template<typename Type>
|
||||
struct page_size<Type, std::enable_if_t<std::is_convertible_v<decltype(Type::page_size), std::size_t>>>
|
||||
: std::integral_constant<std::size_t, Type::page_size> {};
|
||||
|
||||
} // namespace internal
|
||||
|
||||
/**
|
||||
* Internal details not to be documented.
|
||||
* @endcond
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Common way to access various properties of components.
|
||||
* @tparam Type Type of component.
|
||||
*/
|
||||
template<typename Type, typename = void>
|
||||
struct component_traits: basic_component_traits {
|
||||
struct component_traits {
|
||||
static_assert(std::is_same_v<std::decay_t<Type>, Type>, "Unsupported type");
|
||||
|
||||
/*! @brief Pointer stability, default is `false`. */
|
||||
static constexpr bool in_place_delete = internal::in_place_delete<Type>::value;
|
||||
/*! @brief Empty type optimization, default is `ENTT_IGNORE_IF_EMPTY`. */
|
||||
static constexpr bool ignore_if_empty = internal::ignore_if_empty<Type>::value;
|
||||
/*! @brief Page size, default is `ENTT_PACKED_PAGE`. */
|
||||
static constexpr std::size_t page_size = internal::page_size<Type>::value;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -184,6 +184,7 @@ SETUP_BASIC_TEST(utility entt/core/utility.cpp)
|
||||
|
||||
# Test entity
|
||||
|
||||
SETUP_BASIC_TEST(component entt/entity/component.cpp)
|
||||
SETUP_BASIC_TEST(entity entt/entity/entity.cpp)
|
||||
SETUP_BASIC_TEST(group entt/entity/group.cpp)
|
||||
SETUP_BASIC_TEST(handle entt/entity/handle.cpp)
|
||||
|
||||
@@ -13,16 +13,14 @@ struct position {
|
||||
};
|
||||
|
||||
struct velocity: position {};
|
||||
struct stable_position: position {};
|
||||
|
||||
struct stable_position: position {
|
||||
static constexpr auto in_place_delete = true;
|
||||
};
|
||||
|
||||
template<auto>
|
||||
struct comp { int x; };
|
||||
|
||||
template<>
|
||||
struct entt::component_traits<stable_position>: basic_component_traits {
|
||||
static constexpr auto in_place_delete = true;
|
||||
};
|
||||
|
||||
struct timer final {
|
||||
timer()
|
||||
: start{std::chrono::system_clock::now()} {}
|
||||
|
||||
42
test/entt/entity/component.cpp
Normal file
42
test/entt/entity/component.cpp
Normal file
@@ -0,0 +1,42 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/entity/component.hpp>
|
||||
|
||||
struct self_contained {
|
||||
static constexpr auto in_place_delete = true;
|
||||
static constexpr auto page_size = 4u;
|
||||
};
|
||||
|
||||
struct traits_based {};
|
||||
|
||||
template<>
|
||||
struct entt::component_traits<traits_based> {
|
||||
static constexpr auto in_place_delete = false;
|
||||
static constexpr auto ignore_if_empty = false;
|
||||
static constexpr auto page_size = 8u;
|
||||
};
|
||||
|
||||
struct default_params {};
|
||||
|
||||
TEST(Component, DefaultParams) {
|
||||
using traits = entt::component_traits<default_params>;
|
||||
|
||||
static_assert(!traits::in_place_delete);
|
||||
static_assert(traits::ignore_if_empty);
|
||||
static_assert(traits::page_size == ENTT_PACKED_PAGE);
|
||||
}
|
||||
|
||||
TEST(Component, SelfContained) {
|
||||
using traits = entt::component_traits<self_contained>;
|
||||
|
||||
static_assert(traits::in_place_delete);
|
||||
static_assert(traits::ignore_if_empty);
|
||||
static_assert(traits::page_size == 4u);
|
||||
}
|
||||
|
||||
TEST(Component, TraitsBased) {
|
||||
using traits = entt::component_traits<traits_based>;
|
||||
|
||||
static_assert(!traits::in_place_delete);
|
||||
static_assert(!traits::ignore_if_empty);
|
||||
static_assert(traits::page_size == 8u);
|
||||
}
|
||||
@@ -13,12 +13,8 @@ struct clazz {
|
||||
};
|
||||
|
||||
struct stable_type {
|
||||
int value;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct entt::component_traits<stable_type>: basic_component_traits {
|
||||
static constexpr auto in_place_delete = true;
|
||||
int value;
|
||||
};
|
||||
|
||||
TEST(Helper, AsView) {
|
||||
|
||||
@@ -16,12 +16,8 @@
|
||||
struct empty_type {};
|
||||
|
||||
struct stable_type {
|
||||
int value;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct entt::component_traits<stable_type>: basic_component_traits {
|
||||
static constexpr auto in_place_delete = true;
|
||||
int value;
|
||||
};
|
||||
|
||||
struct non_default_constructible {
|
||||
|
||||
@@ -7,12 +7,8 @@
|
||||
#include <entt/entity/runtime_view.hpp>
|
||||
|
||||
struct stable_type {
|
||||
int value;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct entt::component_traits<stable_type>: basic_component_traits {
|
||||
static constexpr auto in_place_delete = true;
|
||||
int value;
|
||||
};
|
||||
|
||||
TEST(RuntimeView, Functionalities) {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
struct empty_type {};
|
||||
|
||||
struct stable_type {
|
||||
static constexpr auto in_place_delete = true;
|
||||
int value{};
|
||||
};
|
||||
|
||||
@@ -19,11 +20,6 @@ struct non_default_constructible {
|
||||
int value{};
|
||||
};
|
||||
|
||||
template<>
|
||||
struct entt::component_traits<stable_type>: basic_component_traits {
|
||||
static constexpr auto in_place_delete = true;
|
||||
};
|
||||
|
||||
struct counter {
|
||||
int value{};
|
||||
};
|
||||
|
||||
@@ -12,13 +12,16 @@
|
||||
#include "../common/throwing_allocator.hpp"
|
||||
#include "../common/throwing_type.hpp"
|
||||
|
||||
struct empty_stable_type {};
|
||||
struct empty_stable_type {
|
||||
static constexpr auto in_place_delete = true;
|
||||
};
|
||||
|
||||
struct boxed_int {
|
||||
int value;
|
||||
};
|
||||
|
||||
struct stable_type {
|
||||
static constexpr auto in_place_delete = true;
|
||||
int value;
|
||||
};
|
||||
|
||||
@@ -72,18 +75,10 @@ struct crete_from_constructor {
|
||||
};
|
||||
|
||||
template<>
|
||||
struct entt::component_traits<stable_type>: basic_component_traits {
|
||||
static constexpr auto in_place_delete = true;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct entt::component_traits<empty_stable_type>: basic_component_traits {
|
||||
static constexpr auto in_place_delete = true;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct entt::component_traits<std::unordered_set<char>>: basic_component_traits {
|
||||
struct entt::component_traits<std::unordered_set<char>> {
|
||||
static constexpr auto in_place_delete = true;
|
||||
static constexpr auto ignore_if_empty = ENTT_IGNORE_IF_EMPTY;
|
||||
static constexpr auto page_size = ENTT_PACKED_PAGE;
|
||||
};
|
||||
|
||||
bool operator==(const boxed_int &lhs, const boxed_int &rhs) {
|
||||
|
||||
@@ -11,12 +11,8 @@
|
||||
struct empty_type {};
|
||||
|
||||
struct stable_type {
|
||||
int value;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct entt::component_traits<stable_type>: basic_component_traits {
|
||||
static constexpr auto in_place_delete = true;
|
||||
int value;
|
||||
};
|
||||
|
||||
TEST(SingleComponentView, Functionalities) {
|
||||
|
||||
Reference in New Issue
Block a user