Browse Source

tests, tags and few other features

Michele Caini 8 years ago
parent
commit
b6c950ffc5

+ 63 - 1
README.md

@@ -461,7 +461,7 @@ their components are destroyed:
   registry.reset();
   ```
 
-Finally, references to components can be retrieved by just doing this:
+Finally, references to components can be retrieved simply by doing this:
 
 ```cpp
 // either a non-const reference ...
@@ -476,6 +476,68 @@ const auto &position = cregistry.get<Position>(entity);
 The `get` member function template gives direct access to the component of an
 entity stored in the underlying data structures of the registry.
 
+### Single instance components
+
+In those cases where all what is needed is a single instance component, tags are
+the right tool to achieve the purpose.<br/>
+Tags undergo the same requirements of components. They can be either plain old
+data structures or more complex and moveable data structures with a proper
+constructor.<br/>
+Actually, the same type can be used both as a tag and as a component and the
+registry will not complain about it. It is up to the users to properly manage
+their own types.
+
+Attaching tags to entities and removing them is trivial:
+
+```cpp
+auto player = registry.create();
+auto camera = registry.create();
+
+// attaches a default-initialized tag to an entity
+registry.attach<PlayingCharacter>(player);
+
+// attaches a tag to an entity and initializes it
+registry.attach<Camera>(camera, player);
+
+// removes tags from their owners
+registry.remove<PlayingCharacter>();
+registry.remove<Camera>();
+```
+
+If in doubt about whether or not a tag has already an owner, the `has` member
+function template may be useful:
+
+```cpp
+bool b = registry.has<PlayingCharacter>();
+```
+
+References to tags can be retrieved simply by doing this:
+
+```cpp
+// either a non-const reference ...
+entt::DefaultRegistry registry;
+auto &player = registry.get<PlayingCharacter>();
+
+// ... or a const one
+const auto &cregistry = registry;
+const auto &camera = cregistry.get<Camera>();
+```
+
+The `get` member function template gives direct access to the tag as stored in
+the underlying data structures of the registry.
+
+As shown above, in almost all the cases the entity identifier isn't required,
+since a single instance component can have only one associated entity and
+therefore it doesn't make much sense to mention it explicitly.<br/>
+To find out who the owner is, just do the following:
+
+```cpp
+auto player = registry.attachee<PlayingCharacter>();
+```
+
+Note that iterating tags isn't possible for obvious reasons. Tags give direct
+access to single entities and nothing more.
+
 ### Sorting: is it possible?
 
 It goes without saying that sorting entities and components is possible with

+ 205 - 20
src/entt/entity/registry.hpp

@@ -7,6 +7,7 @@
 #include <utility>
 #include <cstddef>
 #include <cassert>
+#include <algorithm>
 #include "../core/family.hpp"
 #include "sparse_set.hpp"
 #include "traits.hpp"
