Michele Caini 7 лет назад
Родитель
Сommit
9a785ceb2e
8 измененных файлов с 386 добавлено и 145 удалено
  1. 67 2
      README.md
  2. 5 1
      TODO
  3. 1 1
      src/entt/entity/helper.hpp
  4. 247 139
      src/entt/entity/prototype.hpp
  5. 2 2
      src/entt/entity/registry.hpp
  6. 1 0
      src/entt/entt.hpp
  7. 1 0
      test/CMakeLists.txt
  8. 62 0
      test/entt/entity/prototype.cpp

+ 67 - 2
README.md

@@ -26,6 +26,7 @@
          * [Continuous loader](#continuous-loader)
          * [Archives](#archives)
          * [One example to rule them all](#one-example-to-rule-them-all)
+      * [Prototype](#prototype)
       * [Helpers](#helpers)
          * [Dependency function](#dependency-function)
    * [View: to persist or not to persist?](#view-to-persist-or-not-to-persist)
@@ -679,7 +680,7 @@ destroyed.
 #### Who let the tags out?
 
 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/>
 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
@@ -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
 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,
   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
@@ -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
 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
 
 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
 * 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)
+* 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?
 * 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?
 * "singleton mode" for tags (see #66)
 * 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.
  *
  * 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 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
 #define ENTT_ENTITY_PROTOTYPE_HPP
 
+
+#include <tuple>
+#include <memory>
+#include <vector>
+#include <utility>
+#include <cstddef>
+#include <algorithm>
 #include "registry.hpp"
 
+
 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).
  */
-template <typename Entity>
+template<typename Entity>
 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:
-    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
-     * 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
-     * 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
-     * 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
-     * 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
-     * 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:
-    std::vector<std::unique_ptr<StorageBase>> comps;
+    std::vector<Handler> handlers;
 };
 
+
 /**
  * @brief Default prototype
  */
 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>
     std::enable_if_t<(sizeof...(Component) > 1), std::tuple<const Component &...>>
     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>
     std::enable_if_t<(sizeof...(Component) > 1), std::tuple<Component &...>>
     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/entt_traits.hpp"
 #include "entity/helper.hpp"
+#include "entity/prototype.hpp"
 #include "entity/registry.hpp"
 #include "entity/snapshot.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(helper entt/entity/helper.cpp)
+ADD_ENTT_TEST(prototype entt/entity/prototype.cpp)
 ADD_ENTT_TEST(registry entt/entity/registry.cpp)
 ADD_ENTT_TEST(snapshot entt/entity/snapshot.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');
+}