Browse Source

review: prototype (#89)

Michele Caini 7 years ago
parent
commit
e07128760e
5 changed files with 311 additions and 108 deletions
  1. 13 9
      README.md
  2. 217 90
      src/entt/entity/prototype.hpp
  3. 2 2
      src/entt/entity/registry.hpp
  4. 4 4
      src/entt/entity/view.hpp
  5. 75 3
      test/entt/entity/prototype.cpp

+ 13 - 9
README.md

@@ -1057,16 +1057,15 @@ 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;
+entt::DefaultRegistry registry;
+entt::DefaultPrototype prototype{registry};
 
 prototype.set<Position>(100.f, 100.f);
 prototype.set<Velocity>(0.f, 0.f);
 
 // ...
 
-entt::DefaultRegistry registry;
-
-const auto entity = prototype(registry);
+const auto entity = prototype();
 ```
 
 To assign and remove components from a prototype, it offers two dedicated member
@@ -1079,21 +1078,21 @@ 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);
+  const auto entity = prototype();
   ```
   It is equivalent to the following invokation:
   ```cpp
-  const auto entity = prototype.create(registry);
+  const auto entity = prototype.create();
   ```
 
 * In case we want to initialize an already existing entity, we can provide the
   `operator()` directly with the entity identifier:
   ```cpp
-  prototype(registry, entity);
+  prototype(entity);
   ```
   It is equivalent to the following invokation:
   ```cpp
-  prototype.assign(registry, entity);
+  prototype.assign(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
@@ -1102,9 +1101,14 @@ Creating an entity from a prototype is straightforward:
 * Finally, to assign or replace all the components for an entity, thus
   overwriting existing ones:
   ```cpp
-  prototype.accommodate(registry, entity);
+  prototype.accommodate(entity);
   ```
 
+In the examples above, the prototype uses its underlying registry to create
+entities and components both for its purposes and when it's cloned. To use a
+different repository to clone a prototype, all the member functions accept also
+a reference to a valid registry as a first argument.
+
 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

+ 217 - 90
src/entt/entity/prototype.hpp

@@ -3,11 +3,10 @@
 
 
 #include <tuple>
-#include <memory>
-#include <vector>
 #include <utility>
 #include <cstddef>
-#include <algorithm>
+#include <type_traits>
+#include <unordered_map>
 #include "registry.hpp"
 
 
@@ -24,43 +23,28 @@ namespace entt {
  * entities of a registry at once.
  *
  * @note
- * Components used along with prototypes must be copy constructible.
+ * Components used along with prototypes must be copy constructible. Prototypes
+ * wrap component types with custom types, so they do not interfere with other
+ * users of the registry they were built with.
+ *
+ * @warning
+ * Prototypes directly use their underlying registries to store entities and
+ * components for their purposes. Users must ensure that the lifetime of a
+ * registry and its contents exceed that of the prototypes that use it.
  *
  * @tparam Entity A valid entity type (see entt_traits for more details).
  */
 template<typename Entity>
-class Prototype {
+class Prototype final {
+    using fn_type = void(*)(const Prototype &, Registry<Entity> &, const Entity);
     using component_type = typename Registry<Entity>::component_type;
-    using fn_type = void(*)(Registry<Entity> &, const Entity, const void *);
-    using deleter_type = void(*)(void *);
-    using ptr_type = std::unique_ptr<void, deleter_type>;
-
-    template<typename Component>
-    static void accommodate(Registry<Entity> &registry, const Entity entity, const void *component) {
-        const auto &ref = *static_cast<const Component *>(component);
-        registry.template accommodate<Component>(entity, ref);
-    }
 
     template<typename Component>
-    static void assign(Registry<Entity> &registry, const Entity entity, const void *component) {
-        if(!registry.template has<Component>(entity)) {
-            const auto &ref = *static_cast<const Component *>(component);
-            registry.template assign<Component>(entity, ref);
-        }
-    }
+    struct Wrapper { Component component; };
 
-    struct Handler final {
-        Handler(ptr_type component, const fn_type accommodate, const fn_type assign, const 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;
+    struct Handler {
+        fn_type accommodate;
+        fn_type assign;
     };
 
 public:
@@ -71,6 +55,22 @@ public:
     /*! @brief Unsigned integer type. */
     using size_type = std::size_t;
 
+    /**
+     * @brief Constructs a prototype that is bound to a given registry.
+     * @param registry A valid reference to a registry.
+     */
+    Prototype(Registry<Entity> &registry)
+        : registry{registry},
+          entity{registry.create()}
+    {}
+
+    /**
+     * @brief Releases all its resources.
+     */
+    ~Prototype() {
+        registry.destroy(entity);
+    }
+
     /**
      * @brief Assigns to or replaces the given component of a prototype.
      * @tparam Component Type of component to assign or replace.
@@ -80,22 +80,21 @@ public:
      */
     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};
+        fn_type accommodate = [](const Prototype &prototype, Registry<Entity> &other, const Entity dst) {
+            const auto &wrapper = prototype.registry.template get<Wrapper<Component>>(prototype.entity);
+            other.template accommodate<Component>(dst, wrapper.component);
+        };
 
-        if(it == handlers.cend()) {
-            handlers.emplace_back(std::move(component), &Prototype::accommodate<Component>, &Prototype::assign<Component>, ctype);
-        } else {
-            it->component = std::move(component);
-        }
+        fn_type assign = [](const Prototype &prototype, Registry<Entity> &other, const Entity dst) {
+            if(!other.template has<Component>(dst)) {
+                const auto &wrapper = prototype.registry.template get<Wrapper<Component>>(prototype.entity);
+                other.template accommodate<Component>(dst, wrapper.component);
+            }
+        };
 
-        return get<Component>();
+        handlers[registry.template type<Component>()] = Handler{accommodate, assign};
+        auto &wrapper = registry.template accommodate<Wrapper<Component>>(entity, Component{std::forward<Args>(args)...});
+        return wrapper.component;
     }
 
     /**
@@ -104,9 +103,8 @@ public:
      */
     template<typename Component>
     void unset() ENTT_NOEXCEPT {
-        handlers.erase(std::remove_if(handlers.begin(), handlers.end(), [](const auto &handler) {
-            return handler.type == registry_type::template type<Component>();
-        }), handlers.end());
+        registry.template reset<Wrapper<Component>>(entity);
+        handlers.erase(registry.template type<Component>());
     }
 
     /**
@@ -116,17 +114,7 @@ public:
      */
     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();
-        };
-
-        bool all = true;
-        using accumulator_type = bool[];
-        accumulator_type accumulator = { all, (all = all && found(registry_type::template type<Component>()))... };
-        (void)accumulator;
-        return all;
+        return registry.template has<Wrapper<Component>...>(entity);
     }
 
     /**
@@ -143,13 +131,7 @@ public:
      */
     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());
+        return registry.template get<Wrapper<Component>>(entity).component;
     }
 
     /**
@@ -182,7 +164,7 @@ public:
      * @return References to the components owned by the prototype.
      */
     template<typename... Component>
-    std::enable_if_t<(sizeof...(Component) > 1), std::tuple<const Component &...>>
+    inline std::enable_if_t<(sizeof...(Component) > 1), std::tuple<const Component &...>>
     get() const ENTT_NOEXCEPT {
         return std::tuple<const Component &...>{get<Component>()...};
     }
@@ -200,7 +182,7 @@ public:
      * @return References to the components owned by the prototype.
      */
     template<typename... Component>
-    std::enable_if_t<(sizeof...(Component) > 1), std::tuple<Component &...>>
+    inline std::enable_if_t<(sizeof...(Component) > 1), std::tuple<Component &...>>
     get() ENTT_NOEXCEPT {
         return std::tuple<Component &...>{get<Component>()...};
     }
@@ -215,10 +197,38 @@ public:
      * prototype(registry, entity);
      * @endcode
      *
-     * @param registry A valid reference to a registry.
+     * @note
+     * The registry may or may not be different from the one already used by
+     * the prototype. There is also an overload that directly uses the
+     * underlying registry.
+     *
+     * @param other A valid reference to a registry.
+     * @return A valid entity identifier.
+     */
+    entity_type create(registry_type &other) const {
+        const auto entity = other.create();
+        assign(other, entity);
+        return entity;
+    }
+
+    /**
+     * @brief Creates a new entity using a given prototype.
+     *
+     * Utility shortcut, equivalent to the following snippet:
+     *
+     * @code{.cpp}
+     * const auto entity = registry.create();
+     * prototype(entity);
+     * @endcode
+     *
+     * @note
+     * This overload directly uses the underlying registry as a working space.
+     * Therefore, the components of the prototype and of the entity will share
+     * the same registry.
+     *
      * @return A valid entity identifier.
      */
-    entity_type create(registry_type &registry) {
+    entity_type create() const {
         const auto entity = registry.create();
         assign(registry, entity);
         return entity;
@@ -232,18 +242,49 @@ public:
      * In other words, only those components that the entity doesn't own yet are
      * copied over. All the other components remain unchanged.
      *
+     * @note
+     * The registry may or may not be different from the one already used by
+     * the prototype. There is also an overload that directly uses the
+     * underlying registry.
+     *
      * @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.
-     * @param entity A valid entity identifier.
+     * @param other A valid reference to a registry.
+     * @param dst A valid entity identifier.
+     */
+    void assign(registry_type &other, const entity_type dst) const {
+        for(auto &handler: handlers) {
+            handler.second.assign(*this, other, dst);
+        }
+    }
+
+    /**
+     * @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.
+     *
+     * @note
+     * This overload directly uses the underlying registry as a working space.
+     * Therefore, the components of the prototype and of the entity will share
+     * the same registry.
+     *
+     * @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 dst A valid entity identifier.
      */
-    void assign(registry_type &registry, const entity_type entity) {
-        std::for_each(handlers.begin(), handlers.end(), [&registry, entity](auto &&handler) {
-            handler.assign(registry, entity, handler.component.get());
-        });
+    void assign(const entity_type dst) const {
+        for(auto &handler: handlers) {
+            handler.second.assign(*this, registry, dst);
+        }
     }
 
     /**
@@ -252,18 +293,47 @@ public:
      * Existing components are overwritten, if any. All the other components
      * will be copied over to the target entity.
      *
+     * @note
+     * The registry may or may not be different from the one already used by
+     * the prototype. There is also an overload that directly uses the
+     * underlying registry.
+     *
      * @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.
-     * @param entity A valid entity identifier.
+     * @param other A valid reference to a registry.
+     * @param dst A valid entity identifier.
      */
-    void accommodate(registry_type &registry, const entity_type entity) {
-        std::for_each(handlers.begin(), handlers.end(), [&registry, entity](auto &&handler) {
-            handler.accommodate(registry, entity, handler.component.get());
-        });
+    void accommodate(registry_type &other, const entity_type dst) const {
+        for(auto &handler: handlers) {
+            handler.second.accommodate(*this, other, dst);
+        }
+    }
+
+    /**
+     * @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.
+     *
+     * @note
+     * This overload directly uses the underlying registry as a working space.
+     * Therefore, the components of the prototype and of the entity will share
+     * the same registry.
+     *
+     * @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 dst A valid entity identifier.
+     */
+    void accommodate(const entity_type dst) const {
+        for(auto &handler: handlers) {
+            handler.second.accommodate(*this, registry, dst);
+        }
     }
 
     /**
@@ -274,16 +344,45 @@ public:
      * In other words, only the components that the entity doesn't own yet are
      * copied over. All the other components remain unchanged.
      *
+     * @note
+     * The registry may or may not be different from the one already used by
+     * the prototype. There is also an overload that directly uses the
+     * underlying registry.
+     *
      * @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.
-     * @param entity A valid entity identifier.
+     * @param other A valid reference to a registry.
+     * @param dst A valid entity identifier.
      */
-    inline void operator()(registry_type &registry, const entity_type entity) ENTT_NOEXCEPT {
-        assign(registry, entity);
+    inline void operator()(registry_type &other, const entity_type dst) const ENTT_NOEXCEPT {
+        assign(other, dst);
+    }
+
+    /**
+     * @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.
+     *
+     * @note
+     * This overload directly uses the underlying registry as a working space.
+     * Therefore, the components of the prototype and of the entity will share
+     * the same registry.
+     *
+     * @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 dst A valid entity identifier.
+     */
+    inline void operator()(const entity_type dst) const ENTT_NOEXCEPT {
+        assign(registry, dst);
     }
 
     /**
@@ -296,15 +395,43 @@ public:
      * prototype(registry, entity);
      * @endcode
      *
-     * @param registry A valid reference to a registry.
+     * @note
+     * The registry may or may not be different from the one already used by
+     * the prototype. There is also an overload that directly uses the
+     * underlying registry.
+     *
+     * @param other A valid reference to a registry.
+     * @return A valid entity identifier.
+     */
+    inline entity_type operator()(registry_type &other) const ENTT_NOEXCEPT {
+        return create(other);
+    }
+
+    /**
+     * @brief Creates a new entity using a given prototype.
+     *
+     * Utility shortcut, equivalent to the following snippet:
+     *
+     * @code{.cpp}
+     * const auto entity = registry.create();
+     * prototype(entity);
+     * @endcode
+     *
+     * @note
+     * This overload directly uses the underlying registry as a working space.
+     * Therefore, the components of the prototype and of the entity will share
+     * the same registry.
+     *
      * @return A valid entity identifier.
      */
-    inline entity_type operator()(registry_type &registry) ENTT_NOEXCEPT {
+    inline entity_type operator()() const ENTT_NOEXCEPT {
         return create(registry);
     }
 
 private:
-    std::vector<Handler> handlers;
+    std::unordered_map<component_type, Handler> handlers;
+    Registry<Entity> &registry;
+    entity_type entity;
 };
 
 

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

@@ -756,7 +756,7 @@ public:
      * @return References to the components owned by the entity.
      */
     template<typename... Component>
-    std::enable_if_t<(sizeof...(Component) > 1), std::tuple<const Component &...>>
+    inline std::enable_if_t<(sizeof...(Component) > 1), std::tuple<const Component &...>>
     get(const entity_type entity) const ENTT_NOEXCEPT {
         return std::tuple<const Component &...>{get<Component>(entity)...};
     }
@@ -776,7 +776,7 @@ public:
      * @return References to the components owned by the entity.
      */
     template<typename... Component>
-    std::enable_if_t<(sizeof...(Component) > 1), std::tuple<Component &...>>
+    inline std::enable_if_t<(sizeof...(Component) > 1), std::tuple<Component &...>>
     get(const entity_type entity) ENTT_NOEXCEPT {
         return std::tuple<Component &...>{get<Component>(entity)...};
     }

+ 4 - 4
src/entt/entity/view.hpp

@@ -306,7 +306,7 @@ public:
      * @return The components assigned to the entity.
      */
     template<typename... Comp>
-    std::enable_if_t<(sizeof...(Comp) > 1), std::tuple<const Comp &...>>
+    inline std::enable_if_t<(sizeof...(Comp) > 1), std::tuple<const Comp &...>>
     get(const entity_type entity) const ENTT_NOEXCEPT {
         assert(contains(entity));
         return std::tuple<const Comp &...>{get<Comp>(entity)...};
@@ -330,7 +330,7 @@ public:
      * @return The components assigned to the entity.
      */
     template<typename... Comp>
-    std::enable_if_t<(sizeof...(Comp) > 1), std::tuple<Comp &...>>
+    inline std::enable_if_t<(sizeof...(Comp) > 1), std::tuple<Comp &...>>
     get(const entity_type entity) ENTT_NOEXCEPT {
         assert(contains(entity));
         return std::tuple<Comp &...>{get<Comp>(entity)...};
@@ -819,7 +819,7 @@ public:
      * @return The components assigned to the entity.
      */
     template<typename... Comp>
-    std::enable_if_t<(sizeof...(Comp) > 1), std::tuple<const Comp &...>>
+    inline std::enable_if_t<(sizeof...(Comp) > 1), std::tuple<const Comp &...>>
     get(const entity_type entity) const ENTT_NOEXCEPT {
         assert(contains(entity));
         return std::tuple<const Comp &...>{get<Comp>(entity)...};
@@ -843,7 +843,7 @@ public:
      * @return The components assigned to the entity.
      */
     template<typename... Comp>
-    std::enable_if_t<(sizeof...(Comp) > 1), std::tuple<Comp &...>>
+    inline std::enable_if_t<(sizeof...(Comp) > 1), std::tuple<Comp &...>>
     get(const entity_type entity) ENTT_NOEXCEPT {
         assert(contains(entity));
         return std::tuple<Comp &...>{get<Comp>(entity)...};

+ 75 - 3
test/entt/entity/prototype.cpp

@@ -2,13 +2,73 @@
 #include <entt/entity/prototype.hpp>
 #include <entt/entity/registry.hpp>
 
-TEST(Prototype, Functionalities) {
+TEST(Prototype, SameRegistry) {
     entt::DefaultRegistry registry;
-    entt::DefaultPrototype prototype;
+    entt::DefaultPrototype prototype{registry};
     const auto &cprototype = prototype;
 
+    ASSERT_FALSE(registry.empty());
     ASSERT_FALSE((prototype.has<int, char>()));
+
+    ASSERT_EQ(prototype.set<int>(2), 2);
+    ASSERT_EQ(prototype.set<int>(3), 3);
+    ASSERT_EQ(prototype.set<char>('c'), '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();
+
+    ASSERT_TRUE((prototype.has<int, char>()));
+    ASSERT_FALSE(registry.orphan(e0));
+
+    const auto e1 = prototype();
+    prototype(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(e0);
+    prototype(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(e0);
+
+    ASSERT_EQ(registry.get<char>(e0), '*');
+
+    registry.get<char>(e1) = '*';
+    prototype.accommodate(e1);
+
+    ASSERT_EQ(registry.get<char>(e1), 'c');
+}
+
+TEST(Prototype, OtherRegistry) {
+    entt::DefaultRegistry registry;
+    entt::DefaultRegistry repository;
+    entt::DefaultPrototype prototype{repository};
+    const auto &cprototype = prototype;
+
     ASSERT_TRUE(registry.empty());
+    ASSERT_FALSE((prototype.has<int, char>()));
 
     ASSERT_EQ(prototype.set<int>(2), 2);
     ASSERT_EQ(prototype.set<int>(3), 3);
@@ -23,7 +83,6 @@ TEST(Prototype, Functionalities) {
 
     ASSERT_TRUE((prototype.has<int, char>()));
     ASSERT_FALSE(registry.orphan(e0));
-    ASSERT_FALSE(registry.empty());
 
     const auto e1 = prototype(registry);
     prototype(registry, e0);
@@ -61,3 +120,16 @@ TEST(Prototype, Functionalities) {
 
     ASSERT_EQ(registry.get<char>(e1), 'c');
 }
+
+TEST(Prototype, RAII) {
+    entt::DefaultRegistry registry;
+
+    {
+        entt::DefaultPrototype prototype{registry};
+        prototype.set<int>(0);
+
+        ASSERT_FALSE(registry.empty());
+    }
+
+    ASSERT_TRUE(registry.empty());
+}