@@ -28,10 +29,26 @@ namespace entt {
  */
 template<typename Entity>
 class Registry {
+    using tag_family = Family<struct InternalRegistryTagFamily>;
     using component_family = Family<struct InternalRegistryComponentFamily>;
     using view_family = Family<struct InternalRegistryViewFamily>;
     using traits_type = entt_traits<Entity>;
 
+    struct Attachee {
+        Entity entity;
+    };
+
+    template<typename Tag>
+    struct Attaching: Attachee {
+        // requirements for aggregates are relaxed only since C++17
+        template<typename... Args>
+        Attaching(Entity entity, Tag tag)
+            : Attachee{entity}, tag{std::move(tag)}
+        {}
+
+        Tag tag;
+    };
+
     template<typename Component>
     struct Pool: SparseSet<Entity, Component> {
         using test_fn_type = bool(Registry::*)(Entity) const;
@@ -42,7 +59,7 @@ class Registry {
 
             for(auto &&listener: listeners) {
                 if((registry.*listener.second)(entity)) {
-                    listener.first.construct(entity);
+                    listener.first->construct(entity);
                 }
             }
 
@@ -53,20 +70,26 @@ class Registry {
             SparseSet<Entity, Component>::destroy(entity);
 
             for(auto &&listener: listeners) {
-                auto &handler = listener.first;
+                auto *handler = listener.first;
 
-                if(handler.has(entity)) {
-                    handler.destroy(entity);
+                if(handler->has(entity)) {
+                    handler->destroy(entity);
                 }
             }
         }
 
-        inline void append(SparseSet<Entity> &handler, test_fn_type fn) {
+        inline void append(SparseSet<Entity> *handler, test_fn_type fn) {
             listeners.emplace_back(handler, fn);
         }
 
+        inline void remove(SparseSet<Entity> *handler) {
+            listeners.erase(std::remove_if(listeners.begin(), listeners.end(), [handler](auto &listener) {
+                return listener.first == handler;
+            }), listeners.end());
+        }
+
     private:
-        std::vector<std::pair<SparseSet<Entity> &, test_fn_type>> listeners;
+        std::vector<std::pair<SparseSet<Entity> *, test_fn_type>> listeners;
     };
 
     template<typename Component>
@@ -121,7 +144,7 @@ class Registry {
             }
 
             accumulator_type accumulator = {
-                (ensure<Component>().append(*set, &Registry::has<Component...>), 0)...
+                (ensure<Component>().append(set.get(), &Registry::has<Component...>), 0)...
             };
 
             handlers[vtype] = std::move(set);
@@ -358,6 +381,122 @@ public:
         }
     }
 
+    /**
+     * @brief Attaches a tag to an entity.
+     *
+     * Usually, pools of components allocate enough memory to store a bunch of
+     * elements even if only one of them is used. On the other hand, there are
+     * cases where all what is needed is a single instance component to attach
+     * to an entity.<br/>
+     * Tags are the right tool to achieve the purpose.
+     *
+     * @warning
+     * Attempting to use an invalid entity or to attach to an entity a tag that
+     * already has an owner results in undefined behavior.<br/>
+     * An assertion will abort the execution at runtime in debug mode in case of
+     * invalid entity or if the tag has been already attached to another entity.
+     *
+     * @tparam Tag Type of tag to create.
+     * @tparam Args Types of arguments to use to construct the tag.
+     * @param entity A valid entity identifier
+     * @param args Parameters to use to initialize the tag.
+     * @return A reference to the newly created tag.
+     */
+    template<typename Tag, typename... Args>
+    Tag & attach(entity_type entity, Args&&... args) {
+        assert(valid(entity));
+        assert(!has<Tag>());
+        const auto ttype = tag_family::type<Tag>();
+
+        if(!(ttype < tags.size())) {
+            tags.resize(ttype + 1);
+        }
+
+        tags[ttype].reset(new Attaching<Tag>{entity, { std::forward<Args>(args)... }});
+        tags[ttype]->entity = entity;
+
+        return static_cast<Attaching<Tag> *>(tags[ttype].get())->tag;
+    }
+
+    /**
+     * @brief Removes a tag from its owner, if any.
+     * @tparam Tag Type of tag to remove.
+     */
+    template<typename Tag>
+    void remove() {
+        if(has<Tag>()) {
+            tags[tag_family::type<Tag>()].reset();
+        }
+    }
+
+    /**
+     * @brief Checks if a tag has an owner.
+     * @tparam Tag Type of tag for which to perform the check.
+     * @return True if the tag already has an owner, false otherwise.
+     */
+    template<typename Tag>
+    bool has() const noexcept {
+        const auto ttype = tag_family::type<Tag>();
+        return (ttype < tags.size() &&
+                // it's a valid tag
+                tags[ttype] &&
+                // the associated entity hasn't been destroyed in the meantime
+                tags[ttype]->entity == (entities[tags[ttype]->entity & traits_type::entity_mask]));
+    }
+
+    /**
+     * @brief Returns a reference to a tag.
+     *
+     * @warning
+     * Attempting to get a tag that hasn't an owner results in undefined
+     * behavior.<br/>
+     * An assertion will abort the execution at runtime in debug mode if the
+     * tag hasn't been previously attached to an entity.
+     *
+     * @tparam Tag Type of tag to get.
+     * @return A reference to the tag.
+     */
+    template<typename Tag>
+    const Tag & get() const noexcept {
+        assert(has<Tag>());
+        return static_cast<Attaching<Tag> *>(tags[tag_family::type<Tag>()].get())->tag;
+    }
+
+    /**
+     * @brief Returns a reference to a tag.
+     *
+     * @warning
+     * Attempting to get a tag that hasn't an owner results in undefined
+     * behavior.<br/>
+     * An assertion will abort the execution at runtime in debug mode if the
+     * tag hasn't been previously attached to an entity.
+     *
+     * @tparam Tag Type of tag to get.
+     * @return A reference to the tag.
+     */
+    template<typename Tag>
+    Tag & get() noexcept {
+        return const_cast<Tag &>(const_cast<const Registry *>(this)->get<Tag>());
+    }
+
+    /**
+     * @brief Gets the owner of a 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.
+     */
+    template<typename Tag>
+    entity_type attachee() const noexcept {
+        assert(has<Tag>());
+        return tags[tag_family::type<Tag>()]->entity;
+    }
+
     /**
      * @brief Assigns the given component to an entity.
      *
@@ -372,7 +511,7 @@ public:
      * invalid entity or if the entity already owns an instance of the given
      * component.
      *
-     * @tparam Component Type of the component to create.
+     * @tparam Component Type of component to create.
      * @tparam Args Types of arguments to use to construct the component.
      * @param entity A valid entity identifier.
      * @param args Parameters to use to initialize the component.
@@ -394,7 +533,7 @@ public:
      * invalid entity or if the entity doesn't own an instance of the given
      * component.
      *
-     * @tparam Component Type of the component to remove.
+     * @tparam Component Type of component to remove.
      * @param entity A valid entity identifier.
      */
     template<typename Component>
@@ -436,7 +575,7 @@ public:
      * invalid entity or if the entity doesn't own an instance of the given
      * component.
      *
-     * @tparam Component Type of the component to get.
+     * @tparam Component Type of component to get.
      * @param entity A valid entity identifier.
      * @return A reference to the instance of the component owned by the entity.
      */
@@ -456,7 +595,7 @@ public:
      * invalid entity or if the entity doesn't own an instance of the given
      * component.
      *
-     * @tparam Component Type of the component to get.
+     * @tparam Component Type of component to get.
      * @param entity A valid entity identifier.
      * @return A reference to the instance of the component owned by the entity.
      */
@@ -479,7 +618,7 @@ public:
      * invalid entity or if the entity doesn't own an instance of the given
      * component.
      *
-     * @tparam Component Type of the component to replace.
+     * @tparam Component Type of component to replace.
      * @tparam Args Types of arguments to use to construct the component.
      * @param entity A valid entity identifier.
      * @param args Parameters to use to initialize the component.
@@ -512,7 +651,7 @@ public:
      * An assertion will abort the execution at runtime in debug mode in case of
      * invalid entity.
      *
-     * @tparam Component Type of the component to assign or replace.
+     * @tparam Component Type of component to assign or replace.
      * @tparam Args Types of arguments to use to construct the component.
      * @param entity A valid entity identifier.
      * @param args Parameters to use to initialize the component.
@@ -549,8 +688,8 @@ public:
      *
      * Where `e1` and `e2` are valid entity identifiers.
      *
-     * @tparam Component Type of the components to sort.
-     * @tparam Compare Type of the comparison function object.
+     * @tparam Component Type of components to sort.
+     * @tparam Compare Type of comparison function object.
      * @param compare A valid comparison function object.
      */
     template<typename Component, typename Compare>
@@ -589,8 +728,8 @@ public:
      *
      * Any subsequent change to `B` won't affect the order in `A`.
      *
-     * @tparam To Type of the components to sort.
-     * @tparam From Type of the components to use to sort.
+     * @tparam To Type of components to sort.
+     * @tparam From Type of components to use to sort.
      */
     template<typename To, typename From>
     void sort() {
@@ -608,7 +747,7 @@ public:
      * An assertion will abort the execution at runtime in debug mode in case of
      * invalid entity.
      *
-     * @tparam Component Type of the component to reset.
+     * @tparam Component Type of component to reset.
      * @param entity A valid entity identifier.
      */
     template<typename Component>
@@ -630,7 +769,7 @@ public:
      * For each entity that has an instance of the given component, the
      * component itself is removed and thus destroyed.
      *
-     * @tparam Component type of the component whose pool must be reset.
+     * @tparam Component Type of component whose pool must be reset.
      */
     template<typename Component>
     void reset() {
@@ -673,6 +812,10 @@ public:
                 pool->reset();
             }
         }
+
+        for(auto &&tag: tags) {
+            tag.reset();
+        }
     }
 
     /**
@@ -733,6 +876,46 @@ public:
         handler<Component...>();
     }
 
+    /**
+     * @brief Discards all the data structures used for a given persitent view.
+     *
+     * Persistent views occupy memory, no matter if they are in use or not.<br/>
+     * This function can be used to discard all the internal data structures
+     * dedicated to a specific persisten view, with the goal of reducing the
+     * memory pressure.
+     *
+     * @warning
+     * Attempting to use a persistent view created before calling this function
+     * results in undefined behavior. No assertion available in this case,
+     * neither in debug mode nor in release mode.
+     *
+     * @tparam Component Types of components of the persistent view.
+     */
+    template<typename... Component>
+    void discard() {
+        if(contains<Component...>()) {
+            using accumulator_type = int[];
+            const auto vtype = view_family::type<Component...>();
+            auto *set = handlers[vtype].get();
+            // if a set exists, pools have already been created for it
+            accumulator_type accumulator = { (pool<Component>().remove(set), 0)... };
+            handlers[vtype].reset();
+            (void)accumulator;
+        }
+    }
+
+    /**
+     * @brief Checks if a persistent view has already been prepared.
+     * @tparam Component Types of components of the persistent view.
+     * @return True if the view has already been prepared, false otherwise.
+     */
+    template<typename... Component>
+    bool contains() const noexcept {
+        static_assert(sizeof...(Component) > 1, "!");
+        const auto vtype = view_family::type<Component...>();
+        return vtype < handlers.size() && handlers[vtype];
+    }
+
     /**
      * @brief Returns a persistent view for the given components.
      *
@@ -772,12 +955,14 @@ public:
      */
     template<typename... Component>
     PersistentView<Entity, Component...> persistent() {
-        return PersistentView<Entity, Component...>{handler<Component...>(), ensure<Component>()...};
+        // after the calls to handler, pools have already been created
+        return PersistentView<Entity, Component...>{handler<Component...>(), pool<Component>()...};
     }
 
 private:
     std::vector<std::unique_ptr<SparseSet<Entity>>> handlers;
     std::vector<std::unique_ptr<SparseSet<Entity>>> pools;
+    std::vector<std::unique_ptr<Attachee>> tags;
     std::vector<entity_type> available;
     std::vector<entity_type> entities;
 };

+ 11 - 10
src/entt/entity/sparse_set.hpp

@@ -264,7 +264,7 @@ public:
         // has size 1), switching the two lines below doesn't work as expected
         reverse[back] = pos | in_use;
         reverse[entt] = pos;
-        // swap-and-pop the last element with the selected ont
+        // swapping isn't required here, we are getting rid of the last element
         direct[pos] = direct.back();
         direct.pop_back();
     }
