Browse Source

component: automatic traits deduction

Michele Caini 4 năm trước cách đây
mục cha
commit
ed11bda9fd

+ 38 - 32
docs/md/entity.md

@@ -27,6 +27,7 @@
     * [Organizer](#organizer)
     * [Organizer](#organizer)
   * [Context variables](#context-variables)
   * [Context variables](#context-variables)
     * [Aliased properties](#aliased-properties)
     * [Aliased properties](#aliased-properties)
+  * [Component traits](#component-traits)
   * [Pointer stability](#pointer-stability)
   * [Pointer stability](#pointer-stability)
     * [In-place delete](#in-place-delete)
     * [In-place delete](#in-place-delete)
     * [Hierarchies and the like](#hierarchies-and-the-like)
     * [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.
 Aliased properties can be erased as it happens with any other variable.
 Similarly, they can also be associated with user-generated _names_ (or ids).
 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
 ## Pointer stability
 
 
 The ability to achieve pointer stability for one, several or all components is a
 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
 ### In-place delete
 
 
 The library offers out of the box support for in-place deletion, thus offering
 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
 Views and groups adapt accordingly when they detect a storage with a different
 deletion policy than the default. In particular:
 deletion policy than the default. In particular:
 
 
@@ -1028,14 +1037,11 @@ advantages:
 
 
 ```cpp
 ```cpp
 struct transform {
 struct transform {
+    static constexpr auto in_place_delete = true;
+
     transform *parent;
     transform *parent;
     // ... other data members ...
     // ... 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
 Furthermore, it's quite common for a group of elements to be created close in

+ 43 - 10
src/entt/entity/component.hpp

@@ -1,28 +1,61 @@
 #ifndef ENTT_ENTITY_COMPONENT_HPP
 #ifndef ENTT_ENTITY_COMPONENT_HPP
 #define ENTT_ENTITY_COMPONENT_HPP
 #define ENTT_ENTITY_COMPONENT_HPP
 
 
+#include <cstddef>
 #include <type_traits>
 #include <type_traits>
 #include "../config/config.h"
 #include "../config/config.h"
 
 
 namespace entt {
 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.
  * @brief Common way to access various properties of components.
  * @tparam Type Type of component.
  * @tparam Type Type of component.
  */
  */
 template<typename Type, typename = void>
 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");
     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;
 };
 };
 
 
 /**
 /**

+ 1 - 0
test/CMakeLists.txt

@@ -184,6 +184,7 @@ SETUP_BASIC_TEST(utility entt/core/utility.cpp)
 
 
 # Test entity
 # Test entity
 
 
+SETUP_BASIC_TEST(component entt/entity/component.cpp)
 SETUP_BASIC_TEST(entity entt/entity/entity.cpp)
 SETUP_BASIC_TEST(entity entt/entity/entity.cpp)
 SETUP_BASIC_TEST(group entt/entity/group.cpp)
 SETUP_BASIC_TEST(group entt/entity/group.cpp)
 SETUP_BASIC_TEST(handle entt/entity/handle.cpp)
 SETUP_BASIC_TEST(handle entt/entity/handle.cpp)

+ 4 - 6
test/benchmark/benchmark.cpp

@@ -13,16 +13,14 @@ struct position {
 };
 };
 
 
 struct velocity: position {};
 struct velocity: position {};
-struct stable_position: position {};
 
 
-template<auto>
-struct comp { int x; };
-
-template<>
-struct entt::component_traits<stable_position>: basic_component_traits {
+struct stable_position: position {
     static constexpr auto in_place_delete = true;
     static constexpr auto in_place_delete = true;
 };
 };
 
 
+template<auto>
+struct comp { int x; };
+
 struct timer final {
 struct timer final {
     timer()
     timer()
         : start{std::chrono::system_clock::now()} {}
         : start{std::chrono::system_clock::now()} {}

+ 42 - 0
test/entt/entity/component.cpp

@@ -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);
+}

+ 1 - 5
test/entt/entity/helper.cpp

@@ -13,12 +13,8 @@ struct clazz {
 };
 };
 
 
 struct stable_type {
 struct stable_type {
-    int value;
-};
-
-template<>
-struct entt::component_traits<stable_type>: basic_component_traits {
     static constexpr auto in_place_delete = true;
     static constexpr auto in_place_delete = true;
+    int value;
 };
 };
 
 
 TEST(Helper, AsView) {
 TEST(Helper, AsView) {

+ 1 - 5
test/entt/entity/registry.cpp

@@ -16,12 +16,8 @@
 struct empty_type {};
 struct empty_type {};
 
 
 struct stable_type {
 struct stable_type {
-    int value;
-};
-
-template<>
-struct entt::component_traits<stable_type>: basic_component_traits {
     static constexpr auto in_place_delete = true;
     static constexpr auto in_place_delete = true;
+    int value;
 };
 };
 
 
 struct non_default_constructible {
 struct non_default_constructible {

+ 1 - 5
test/entt/entity/runtime_view.cpp

@@ -7,12 +7,8 @@
 #include <entt/entity/runtime_view.hpp>
 #include <entt/entity/runtime_view.hpp>
 
 
 struct stable_type {
 struct stable_type {
-    int value;
-};
-
-template<>
-struct entt::component_traits<stable_type>: basic_component_traits {
     static constexpr auto in_place_delete = true;
     static constexpr auto in_place_delete = true;
+    int value;
 };
 };
 
 
 TEST(RuntimeView, Functionalities) {
 TEST(RuntimeView, Functionalities) {

+ 1 - 5
test/entt/entity/sigh_storage_mixin.cpp

@@ -7,6 +7,7 @@
 struct empty_type {};
 struct empty_type {};
 
 
 struct stable_type {
 struct stable_type {
+    static constexpr auto in_place_delete = true;
     int value{};
     int value{};
 };
 };
 
 
@@ -19,11 +20,6 @@ struct non_default_constructible {
     int value{};
     int value{};
 };
 };
 
 
-template<>
-struct entt::component_traits<stable_type>: basic_component_traits {
-    static constexpr auto in_place_delete = true;
-};
-
 struct counter {
 struct counter {
     int value{};
     int value{};
 };
 };

+ 7 - 12
test/entt/entity/storage.cpp

@@ -12,13 +12,16 @@
 #include "../common/throwing_allocator.hpp"
 #include "../common/throwing_allocator.hpp"
 #include "../common/throwing_type.hpp"
 #include "../common/throwing_type.hpp"
 
 
-struct empty_stable_type {};
+struct empty_stable_type {
+    static constexpr auto in_place_delete = true;
+};
 
 
 struct boxed_int {
 struct boxed_int {
     int value;
     int value;
 };
 };
 
 
 struct stable_type {
 struct stable_type {
+    static constexpr auto in_place_delete = true;
     int value;
     int value;
 };
 };
 
 
@@ -72,18 +75,10 @@ struct crete_from_constructor {
 };
 };
 
 
 template<>
 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 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) {
 bool operator==(const boxed_int &lhs, const boxed_int &rhs) {

+ 1 - 5
test/entt/entity/view.cpp

@@ -11,12 +11,8 @@
 struct empty_type {};
 struct empty_type {};
 
 
 struct stable_type {
 struct stable_type {
-    int value;
-};
-
-template<>
-struct entt::component_traits<stable_type>: basic_component_traits {
     static constexpr auto in_place_delete = true;
     static constexpr auto in_place_delete = true;
+    int value;
 };
 };
 
 
 TEST(SingleComponentView, Functionalities) {
 TEST(SingleComponentView, Functionalities) {