Przeglądaj źródła

added prototype class

Michele Caini 7 lat temu
rodzic
commit
9a785ceb2e

+ 67 - 2
README.md

@@ -26,6 +26,7 @@
          * [Continuous loader](#continuous-loader)
          * [Continuous loader](#continuous-loader)
          * [Archives](#archives)
          * [Archives](#archives)
          * [One example to rule them all](#one-example-to-rule-them-all)
          * [One example to rule them all](#one-example-to-rule-them-all)
+      * [Prototype](#prototype)
       * [Helpers](#helpers)
       * [Helpers](#helpers)
          * [Dependency function](#dependency-function)
          * [Dependency function](#dependency-function)
    * [View: to persist or not to persist?](#view-to-persist-or-not-to-persist)
    * [View: to persist or not to persist?](#view-to-persist-or-not-to-persist)
@@ -679,7 +680,7 @@ destroyed.
 #### Who let the tags out?
 #### Who let the tags out?
 
 
 As an extension, signals are also provided with tags. Although they are not
 As an extension, signals are also provided with tags. Although they are not
-strictly required internally, it makes sense that an user expects signal support
+strictly required internally, it makes sense that a user expects signal support
 even when it comes to tags actually.<br/>
 even when it comes to tags actually.<br/>
 Signals for tags undergo exactly the same requirements of those introduced for
 Signals for tags undergo exactly the same requirements of those introduced for
 components. Also the function type for a listener is the same and it's invoked
 components. Also the function type for a listener is the same and it's invoked
@@ -829,7 +830,7 @@ The `component` member function is a function template the aim of which is to
 store aside components. The presence of a template parameter list is a
 store aside components. The presence of a template parameter list is a
 consequence of a couple of design choices from the past and in the present:
 consequence of a couple of design choices from the past and in the present:
 
 
-* First of all, there is no reason to force an user to serialize all the
+* First of all, there is no reason to force a user to serialize all the
   components at once and most of the times it isn't desiderable. As an example,
   components at once and most of the times it isn't desiderable. As an example,
   in case the stuff for the HUD in a game is put into the registry for some
   in case the stuff for the HUD in a game is put into the registry for some
   reasons, its components can be freely discarded during a serialization step
   reasons, its components can be freely discarded during a serialization step
@@ -1023,6 +1024,70 @@ the best way to do it. However, feel free to use it at your own risk.
 The basic idea is to store everything in a group of queues in memory, then bring
 The basic idea is to store everything in a group of queues in memory, then bring
 everything back to the registry with different loaders.
 everything back to the registry with different loaders.
 
 
+### Prototype
+
+A prototype defines a type of an application in terms of its parts. They can be
+used to assign components to entities of a registry at once.<br/>
+Roughly speaking, in most cases prototypes can be considered just as templates
+to use to initialize entities according to _concepts_. In fact, users can create
+how many prototypes they want, each one initialized differently from the others.
+
+The following is an example of use of a prototype:
+
+```cpp
+entt::DefaultPrototype prototype;
+
+prototype.set<Position>(100.f, 100.f);
+prototype.set<Velocity>(0.f, 0.f);
+
+// ...
+
+entt::DefaultRegistry registry;
+
+const auto entity = prototype(registry);
+```
+
+To assign and remove components from a prototype, it offers two dedicated member
+functions named `set` and `unset`. The `has` member function can be used to know
+if a given prototype contains one or more components and the `get` member
+function can be used to retrieve the components.
+
+Creating an entity from a prototype is straightforward:
+
+* To create a new entity from scratch and assign it a prototype, this is the way
+  to go:
+  ```cpp
+  const auto entity = prototype(registry);
+  ```
+  It is equivalent to the following invokation:
+  ```cpp
+  const auto entity = prototype.create(registry);
+  ```
+
+* In case we want to initialize an already existing entity, we can provide the
+  `operator()` directly with the entity identifier:
+  ```cpp
+  prototype(registry, entity);
+  ```
+  It is equivalent to the following invokation:
+  ```cpp
+  prototype.assign(registry, entity);
+  ```
+  Note that existing components aren't overwritten in this case. Only those
+  components that the entity doesn't own yet are copied over. All the other
+  components remain unchanged.
+
+* Finally, to assign or replace all the components for an entity, thus
+  overwriting existing ones:
+  ```cpp
+  prototype.accommodate(registry, entity);
+  ```
+
+Prototypes are a very useful tool that can save a lot of typing sometimes.
+Furthermore, the codebase may be easier to maintain, since updating a prototype
+is much less error prone than jumping around in the codebase to update all the
+snippets copied and pasted around to initialize entities and components.
+
 ### Helpers
 ### Helpers
 
 
 The so called _helpers_ are small classes and functions mainly designed to offer
 The so called _helpers_ are small classes and functions mainly designed to offer

+ 5 - 1
TODO

@@ -4,9 +4,13 @@
 * debugging tools (#60): the issue online already contains interesting tips on this, look at it
 * debugging tools (#60): the issue online already contains interesting tips on this, look at it
 * define basic reactive systems (track entities to which component is attached, track entities from which component is removed, and so on)
 * define basic reactive systems (track entities to which component is attached, track entities from which component is removed, and so on)
 * define systems as composable mixins (initializazion, reactive, update, whatever) with flexible auto-detected arguments (registry, views, etc)
 * define systems as composable mixins (initializazion, reactive, update, whatever) with flexible auto-detected arguments (registry, views, etc)
+* is it possible to allow multiple components for registry remove, reset, reserve, (maybe) managed, empty, get (tags), move and so on?
+* create dedicated flat map based on types implementation (sort of "type map") for types to use within the registry and so on...
 * does it worth it to add an optional functor to the member functions of snapshot so as to filter out instances and entities?
 * does it worth it to add an optional functor to the member functions of snapshot so as to filter out instances and entities?
 * ease the assignment of tags as string (use a template class with a non-type template parameter behind the scene)
 * ease the assignment of tags as string (use a template class with a non-type template parameter behind the scene)
-* prototype entities, a really interesting feature (see #56)
+* define a dedicated specialization for multi component view in case of two components
+* is it possible to reduce the storage used to manage empty components?
+* reintroduce meaningful copy/clone functionalities into the registry
 * is registry/utility.hpp really required?
 * is registry/utility.hpp really required?
 * "singleton mode" for tags (see #66)
 * "singleton mode" for tags (see #66)
 * AOB
 * AOB

+ 1 - 1
src/entt/entity/helper.hpp

@@ -17,7 +17,7 @@ namespace entt {
  * components to an entity when a type has a dependency on some other types.
  * components to an entity when a type has a dependency on some other types.
  *
  *
  * This is a prototype function to use to create dependencies.<br/>
  * This is a prototype function to use to create dependencies.<br/>
- * It isn't intended for direct use, even if nothing forbids using it freely.
+ * It isn't intended for direct use, although nothing forbids using it freely.
  *
  *
  * @tparam Entity A valid entity type (see entt_traits for more details).
  * @tparam Entity A valid entity type (see entt_traits for more details).
  * @tparam Component Types of components to assign to an entity if triggered.
  * @tparam Component Types of components to assign to an entity if triggered.

+ 247 - 139
src/entt/entity/prototype.hpp

@@ -1,226 +1,334 @@
 #ifndef ENTT_ENTITY_PROTOTYPE_HPP
 #ifndef ENTT_ENTITY_PROTOTYPE_HPP
 #define ENTT_ENTITY_PROTOTYPE_HPP
 #define ENTT_ENTITY_PROTOTYPE_HPP
 
 
+
+#include <tuple>
+#include <memory>
+#include <vector>
+#include <utility>
+#include <cstddef>
+#include <algorithm>
 #include "registry.hpp"
 #include "registry.hpp"
 
 
+
 namespace entt {
 namespace entt {
 
 
+
 /**
 /**
- * @brief A prototype entity for creating new entities
+ * @brief Prototype container for _concepts_.
  *
  *
- * Prototype provides a similar interface to the registry except that Prototype
- * stores a single entity. This entity is not apart of the registry so it is not
- * seen by views. The Prototype can be used to accommodate components to an
- * entity on the registry.
+ * A prototype is used to define a _concept_ in terms of components.<br/>
+ * Prototypes act as templates for those specific types of an application which
+ * users would otherwise define through a series of component assignments to
+ * entities. In other words, prototypes can be used to assign components to
+ * entities of a registry at once.
  *
  *
- * Note that components stored in the Prototype must have copy constructors to
- * initialize an entity.
+ * @note
+ * Components used along with prototypes must be copy constructible.
  *
  *
  * @tparam Entity A valid entity type (see entt_traits for more details).
  * @tparam Entity A valid entity type (see entt_traits for more details).
  */
  */
-template <typename Entity>
+template<typename Entity>
 class Prototype {
 class Prototype {
-public:
-    using entity_t = Entity;
-    using registry_t = entt::Registry<Entity>;
-    using family_t = entt::Family<struct PrototypeFamily>;
+    using component_type = typename Registry<Entity>::component_type;
+    using fn_type = void(*)(const void *, Registry<Entity> &, Entity);
+    using deleter_type = void(*)(void *);
+    using ptr_type = std::unique_ptr<void, deleter_type>;
 
 
-private:
-    struct StorageBase {
-        virtual ~StorageBase() = default;
-        virtual void accommodate(registry_t &, entity_t) const ENTT_NOEXCEPT = 0;
-    };
-    
-    template <typename Component>
-    struct Storage : StorageBase {
-        void accommodate(registry_t &reg, const entity_t entity) const ENTT_NOEXCEPT override {
-            reg.template accommodate<Component>(entity, comp);
+    template<typename Component>
+    static void accommodate(const void *component, Registry<Entity> &registry, Entity entity) {
+        const auto &ref = *static_cast<const Component *>(component);
+        registry.template accommodate<Component>(entity, ref);
+    }
+
+    template<typename Component>
+    static void assign(const void *component, Registry<Entity> &registry, Entity entity) {
+        if(!registry.template has<Component>(entity)) {
+            const auto &ref = *static_cast<const Component *>(component);
+            registry.template assign<Component>(entity, ref);
         }
         }
-        
-        Component comp;
+    }
+
+    struct Handler final {
+        Handler(ptr_type component, fn_type accommodate, fn_type assign, component_type type)
+            : component{std::move(component)},
+              accommodate{accommodate},
+              assign{assign},
+              type{type}
+        {}
+
+        ptr_type component{nullptr, +[](void *) {}};
+        fn_type accommodate{nullptr};
+        fn_type assign{nullptr};
+        component_type type;
     };
     };
 
 
 public:
 public:
-    Prototype() = default;
-    Prototype(Prototype &&) = default;
-    Prototype &operator=(Prototype &&) = default;
+    /*! @brief Registry type. */
+    using registry_type = Registry<Entity>;
+    /*! @brief Underlying entity identifier. */
+    using entity_type = Entity;
+    /*! @brief Unsigned integer type. */
+    using size_type = std::size_t;
 
 
     /**
     /**
-     * @brief Checks if there exists at least one component assigned
-     *
-     * @return True if no components are assigned
+     * @brief Assigns to or replaces the given component of a prototype.
+     * @tparam Component Type of component to assign or replace.
+     * @tparam Args Types of arguments to use to construct the component.
+     * @param args Parameters to use to initialize the component.
+     * @return A reference to the newly created component.
      */
      */
-    bool empty() const ENTT_NOEXCEPT {
-        return std::all_of(comps.cbegin(), comps.cend(), [] (auto comp) {
-            return comp == nullptr;
+    template<typename Component, typename... Args>
+    Component & set(Args &&... args) {
+        const auto ctype = registry_type::template type<Component>();
+
+        auto it = std::find_if(handlers.begin(), handlers.end(), [ctype](const auto &handler) {
+            return handler.type == ctype;
         });
         });
+
+        const auto deleter = +[](void *component) { delete static_cast<Component *>(component); };
+        ptr_type component{new Component{std::forward<Args>(args)...}, deleter};
+
+        if(it == handlers.cend()) {
+            handlers.emplace_back(std::move(component), &Prototype::accommodate<Component>, &Prototype::assign<Component>, ctype);
+        } else {
+            it->component = std::move(component);
+        }
+
+        return *static_cast<Component *>(component.get());
     }
     }
 
 
     /**
     /**
-     * @brief Accommodate copies of the components to the given entity
+     * @brief Removes the given component from a prototype.
+     * @tparam Component Type of component to remove.
      */
      */
-    void operator()(registry_t &reg, const entity_t entity) const ENTT_NOEXCEPT {
-        for (const std::unique_ptr<StorageBase> &component : comps) {
-            if (component) {
-                component->accommodate(reg, entity);
-            }
-        }
+    template<typename... Component>
+    void unset() ENTT_NOEXCEPT {
+        handlers.erase(std::remove_if(handlers.begin(), handlers.end(), [](const auto &handler) {
+            using accumulator_type = bool[];
+            bool match = false;
+            accumulator_type accumulator = { (match = match || handler.type == registry_type::template type<Component>())... };
+            (void)accumulator;
+            return match;
+        }), handlers.end());
     }
     }
+
     /**
     /**
-     * @brief Create an new entity assign copies of the components to it
+     * @brief Checks if a prototype owns all the given components.
+     * @tparam Component Components for which to perform the check.
+     * @return True if the prototype owns all the components, false otherwise.
+     */
+    template<typename... Component>
+    bool has() const ENTT_NOEXCEPT {
+        auto found = [this](const auto ctype) {
+            return std::find_if(handlers.cbegin(), handlers.cend(), [ctype](const auto &handler) {
+                return handler.type == ctype;
+            }) != handlers.cend();
+        };
+
+        using accumulator_type = bool[];
+        bool all = true;
+        accumulator_type accumulator = { all, (all = all && found(registry_type::template type<Component>()))... };
+        (void)accumulator;
+        return all;
+    }
+
+    /**
+     * @brief Returns a reference to the given component.
      *
      *
-     * @return Newly created entity
+     * @warning
+     * Attempting to get a component from a prototype that doesn't own it
+     * results in undefined behavior.<br/>
+     * An assertion will abort the execution at runtime in debug mode if the
+     * prototype doesn't own an instance of the given component.
+     *
+     * @tparam Component Type of component to get.
+     * @return A reference to the component owned by the prototype.
      */
      */
-    entity_t operator()(registry_t &reg) const ENTT_NOEXCEPT {
-        const entity_t entity = reg.create();
-        (*this)(reg, entity);
-        return entity;
+    template<typename Component>
+    const Component & get() const ENTT_NOEXCEPT {
+        assert(has<Component>());
+
+        auto it = std::find_if(handlers.cbegin(), handlers.cend(), [](const auto &handler) {
+            return handler.type == registry_type::template type<Component>();
+        });
+
+        return *static_cast<Component *>(it->component.get());
     }
     }
 
 
     /**
     /**
-     * @brief Returns the numeric identifier of a type of component at runtime
+     * @brief Returns a reference to the given component.
      *
      *
-     * The component doesn't need to be assigned to the prototype for this
-     * function to return a valid ID. The IDs are not synced with the registry.
+     * @warning
+     * Attempting to get a component from a prototype that doesn't own it
+     * results in undefined behavior.<br/>
+     * An assertion will abort the execution at runtime in debug mode if the
+     * prototype doesn't own an instance of the given component.
      *
      *
-     * @tparam Component Type of component to query.
-     * @return Runtime numeric identifier of the given type of component
+     * @tparam Component Type of component to get.
+     * @return A reference to the component owned by the prototype.
      */
      */
-    template <typename Component>
-    size_t type() const ENTT_NOEXCEPT {
-        return family_t::template type<Component>();
+    template<typename Component>
+    inline Component & get() ENTT_NOEXCEPT {
+        return const_cast<Component &>(const_cast<const Prototype *>(this)->get<Component>());
     }
     }
 
 
     /**
     /**
-     * @brief Assigns the given component to the prototype
+     * @brief Returns a reference to the given components.
      *
      *
      * @warning
      * @warning
-     * An assertion will abort the execution at runtime in debug mode in case
-     * the prototype already owns the given component
+     * Attempting to get components from a prototype that doesn't own them
+     * results in undefined behavior.<br/>
+     * An assertion will abort the execution at runtime in debug mode if the
+     * prototype doesn't own instances of the given components.
      *
      *
-     * @tparam Component Type of component to create
-     * @tparam Args Types of arguments to use to construct the component.
-     * @param args Parameters to use to initialize the component.
-     * @return A reference to the newly created component.
+     * @tparam Component Type of components to get.
+     * @return References to the components owned by the prototype.
      */
      */
-    template <typename Component, typename... Args>
-    Component &assign(Args &&... args) {
-        assert(!has<Component>());
-        auto component = std::make_unique<Storage<Component>>();
-        component->comp = Component {std::forward<Args>(args)...};
-        const size_t index = type<Component>();
-        while (comps.size() <= index) {
-            comps.emplace_back();
-        }
-        Component &comp = component->comp;
-        comps[index] = std::move(component);
-        return comp;
+    template<typename... Component>
+    std::enable_if_t<(sizeof...(Component) > 1), std::tuple<const Component &...>>
+    get() const ENTT_NOEXCEPT {
+        return { get<Component>()... };
     }
     }
-    
+
     /**
     /**
-     * @brief Removes the given component from the prototype
+     * @brief Returns a reference to the given components.
      *
      *
      * @warning
      * @warning
-     * An assertion will abort the execution at runtime in debug mode in case
-     * the prototype doesn't own the given component
+     * Attempting to get components from a prototype that doesn't own them
+     * results in undefined behavior.<br/>
+     * An assertion will abort the execution at runtime in debug mode if the
+     * prototype doesn't own instances of the given components.
      *
      *
-     * @tparam Component Type of component to remove
+     * @tparam Component Type of components to get.
+     * @return References to the components owned by the prototype.
      */
      */
-    template <typename Component>
-    void remove() ENTT_NOEXCEPT {
-        assert(has<Component>());
-        comps[type<Component>()] = nullptr;
+    template<typename... Component>
+    std::enable_if_t<(sizeof...(Component) > 1), std::tuple<Component &...>>
+    get() ENTT_NOEXCEPT {
+        return std::tuple<Component &...>{get<Component>()...};
     }
     }
-    
+
     /**
     /**
-     * @brief Checks if the prototype has all the given components
+     * @brief Creates a new entity using a given prototype.
+     *
+     * Utility shortcut, equivalent to the following snippet:
+     *
+     * @code{.cpp}
+     * const auto entity = registry.create();
+     * prototype(registry, entity);
+     * @endcode
      *
      *
-     * @tparam Components Components that the prototype must own
-     * @return True if the prototype owns all of the components, false otherwise.
+     * @warning
+     * Attempting to use an invalid entity results in undefined behavior.<br/>
+     * An assertion will abort the execution at runtime in debug mode in case of
+     * invalid entity.
+     *
+     * @param registry A valid reference to a registry.
+     * @return A valid entity identifier.
      */
      */
-    template <typename... Components>
-    bool has() const ENTT_NOEXCEPT {
-        bool all = true;
-        [[maybe_unused]]
-        bool acc[] = {(all = all && type<Components>() < comps.size() && comps[type<Components>()] != nullptr)...};
-        return all;
+    entity_type create(registry_type &registry) {
+        const auto entity = registry.create();
+        assign(registry, entity);
+        return entity;
     }
     }
-    
+
     /**
     /**
-     * @brief Returns a const reference to the given component
+     * @brief Assigns the components of a prototype to a given entity.
+     *
+     * Assigning a prototype to an entity won't overwrite existing components
+     * under any circumstances.<br/>
+     * In other words, only those components that the entity doesn't own yet are
+     * copied over. All the other components remain unchanged.
      *
      *
      * @warning
      * @warning
-     * An assertion will abort the execution at runtime in debug mode in case
-     * the prototype doesn't own the given component
+     * Attempting to use an invalid entity results in undefined behavior.<br/>
+     * An assertion will abort the execution at runtime in debug mode in case of
+     * invalid entity.
      *
      *
-     * @tparam Component Type of component to get
-     * @return A reference to the component
+     * @param registry A valid reference to a registry.
+     * @param entity A valid entity identifier.
      */
      */
-    template <typename Component>
-    const Component &get() const ENTT_NOEXCEPT {
-        assert(has<Component>());
-        return static_cast<Storage<Component> *>(comps[type<Component>()].get())->comp;
+    void assign(registry_type &registry, entity_type entity) {
+        std::for_each(handlers.begin(), handlers.end(), [&registry, entity, this](auto &&handler) {
+            handler.assign(handler.component.get(), registry, entity);
+        });
     }
     }
-    
+
     /**
     /**
-     * @brief Returns a reference to the given component
+     * @brief Assigns or replaces the components of a prototype for an entity.
+     *
+     * Existing components are overwritten, if any. All the other components
+     * will be copied over to the target entity.
      *
      *
      * @warning
      * @warning
-     * An assertion will abort the execution at runtime in debug mode in case
-     * the prototype doesn't own the given component
+     * Attempting to use an invalid entity results in undefined behavior.<br/>
+     * An assertion will abort the execution at runtime in debug mode in case of
+     * invalid entity.
      *
      *
-     * @tparam Component Type of component to get
-     * @return A reference to the component
+     * @param registry A valid reference to a registry.
+     * @param entity A valid entity identifier.
      */
      */
-    template <typename Component>
-    Component &get() ENTT_NOEXCEPT {
-        assert(has<Component>());
-        return static_cast<Storage<Component> *>(comps[type<Component>()].get())->comp;
+    void accommodate(registry_type &registry, entity_type entity) {
+        std::for_each(handlers.begin(), handlers.end(), [&registry, entity, this](auto &&handler) {
+            handler.accommodate(handler.component.get(), registry, entity);
+        });
     }
     }
-    
+
     /**
     /**
-     * @brief Replaces the given component
+     * @brief Assigns the components of a prototype to an entity.
+     *
+     * Assigning a prototype to an entity won't overwrite existing components
+     * under any circumstances.<br/>
+     * In other words, only the components that the entity doesn't own yet are
+     * copied over. All the other components remain unchanged.
      *
      *
      * @warning
      * @warning
-     * An assertion will abort the execution at runtime in debug mode in case
-     * the prototype doesn't own the given component
+     * Attempting to use an invalid entity results in undefined behavior.<br/>
+     * An assertion will abort the execution at runtime in debug mode in case of
+     * invalid entity.
      *
      *
-     * @tparam Component Type of component to replace
-     * @tparam Args Types of arguments to use to construct the component
-     * @param args Parameters to use to initialize the component
-     * @return A refernce to the newly created component
+     * @param registry A valid reference to a registry.
+     * @param entity A valid entity identifier.
      */
      */
-    template <typename Component, typename... Args>
-    Component &replace(Args &&... args) ENTT_NOEXCEPT {
-        return (get<Component>() = Component{std::forward<Args>(args)...});
+    inline void operator()(registry_type &registry, entity_type entity) ENTT_NOEXCEPT {
+        assign(registry, entity);
     }
     }
-    
+
     /**
     /**
-     * @brief Assigns of replaces the given component
+     * @brief Creates a new entity using a given prototype.
+     *
+     * Utility shortcut, equivalent to the following snippet:
      *
      *
-     * @tparam Component Type of component to replace
-     * @tparam Args Types of arguments to use to construct the component
-     * @param args Parameters to use to initialize the component
-     * @return A refernce to the newly created component
+     * @code{.cpp}
+     * const auto entity = registry.create();
+     * prototype(registry, entity);
+     * @endcode
+     *
+     * @warning
+     * Attempting to use an invalid entity results in undefined behavior.<br/>
+     * An assertion will abort the execution at runtime in debug mode in case of
+     * invalid entity.
+     *
+     * @param registry A valid reference to a registry.
+     * @return A valid entity identifier.
      */
      */
-    template <typename Component, typename... Args>
-    Component &accommodate(Args &&... args) ENTT_NOEXCEPT {
-        if (has<Component>()) {
-            return replace<Component>(std::forward<Args>(args)...);
-        } else {
-            return assign<Component>(std::forward<Args>(args)...);
-        }
+    inline entity_type operator()(registry_type &registry) ENTT_NOEXCEPT {
+        return create(registry);
     }
     }
-    
+
 private:
 private:
-    std::vector<std::unique_ptr<StorageBase>> comps;
+    std::vector<Handler> handlers;
 };
 };
 
 
+
 /**
 /**
  * @brief Default prototype
  * @brief Default prototype
  */
  */
 using DefaultPrototype = Prototype<uint32_t>;
 using DefaultPrototype = Prototype<uint32_t>;
 
 
+
 }
 }
 
 
-#endif
+
+#endif // ENTT_ENTITY_PROTOTYPE_HPP

+ 2 - 2
src/entt/entity/registry.hpp

@@ -704,7 +704,7 @@ public:
     template<typename... Component>
     template<typename... Component>
     std::enable_if_t<(sizeof...(Component) > 1), std::tuple<const Component &...>>
     std::enable_if_t<(sizeof...(Component) > 1), std::tuple<const Component &...>>
     get(entity_type entity) const ENTT_NOEXCEPT {
     get(entity_type entity) const ENTT_NOEXCEPT {
-        return std::tuple<const Component &...>{get<Component>(entity)...};
+        return { get<Component>(entity)... };
     }
     }
 
 
     /**
     /**
@@ -724,7 +724,7 @@ public:
     template<typename... Component>
     template<typename... Component>
     std::enable_if_t<(sizeof...(Component) > 1), std::tuple<Component &...>>
     std::enable_if_t<(sizeof...(Component) > 1), std::tuple<Component &...>>
     get(entity_type entity) ENTT_NOEXCEPT {
     get(entity_type entity) ENTT_NOEXCEPT {
-        return std::tuple<Component &...>{get<Component>(entity)...};
+        return { get<Component>(entity)... };
     }
     }
 
 
     /**
     /**

+ 1 - 0
src/entt/entt.hpp

@@ -5,6 +5,7 @@
 #include "entity/actor.hpp"
 #include "entity/actor.hpp"
 #include "entity/entt_traits.hpp"
 #include "entity/entt_traits.hpp"
 #include "entity/helper.hpp"
 #include "entity/helper.hpp"
+#include "entity/prototype.hpp"
 #include "entity/registry.hpp"
 #include "entity/registry.hpp"
 #include "entity/snapshot.hpp"
 #include "entity/snapshot.hpp"
 #include "entity/sparse_set.hpp"
 #include "entity/sparse_set.hpp"

+ 1 - 0
test/CMakeLists.txt

@@ -54,6 +54,7 @@ ADD_ENTT_TEST(ident entt/core/ident.cpp)
 
 
 ADD_ENTT_TEST(actor entt/entity/actor.cpp)
 ADD_ENTT_TEST(actor entt/entity/actor.cpp)
 ADD_ENTT_TEST(helper entt/entity/helper.cpp)
 ADD_ENTT_TEST(helper entt/entity/helper.cpp)
+ADD_ENTT_TEST(prototype entt/entity/prototype.cpp)
 ADD_ENTT_TEST(registry entt/entity/registry.cpp)
 ADD_ENTT_TEST(registry entt/entity/registry.cpp)
 ADD_ENTT_TEST(snapshot entt/entity/snapshot.cpp)
 ADD_ENTT_TEST(snapshot entt/entity/snapshot.cpp)
 ADD_ENTT_TEST(sparse_set entt/entity/sparse_set.cpp)
 ADD_ENTT_TEST(sparse_set entt/entity/sparse_set.cpp)

+ 62 - 0
test/entt/entity/prototype.cpp

@@ -0,0 +1,62 @@
+#include <gtest/gtest.h>
+#include <entt/entity/prototype.hpp>
+#include <entt/entity/registry.hpp>
+
+TEST(Prototype, Functionalities) {
+    entt::DefaultRegistry registry;
+    entt::DefaultPrototype prototype;
+    const auto &cprototype = prototype;
+
+    ASSERT_FALSE((prototype.has<int, char>()));
+    ASSERT_TRUE(registry.empty());
+
+    prototype.set<int>(3);
+    prototype.set<char>('c');
+
+    ASSERT_EQ(prototype.get<int>(), 3);
+    ASSERT_EQ(cprototype.get<char>(), 'c');
+    ASSERT_EQ(std::get<0>(prototype.get<int, char>()), 3);
+    ASSERT_EQ(std::get<1>(cprototype.get<int, char>()), 'c');
+
+    const auto e0 = prototype(registry);
+
+    ASSERT_TRUE((prototype.has<int, char>()));
+    ASSERT_FALSE(registry.orphan(e0));
+    ASSERT_FALSE(registry.empty());
+
+    const auto e1 = prototype(registry);
+    prototype(registry, e0);
+
+    ASSERT_FALSE(registry.orphan(e0));
+    ASSERT_FALSE(registry.orphan(e1));
+
+    ASSERT_TRUE((registry.has<int, char>(e0)));
+    ASSERT_TRUE((registry.has<int, char>(e1)));
+
+    registry.remove<int>(e0);
+    registry.remove<int>(e1);
+    prototype.unset<int>();
+
+    ASSERT_FALSE((prototype.has<int, char>()));
+    ASSERT_FALSE((prototype.has<int>()));
+    ASSERT_TRUE((prototype.has<char>()));
+
+    prototype(registry, e0);
+    prototype(registry, e1);
+
+    ASSERT_FALSE(registry.has<int>(e0));
+    ASSERT_FALSE(registry.has<int>(e1));
+
+    ASSERT_EQ(registry.get<char>(e0), 'c');
+    ASSERT_EQ(registry.get<char>(e1), 'c');
+
+    registry.get<char>(e0) = '*';
+    prototype.assign(registry, e0);
+
+    ASSERT_EQ(registry.get<char>(e0), '*');
+
+    registry.get<char>(e1) = '*';
+    prototype.accommodate(registry, e1);
+
+    ASSERT_EQ(registry.get<char>(e1), 'c');
+}