@@ -412,7 +412,7 @@ class SparseSet<Entity, Type>: public SparseSet<Entity> {
 
 public:
     /*! @brief Type of the objects associated to the entities. */
-    using type = Type;
+    using object_type = Type;
     /*! @brief Underlying entity identifier. */
     using entity_type = typename underlying_type::entity_type;
     /*! @brief Entity dependent position type. */
@@ -450,7 +450,7 @@ public:
      *
      * @return A pointer to the array of objects.
      */
-    const type * raw() const noexcept {
+    const object_type * raw() const noexcept {
         return instances.data();
     }
 
@@ -469,7 +469,7 @@ public:
      *
      * @return A pointer to the array of objects.
      */
-    type * raw() noexcept {
+    object_type * raw() noexcept {
         return instances.data();
     }
 
@@ -485,7 +485,7 @@ public:
      * @param entity A valid entity identifier.
      * @return The object associated to the entity.
      */
-    const type & get(entity_type entity) const noexcept {
+    const object_type & get(entity_type entity) const noexcept {
         return instances[underlying_type::get(entity)];
     }
 
@@ -501,8 +501,8 @@ public:
      * @param entity A valid entity identifier.
      * @return The object associated to the entity.
      */
-    type & get(entity_type entity) noexcept {
-        return const_cast<type &>(const_cast<const SparseSet *>(this)->get(entity));
+    object_type & get(entity_type entity) noexcept {
+        return const_cast<object_type &>(const_cast<const SparseSet *>(this)->get(entity));
     }
 
     /**
@@ -520,8 +520,9 @@ public:
      * @return The object associated to the entity.
      */
     template<typename... Args>
-    type & construct(entity_type entity, Args&&... args) {
+    object_type & construct(entity_type entity, Args&&... args) {
         underlying_type::construct(entity);
+        // emplace_back doesn't work well with PODs because of its placement new
         instances.push_back({ std::forward<Args>(args)... });
         return instances.back();
     }
@@ -538,7 +539,7 @@ public:
      * @param entity A valid entity identifier.
      */
     void destroy(entity_type entity) override {
-        // swaps isn't required here, we are getting rid of the last element
+        // swapping isn't required here, we are getting rid of the last element
         instances[underlying_type::get(entity)] = std::move(instances.back());
         instances.pop_back();
         underlying_type::destroy(entity);
@@ -574,7 +575,7 @@ public:
     }
 
 private:
-    std::vector<type> instances;
+    std::vector<object_type> instances;
 };
 
 

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

@@ -571,7 +571,7 @@ public:
     /*! @brief Unsigned integer type. */
     using size_type = typename pool_type::size_type;
     /*! Type of the component iterated by the view. */
-    using raw_type = typename pool_type::type;
+    using raw_type = typename pool_type::object_type;
 
     /**
      * @brief Constructs a view out of a pool of components.

+ 1 - 1
src/entt/process/scheduler.hpp

@@ -292,7 +292,7 @@ public:
      * Unless an immediate operation is requested, the abort is scheduled for
      * the next tick. Processes won't be executed anymore in any case.<br/>
      * Once a process is fully aborted and thus finished, it's discarded along
-     * with its child if any.
+     * with its child, if any.
      *
      * @param immediately Requests an immediate operation.
      */

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

@@ -134,6 +134,110 @@ TEST(DefaultRegistry, CreateDestroyEntities) {
     ASSERT_EQ(registry.current(pre), registry.current(post));
 }
 
+TEST(DefaultRegistry, AttachRemoveTags) {
+    entt::DefaultRegistry registry;
+    const auto &cregistry = registry;
+
+    ASSERT_FALSE(registry.has<int>());
+
+    auto entity = registry.create();
+    registry.attach<int>(entity, 42);
+
+    ASSERT_TRUE(registry.has<int>());
+    ASSERT_EQ(registry.get<int>(), 42);
+    ASSERT_EQ(cregistry.get<int>(), 42);
+    ASSERT_EQ(registry.attachee<int>(), entity);
+
+    registry.remove<int>();
+
+    ASSERT_FALSE(registry.has<int>());
+
+    registry.attach<int>(entity, 42);
+    registry.destroy(entity);
+
+    ASSERT_FALSE(registry.has<int>());
+}
+
+TEST(DefaultRegistry, StandardViews) {
+    entt::DefaultRegistry registry;
+    auto mview = registry.view<int, char>();
+    auto iview = registry.view<int>();
+    auto cview = registry.view<char>();
+
+    registry.create(0, 'c');
+    registry.create(0);
+    registry.create(0, 'c');
+
+    ASSERT_EQ(iview.size(), decltype(iview)::size_type{3});
+    ASSERT_EQ(cview.size(), decltype(cview)::size_type{2});
+
+    decltype(mview)::size_type cnt{0};
+    mview.each([&cnt](auto...) { ++cnt; });
+
+    ASSERT_EQ(cnt, decltype(mview)::size_type{2});
+}
+
+TEST(DefaultRegistry, PersistentViews) {
+    entt::DefaultRegistry registry;
+    auto view = registry.persistent<int, char>();
+
+    ASSERT_TRUE((registry.contains<int, char>()));
+    ASSERT_FALSE((registry.contains<int, double>()));
+
+    registry.prepare<int, double>();
+
+    ASSERT_TRUE((registry.contains<int, double>()));
+
+    registry.discard<int, double>();
+
+    ASSERT_FALSE((registry.contains<int, double>()));
+
+    registry.create(0, 'c');
+    registry.create(0);
+    registry.create(0, 'c');
+
+    decltype(view)::size_type cnt{0};
+    view.each([&cnt](auto...) { ++cnt; });
+
+    ASSERT_EQ(cnt, decltype(view)::size_type{2});
+}
+
+TEST(DefaultRegistry, CleanStandardViewsAfterReset) {
+    entt::DefaultRegistry registry;
+    auto view = registry.view<int>();
+    registry.create(0);
+
+    ASSERT_EQ(view.size(), entt::DefaultRegistry::size_type{1});
+
+    registry.reset();
+
+    ASSERT_EQ(view.size(), entt::DefaultRegistry::size_type{0});
+}
+
+TEST(DefaultRegistry, CleanPersistentViewsAfterReset) {
+    entt::DefaultRegistry registry;
+    auto view = registry.persistent<int, char>();
+    registry.create(0, 'c');
+
+    ASSERT_EQ(view.size(), entt::DefaultRegistry::size_type{1});
+
+    registry.reset();
+
+    ASSERT_EQ(view.size(), entt::DefaultRegistry::size_type{0});
+}
+
+TEST(DefaultRegistry, CleanTagsAfterReset) {
+    entt::DefaultRegistry registry;
+    auto entity = registry.create();
+    registry.attach<int>(entity);
+
+    ASSERT_TRUE(registry.has<int>());
+
+    registry.reset();
+
+    ASSERT_FALSE(registry.has<int>());
+}
+
 TEST(DefaultRegistry, SortSingle) {
     entt::DefaultRegistry registry;