Michele Caini 7 år sedan
förälder
incheckning
2d945e426b

+ 230 - 0
src/entt/entity/attachee.hpp

@@ -0,0 +1,230 @@
+#ifndef ENTT_ENTITY_ATTACHEE_HPP
+#define ENTT_ENTITY_ATTACHEE_HPP
+
+
+#include <cassert>
+#include <utility>
+#include <type_traits>
+#include "../config/config.h"
+#include "entity.hpp"
+
+
+namespace entt {
+
+
+/**
+ * @brief Attachee.
+ *
+ * Primary template isn't defined on purpose. All the specializations give a
+ * compile-time error, but for a few reasonable cases.
+ */
+template<typename...>
+class Attachee;
+
+
+/**
+ * @brief Basic attachee implementation.
+ *
+ * Convenience data structure used to store single instance components.
+ *
+ * @tparam Entity A valid entity type (see entt_traits for more details).
+ */
+template<typename Entity>
+class Attachee<Entity> {
+public:
+    /*! @brief Underlying entity identifier. */
+    using entity_type = Entity;
+
+    /*! @brief Default constructor. */
+    Attachee() ENTT_NOEXCEPT
+        : owner{null}
+    {}
+
+    /*! @brief Default copy constructor. */
+    Attachee(const Attachee &) = default;
+    /*! @brief Default move constructor. */
+    Attachee(Attachee &&) = default;
+
+    /*! @brief Default copy assignment operator. @return This attachee. */
+    Attachee & operator=(const Attachee &) = default;
+    /*! @brief Default move assignment operator. @return This attachee. */
+    Attachee & operator=(Attachee &&) = default;
+
+    /*! @brief Default destructor. */
+    virtual ~Attachee() ENTT_NOEXCEPT = default;
+
+    /**
+     * @brief Returns the owner of an attachee.
+     * @return A valid entity identifier if an owner exists, the null entity
+     * identifier otherwise.
+     */
+    inline entity_type get() const ENTT_NOEXCEPT {
+        return owner;
+    }
+
+    /**
+     * @brief Assigns an entity to an attachee.
+     *
+     * @warning
+     * Attempting to assigns an entity to an attachee that already has an owner
+     * results in undefined behavior.<br/>
+     * An assertion will abort the execution at runtime in debug mode in case
+     * the attachee already has an owner.
+     *
+     * @param entity A valid entity identifier.
+     */
+    inline void construct(const entity_type entity) ENTT_NOEXCEPT {
+        assert(owner == null);
+        owner = entity;
+    }
+
+    /**
+     * @brief Removes an entity from an attachee.
+     *
+     * @warning
+     * Attempting to free an empty attachee results in undefined behavior.<br/>
+     * An assertion will abort the execution at runtime in debug mode if the
+     * attachee is already empty.
+     */
+    virtual void destroy() ENTT_NOEXCEPT {
+        assert(owner != null);
+        owner = null;
+    }
+
+private:
+    entity_type owner;
+};
+
+
+/**
+ * @brief Extended attachee implementation.
+ *
+ * This specialization of an attachee associates an object to an entity. The
+ * main purpose of this class is to use attachees to store tags in a Registry.
+ * It guarantees fast access both to the element and to the entity.
+ *
+ * @sa Attachee<Entity>
+ *
+ * @tparam Entity A valid entity type (see entt_traits for more details).
+ * @tparam Type Type of object assigned to the entity.
+ */
+template<typename Entity, typename Type>
+class Attachee<Entity, Type>: public Attachee<Entity> {
+    using underlying_type = Attachee<Entity>;
+
+public:
+    /*! @brief Type of the object associated to the attachee. */
+    using object_type = Type;
+    /*! @brief Underlying entity identifier. */
+    using entity_type = typename underlying_type::entity_type;
+
+    /*! @brief Default constructor. */
+    Attachee() ENTT_NOEXCEPT = default;
+
+    /*! @brief Copying an attachee isn't allowed. */
+    Attachee(const Attachee &) = delete;
+    /*! @brief Moving an attachee isn't allowed. */
+    Attachee(Attachee &&) = delete;
+
+    /*! @brief Copying an attachee isn't allowed. @return This attachee. */
+    Attachee & operator=(const Attachee &) = delete;
+    /*! @brief Moving an attachee isn't allowed. @return This attachee. */
+    Attachee & operator=(Attachee &&) = delete;
+
+    /*! @brief Default destructor. */
+    ~Attachee() {
+        if(underlying_type::get() != null) {
+            reinterpret_cast<Type *>(&storage)->~Type();
+        }
+    }
+
+    /**
+     * @brief Returns the object associated to an attachee.
+     *
+     * @warning
+     * Attempting to query an empty attachee results in undefined behavior.<br/>
+     * An assertion will abort the execution at runtime in debug mode if the
+     * attachee is empty.
+     *
+     * @return The object associated to the attachee.
+     */
+    const Type & get() const ENTT_NOEXCEPT {
+        assert(underlying_type::get() != null);
+        return *reinterpret_cast<const Type *>(&storage);
+    }
+
+    /**
+     * @brief Returns the object associated to an attachee.
+     *
+     * @warning
+     * Attempting to query an empty attachee results in undefined behavior.<br/>
+     * An assertion will abort the execution at runtime in debug mode if the
+     * attachee is empty.
+     *
+     * @return The object associated to the attachee.
+     */
+    Type & get() ENTT_NOEXCEPT {
+        return const_cast<Type &>(const_cast<const Attachee *>(this)->get());
+    }
+
+    /**
+     * @brief Assigns an entity to an attachee and constructs its object.
+     *
+     * @warning
+     * Attempting to assigns an entity to an attachee that already has an owner
+     * results in undefined behavior.<br/>
+     * An assertion will abort the execution at runtime in debug mode in case
+     * the attachee already has an owner.
+     *
+     * @tparam Args Types of arguments to use to construct the object.
+     * @param entity A valid entity identifier.
+     * @param args Parameters to use to construct an object for the entity.
+     * @return The object associated to the attachee.
+     */
+    template<typename... Args>
+    Type & construct(entity_type entity, Args &&... args) ENTT_NOEXCEPT {
+        underlying_type::construct(entity);
+        new (&storage) Type{std::forward<Args>(args)...};
+        return *reinterpret_cast<Type *>(&storage);
+    }
+
+    /**
+     * @brief Removes an entity from an attachee and destroies its object.
+     *
+     * @warning
+     * Attempting to free an empty attachee results in undefined behavior.<br/>
+     * An assertion will abort the execution at runtime in debug mode if the
+     * attachee is already empty.
+     */
+    void destroy() ENTT_NOEXCEPT override {
+        reinterpret_cast<Type *>(&storage)->~Type();
+        underlying_type::destroy();
+    }
+
+    /**
+     * @brief Changes the owner of an attachee.
+     *
+     * The ownership of the attachee is transferred from one entity to another.
+     *
+     * @warning
+     * Attempting to transfer the ownership of an attachee that hasn't an owner
+     * results in undefined behavior.<br/>
+     * An assertion will abort the execution at runtime in debug mode in case
+     * the attachee hasn't an owner yet.
+     *
+     * @param entity A valid entity identifier.
+     */
+    void move(const entity_type entity) ENTT_NOEXCEPT {
+        underlying_type::destroy();
+        underlying_type::construct(entity);
+    }
+
+private:
+    std::aligned_storage_t<sizeof(Type), alignof(Type)> storage;
+};
+
+
+}
+
+
+#endif // ENTT_ENTITY_ATTACHEE_HPP

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

@@ -23,7 +23,7 @@ struct Null {
 
     template<typename Entity>
     constexpr operator Entity() const ENTT_NOEXCEPT {
-        using traits_type = entt_traits<Entity>;
+        using traits_type = entt::entt_traits<Entity>;
         return traits_type::entity_mask | (traits_type::version_mask << traits_type::entity_shift);
     }
 

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

@@ -140,7 +140,7 @@ public:
         basic_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);
+                other.template assign<Component>(dst, wrapper.component);
             }
         };
 

+ 143 - 110
src/entt/entity/registry.hpp

@@ -16,6 +16,8 @@
 #include "../core/algorithm.hpp"
 #include "../core/family.hpp"
 #include "../signal/sigh.hpp"
+#include "attachee.hpp"
+#include "entity.hpp"
 #include "entt_traits.hpp"
 #include "snapshot.hpp"
 #include "sparse_set.hpp"
@@ -44,6 +46,78 @@ class Registry {
     using signal_type = SigH<void(Registry &, const Entity)>;
     using traits_type = entt_traits<Entity>;
 
+    template<typename Component>
+    struct Pool: SparseSet<Entity, Component> {
+        Pool(Registry *registry) ENTT_NOEXCEPT
+            : registry{registry}
+        {}
+
+        template<typename... Args>
+        Component & construct(const Entity entity, Args &&... args) {
+            auto &component = SparseSet<Entity, Component>::construct(entity, std::forward<Args>(args)...);
+            ctor.publish(*registry, entity);
+            return component;
+        }
+
+        void destroy(const Entity entity) override {
+            dtor.publish(*registry, entity);
+            SparseSet<Entity, Component>::destroy(entity);
+        }
+
+        typename signal_type::sink_type construction() ENTT_NOEXCEPT {
+            return ctor.sink();
+        }
+
+        typename signal_type::sink_type destruction() ENTT_NOEXCEPT {
+            return dtor.sink();
+        }
+
+    private:
+        Registry *registry;
+        signal_type ctor;
+        signal_type dtor;
+    };
+
+    template<typename Tag>
+    struct Attaching: Attachee<Entity, Tag> {
+        Attaching(Registry *registry)
+            : registry{registry}
+        {}
+
+        template<typename... Args>
+        Tag & construct(const Entity entity, Args &&... args) ENTT_NOEXCEPT {
+            auto &tag = Attachee<Entity, Tag>::construct(entity, std::forward<Args>(args)...);
+            ctor.publish(*registry, entity);
+            return tag;
+        }
+
+        void destroy() ENTT_NOEXCEPT override {
+            dtor.publish(*registry, Attachee<Entity>::get());
+            Attachee<Entity, Tag>::destroy();
+        }
+
+        Entity move(const Entity entity) ENTT_NOEXCEPT {
+            const auto owner = Attachee<Entity>::get();
+            dtor.publish(*registry, owner);
+            Attachee<Entity, Tag>::move(entity);
+            ctor.publish(*registry, entity);
+            return owner;
+        }
+
+        typename signal_type::sink_type construction() ENTT_NOEXCEPT {
+            return ctor.sink();
+        }
+
+        typename signal_type::sink_type destruction() ENTT_NOEXCEPT {
+            return dtor.sink();
+        }
+
+    private:
+        Registry *registry;
+        signal_type ctor;
+        signal_type dtor;
+    };
+
     template<typename handler_family::family_type(*Type)(), typename... Component>
     static void creating(Registry &registry, const Entity entity) {
         if(registry.has<Component...>(entity)) {
@@ -57,28 +131,44 @@ class Registry {
         return handler.has(entity) ? handler.destroy(entity) : void();
     }
 
-    struct Attachee {
-        Attachee(const Entity entity) ENTT_NOEXCEPT: entity{entity} {}
-        virtual ~Attachee() = default;
-        Entity entity;
-    };
+    template<typename Tag>
+    inline bool managed(tag_t) const ENTT_NOEXCEPT {
+        const auto ttype = tag_family::type<Tag>();
+        return ttype < tags.size() && tags[ttype];
+    }
+
+    template<typename Component>
+    inline bool managed() const ENTT_NOEXCEPT {
+        const auto ctype = component_family::type<Component>();
+        return ctype < pools.size() && pools[ctype];
+    }
 
     template<typename Tag>
-    struct Attaching: Attachee {
-        // requirements for aggregates are relaxed only since C++17
-        template<typename... Args>
-        Attaching(const Entity entity, Args &&... args)
-            : Attachee{entity}, tag{std::forward<Args>(args)...}
-        {}
+    inline const Attaching<Tag> & pool(tag_t) const ENTT_NOEXCEPT {
+        assert(managed<Tag>(tag_t{}));
+        return static_cast<const Attaching<Tag> &>(*tags[tag_family::type<Tag>()]);
+    }
 
-        Tag tag;
-    };
+    template<typename Tag>
+    inline Attaching<Tag> & pool(tag_t) ENTT_NOEXCEPT {
+        return const_cast<Attaching<Tag> &>(const_cast<const Registry *>(this)->pool<Tag>(tag_t{}));
+    }
+
+    template<typename Component>
+    inline const Pool<Component> & pool() const ENTT_NOEXCEPT {
+        assert(managed<Component>());
+        return static_cast<const Pool<Component> &>(*pools[component_family::type<Component>()]);
+    }
+
+    template<typename Component>
+    inline Pool<Component> & pool() ENTT_NOEXCEPT {
+        return const_cast<Pool<Component> &>(const_cast<const Registry *>(this)->pool<Component>());
+    }
 
     template<typename Comp, std::size_t Pivot, typename... Component, std::size_t... Indexes>
     void connect(std::index_sequence<Indexes...>) {
-        auto &cpool = pools[component_family::type<Comp>()];
-        std::get<1>(cpool).sink().template connect<&Registry::creating<&handler_family::type<Component...>, std::tuple_element_t<(Indexes < Pivot ? Indexes : (Indexes+1)), std::tuple<Component...>>...>>();
-        std::get<2>(cpool).sink().template connect<&Registry::destroying<Component...>>();
+        pool<Comp>().construction().template connect<&Registry::creating<&handler_family::type<Component...>, std::tuple_element_t<(Indexes < Pivot ? Indexes : (Indexes+1)), std::tuple<Component...>>...>>();
+        pool<Comp>().destruction().template connect<&Registry::destroying<Component...>>();
     }
 
     template<typename... Component, std::size_t... Indexes>
@@ -90,9 +180,8 @@ class Registry {
 
     template<typename Comp, std::size_t Pivot, typename... Component, std::size_t... Indexes>
     void disconnect(std::index_sequence<Indexes...>) {
-        auto &cpool = pools[component_family::type<Comp>()];
-        std::get<1>(cpool).sink().template disconnect<&Registry::creating<&handler_family::type<Component...>, std::tuple_element_t<(Indexes < Pivot ? Indexes : (Indexes+1)), std::tuple<Component...>>...>>();
-        std::get<2>(cpool).sink().template disconnect<&Registry::destroying<Component...>>();
+        pool<Comp>().construction().template disconnect<&Registry::creating<&handler_family::type<Component...>, std::tuple_element_t<(Indexes < Pivot ? Indexes : (Indexes+1)), std::tuple<Component...>>...>>();
+        pool<Comp>().destruction().template disconnect<&Registry::destroying<Component...>>();
     }
 
     template<typename... Component, std::size_t... Indexes>
@@ -103,24 +192,6 @@ class Registry {
         (void)accumulator;
     }
 
-    template<typename Component>
-    inline bool managed() const ENTT_NOEXCEPT {
-        const auto ctype = component_family::type<Component>();
-        return ctype < pools.size() && std::get<0>(pools[ctype]);
-    }
-
-    template<typename Component>
-    inline const SparseSet<Entity, Component> & pool() const ENTT_NOEXCEPT {
-        assert(managed<Component>());
-        const auto ctype = component_family::type<Component>();
-        return static_cast<SparseSet<Entity, Component> &>(*std::get<0>(pools[ctype]));
-    }
-
-    template<typename Component>
-    inline SparseSet<Entity, Component> & pool() ENTT_NOEXCEPT {
-        return const_cast<SparseSet<Entity, Component> &>(const_cast<const Registry *>(this)->pool<Component>());
-    }
-
     template<typename Component>
     void assure() {
         const auto ctype = component_family::type<Component>();
@@ -129,10 +200,8 @@ class Registry {
             pools.resize(ctype + 1);
         }
 
-        auto &cpool = std::get<0>(pools[ctype]);
-
-        if(!cpool) {
-            cpool = std::make_unique<SparseSet<Entity, Component>>();
+        if(!pools[ctype]) {
+            pools[ctype] = std::make_unique<Pool<Component>>(this);
         }
     }
 
@@ -143,6 +212,10 @@ class Registry {
         if(!(ttype < tags.size())) {
             tags.resize(ttype + 1);
         }
+
+        if(!tags[ttype]) {
+            tags[ttype] = std::make_unique<Attaching<Tag>>(this);
+        }
     }
 
 public:
@@ -510,22 +583,18 @@ public:
         assert(valid(entity));
 
         for(auto pos = pools.size(); pos; --pos) {
-            auto &tup = pools[pos-1];
-            auto &cpool = std::get<0>(tup);
+            auto &cpool = pools[pos-1];
 
             if(cpool && cpool->has(entity)) {
-                std::get<2>(tup).publish(*this, entity);
                 cpool->destroy(entity);
             }
         };
 
         for(auto pos = tags.size(); pos; --pos) {
-            auto &tup = tags[pos-1];
-            auto &tag = std::get<0>(tup);
+            auto &tag = tags[pos-1];
 
-            if(tag && tag->entity == entity) {
-                std::get<2>(tup).publish(*this, entity);
-                tag.reset();
+            if(tag && tag->get() == entity) {
+                tag->destroy();
             }
         };
 
@@ -589,10 +658,7 @@ public:
         assert(valid(entity));
         assert(!has<Tag>());
         assure<Tag>(tag_t{});
-        auto &tup = tags[tag_family::type<Tag>()];
-        std::get<0>(tup).reset(new Attaching<Tag>{entity, std::forward<Args>(args)...});
-        std::get<1>(tup).publish(*this, entity);
-        return get<Tag>();
+        return pool<Tag>(tag_t{}).construct(entity, std::forward<Args>(args)...);
     }
 
     /**
@@ -619,9 +685,7 @@ public:
     Component & assign(const entity_type entity, Args &&... args) {
         assert(valid(entity));
         assure<Component>();
-        pool<Component>().construct(entity, std::forward<Args>(args)...);
-        std::get<1>(pools[component_family::type<Component>()]).publish(*this, entity);
-        return pool<Component>().get(entity);
+        return pool<Component>().construct(entity, std::forward<Args>(args)...);
     }
 
     /**
@@ -630,12 +694,7 @@ public:
      */
     template<typename Tag>
     void remove() {
-        if(has<Tag>()) {
-            auto &tup = tags[tag_family::type<Tag>()];
-            auto &tag = std::get<0>(tup);
-            std::get<2>(tup).publish(*this, tag->entity);
-            tag.reset();
-        }
+        return has<Tag>() ? pool<Tag>(tag_t{}).destroy() : void();
     }
 
     /**
@@ -655,8 +714,6 @@ public:
     void remove(const entity_type entity) {
         assert(valid(entity));
         assert(managed<Component>());
-        const auto ctype = component_family::type<Component>();
-        std::get<2>(pools[ctype]).publish(*this, entity);
         pool<Component>().destroy(entity);
     }
 
@@ -667,16 +724,7 @@ public:
      */
     template<typename Tag>
     bool has() const ENTT_NOEXCEPT {
-        const auto ttype = tag_family::type<Tag>();
-        bool found = false;
-
-        if(ttype < tags.size()) {
-            auto &tag = std::get<0>(tags[ttype]);
-            // it's a valid tag and the associated entity hasn't been destroyed in the meantime
-            found = tag && (tag->entity == (entities[tag->entity & traits_type::entity_mask]));
-        }
-
-        return found;
+        return managed<Tag>(tag_t{}) && tags[tag_family::type<Tag>()]->get() != null;
     }
 
     /**
@@ -734,7 +782,7 @@ public:
     template<typename Tag>
     const Tag & get() const ENTT_NOEXCEPT {
         assert(has<Tag>());
-        return static_cast<Attaching<Tag> *>(std::get<0>(tags[tag_family::type<Tag>()]).get())->tag;
+        return pool<Tag>(tag_t{}).get();
     }
 
     /**
@@ -902,28 +950,18 @@ public:
     entity_type move(const entity_type entity) ENTT_NOEXCEPT {
         assert(valid(entity));
         assert(has<Tag>());
-        auto &tag = std::get<0>(tags[tag_family::type<Tag>()]);
-        const auto owner = tag->entity;
-        tag->entity = entity;
-        return owner;
+        return pool<Tag>(tag_t{}).move(entity);
     }
 
     /**
      * @brief Gets the owner of the given tag, if any.
-     *
-     * @warning
-     * Attempting to get the owner of a tag that hasn't been previously attached
-     * to an entity results in undefined behavior.<br/>
-     * An assertion will abort the execution at runtime in debug mode if the
-     * tag hasn't an owner.
-     *
      * @tparam Tag Type of tag of which to get the owner.
-     * @return A valid entity identifier.
+     * @return A valid entity identifier if an owner exists, the null entity
+     * identifier otherwise.
      */
     template<typename Tag>
     entity_type attachee() const ENTT_NOEXCEPT {
-        assert(has<Tag>());
-        return std::get<0>(tags[tag_family::type<Tag>()])->entity;
+        return managed<Tag>(tag_t{}) ? tags[tag_family::type<Tag>()]->get() : null;
     }
 
     /**
@@ -957,9 +995,9 @@ public:
         assure<Component>();
         auto &cpool = pool<Component>();
 
-        return (cpool.has(entity)
+        return cpool.has(entity)
                 ? cpool.get(entity) = Component{std::forward<Args>(args)...}
-                : cpool.construct(entity, std::forward<Args>(args)...));
+                : cpool.construct(entity, std::forward<Args>(args)...);
     }
 
     /**
@@ -988,7 +1026,7 @@ public:
     template<typename Tag>
     sink_type construction(tag_t) ENTT_NOEXCEPT {
         assure<Tag>(tag_t{});
-        return std::get<1>(tags[tag_family::type<Tag>()]).sink();
+        return pool<Tag>(tag_t{}).construction();
     }
 
     /**
@@ -1017,7 +1055,7 @@ public:
     template<typename Component>
     sink_type construction() ENTT_NOEXCEPT {
         assure<Component>();
-        return std::get<1>(pools[component_family::type<Component>()]).sink();
+        return pool<Component>().construction();
     }
 
     /**
@@ -1046,7 +1084,7 @@ public:
     template<typename Tag>
     sink_type destruction(tag_t) ENTT_NOEXCEPT {
         assure<Tag>(tag_t{});
-        return std::get<2>(tags[tag_family::type<Tag>()]).sink();
+        return pool<Tag>(tag_t{}).destruction();
     }
 
     /**
@@ -1075,7 +1113,7 @@ public:
     template<typename Component>
     sink_type destruction() ENTT_NOEXCEPT {
         assure<Component>();
-        return std::get<2>(pools[component_family::type<Component>()]).sink();
+        return pool<Component>().destruction();
     }
 
     /**
@@ -1180,11 +1218,9 @@ public:
     void reset(const entity_type entity) {
         assert(valid(entity));
         assure<Component>();
-        const auto ctype = component_family::type<Component>();
-        auto &cpool = *std::get<0>(pools[ctype]);
+        auto &cpool = pool<Component>();
 
         if(cpool.has(entity)) {
-            std::get<2>(pools[ctype]).publish(*this, entity);
             cpool.destroy(entity);
         }
     }
@@ -1200,12 +1236,9 @@ public:
     template<typename Component>
     void reset() {
         assure<Component>();
-        const auto ctype = component_family::type<Component>();
-        auto &cpool = *std::get<0>(pools[ctype]);
-        auto &sig = std::get<2>(pools[ctype]);
+        auto &cpool = pool<Component>();
 
-        for(const auto entity: cpool) {
-            sig.publish(*this, entity);
+        for(const auto entity: static_cast<SparseSet<Entity> &>(cpool)) {
             cpool.destroy(entity);
         }
     }
@@ -1278,13 +1311,13 @@ public:
         bool orphan = true;
 
         for(std::size_t i = 0; i < pools.size() && orphan; ++i) {
-            const auto &pool = std::get<0>(pools[i]);
-            orphan = !(pool && pool->has(entity));
+            const auto &cpool = pools[i];
+            orphan = !(cpool && cpool->has(entity));
         }
 
         for(std::size_t i = 0; i < tags.size() && orphan; ++i) {
-            const auto &tag = std::get<0>(tags[i]);
-            orphan = !(tag && (tag->entity == entity));
+            const auto &tag = tags[i];
+            orphan = !(tag && (tag->get() == entity));
         }
 
         return orphan;
@@ -1532,7 +1565,7 @@ public:
         std::vector<const SparseSet<Entity> *> set(last - first);
 
         std::transform(first, last, set.begin(), [this](const component_type ctype) {
-            return ctype < pools.size() ? std::get<0>(pools[ctype]).get() : nullptr;
+            return ctype < pools.size() ? pools[ctype].get() : nullptr;
         });
 
         return RuntimeView<Entity>{std::move(set)};
@@ -1606,8 +1639,8 @@ public:
 
 private:
     std::vector<std::unique_ptr<SparseSet<Entity>>> handlers;
-    std::vector<std::tuple<std::unique_ptr<SparseSet<Entity>>, signal_type, signal_type>> pools;
-    std::vector<std::tuple<std::unique_ptr<Attachee>, signal_type, signal_type>> tags;
+    std::vector<std::unique_ptr<SparseSet<Entity>>> pools;
+    std::vector<std::unique_ptr<Attachee<Entity>>> tags;
     std::vector<entity_type> entities;
     size_type available{};
     entity_type next{};

+ 1 - 0
src/entt/entt.hpp

@@ -4,6 +4,7 @@
 #include "core/ident.hpp"
 #include "core/monostate.hpp"
 #include "entity/actor.hpp"
+#include "entity/attachee.hpp"
 #include "entity/entity.hpp"
 #include "entity/entt_traits.hpp"
 #include "entity/helper.hpp"

+ 1 - 0
test/CMakeLists.txt

@@ -67,6 +67,7 @@ SETUP_AND_ADD_TEST(monostate entt/core/monostate.cpp)
 # Test entity
 
 SETUP_AND_ADD_TEST(actor entt/entity/actor.cpp)
+SETUP_AND_ADD_TEST(attachee entt/entity/attachee.cpp)
 SETUP_AND_ADD_TEST(entity entt/entity/entity.cpp)
 SETUP_AND_ADD_TEST(helper entt/entity/helper.cpp)
 SETUP_AND_ADD_TEST(prototype entt/entity/prototype.cpp)

+ 69 - 0
test/entt/entity/attachee.cpp

@@ -0,0 +1,69 @@
+#include <unordered_set>
+#include <gtest/gtest.h>
+#include <entt/entity/attachee.hpp>
+
+TEST(AttacheeNoType, Functionalities) {
+    entt::Attachee<std::uint64_t> attachee;
+
+    attachee.construct(42u);
+
+    ASSERT_EQ(attachee.get(), 42u);
+
+    attachee.destroy();
+
+    ASSERT_NE(attachee.get(), 42u);
+
+    (void)entt::Attachee<std::uint64_t>{std::move(attachee)};
+    entt::Attachee<std::uint64_t> other;
+    other = std::move(attachee);
+}
+
+TEST(AttacheeWithType, Functionalities) {
+    entt::Attachee<std::uint64_t, int> attachee;
+    const auto &cattachee = attachee;
+
+    attachee.construct(42u, 3);
+
+    ASSERT_EQ(attachee.get(), 3);
+    ASSERT_EQ(cattachee.get(), 3);
+    ASSERT_EQ(attachee.Attachee<std::uint64_t>::get(), 42u);
+
+    attachee.move(0u);
+
+    ASSERT_EQ(attachee.get(), 3);
+    ASSERT_EQ(cattachee.get(), 3);
+    ASSERT_EQ(attachee.Attachee<std::uint64_t>::get(), 0u);
+
+    attachee.destroy();
+
+    ASSERT_NE(attachee.Attachee<std::uint64_t>::get(), 0u);
+    ASSERT_NE(attachee.Attachee<std::uint64_t>::get(), 42u);
+}
+
+TEST(AttacheeWithType, AggregatesMustWork) {
+    struct AggregateType { int value; };
+    // the goal of this test is to enforce the requirements for aggregate types
+    entt::Attachee<std::uint64_t, AggregateType>{}.construct(0, 42);
+}
+
+TEST(AttacheeWithType, TypesFromStandardTemplateLibraryMustWork) {
+    // see #37 - this test shouldn't crash, that's all
+    entt::Attachee<std::uint64_t, std::unordered_set<int>> attachee;
+    attachee.construct(0).insert(42);
+    attachee.destroy();
+}
+
+TEST(AttacheeWithType, MoveOnlyComponent) {
+    struct MoveOnlyComponent {
+        MoveOnlyComponent() = default;
+        ~MoveOnlyComponent() = default;
+        MoveOnlyComponent(const MoveOnlyComponent &) = delete;
+        MoveOnlyComponent(MoveOnlyComponent &&) = default;
+        MoveOnlyComponent & operator=(const MoveOnlyComponent &) = delete;
+        MoveOnlyComponent & operator=(MoveOnlyComponent &&) = default;
+    };
+
+    // it's purpose is to ensure that move only components are always accepted
+    entt::Attachee<std::uint64_t, MoveOnlyComponent> attachee;
+    (void)attachee;
+}

+ 14 - 0
test/entt/entity/registry.cpp

@@ -376,8 +376,10 @@ TEST(DefaultRegistry, CreateDestroyEntities) {
 TEST(DefaultRegistry, AttachSetRemoveTags) {
     entt::DefaultRegistry registry;
     const auto &cregistry = registry;
+    const typename decltype(registry)::entity_type null = entt::null;
 
     ASSERT_FALSE(registry.has<int>());
+    ASSERT_EQ(registry.attachee<int>(), null);
 
     const auto entity = registry.create();
     registry.assign<int>(entt::tag_t{}, entity, 42);
@@ -411,6 +413,7 @@ TEST(DefaultRegistry, AttachSetRemoveTags) {
     ASSERT_FALSE(registry.has<int>());
     ASSERT_FALSE(registry.has<int>(entt::tag_t{}, entity));
     ASSERT_FALSE(registry.has<int>(entt::tag_t{}, other));
+    ASSERT_EQ(registry.attachee<int>(), null);
 
     registry.assign<int>(entt::tag_t{}, entity, 42);
     registry.destroy(entity);
@@ -811,3 +814,14 @@ TEST(DefaultRegistry, DestroyByTagAndComponents) {
     registry.destroy<double>(entt::tag_t{});
     registry.destroy<float>(entt::tag_t{});
 }
+
+TEST(DefaultRegistry, SignalsOnAccommodate) {
+    entt::DefaultRegistry registry;
+    const auto entity = registry.create();
+
+    registry.prepare<int, char>();
+    registry.assign<int>(entity);
+    registry.accommodate<char>(entity);
+
+    ASSERT_FALSE((registry.view<int, char>(entt::persistent_t{}).empty()));
+}

+ 2 - 2
test/entt/entity/sparse_set.cpp

@@ -400,8 +400,8 @@ TEST(SparseSetWithType, Functionalities) {
     ASSERT_FALSE(set.has(0));
     ASSERT_FALSE(set.has(42));
 
-    (void)entt::SparseSet<std::uint64_t>{std::move(set)};
-    entt::SparseSet<std::uint64_t> other;
+    (void)entt::SparseSet<std::uint64_t, int>{std::move(set)};
+    entt::SparseSet<std::uint64_t, int> other;
     other = std::move(set);
 }