Przeglądaj źródła

orphans/orphan + minor changes

Michele Caini 8 lat temu
rodzic
commit
d54594f11d
3 zmienionych plików z 165 dodań i 68 usunięć
  1. 38 16
      README.md
  2. 59 6
      src/entt/entity/registry.hpp
  3. 68 46
      test/entt/entity/registry.cpp

+ 38 - 16
README.md

@@ -353,8 +353,8 @@ describe below. For more details, please refer to the
 
 
 ## The Registry, the Entity and the Component
 ## The Registry, the Entity and the Component
 
 
-A registry is used to store and manage entities as well as to create views to
-iterate the underlying data structures.<br/>
+A registry can store and manage entities, as well as create views to iterate the
+underlying data structures.<br/>
 `Registry` is a class template that lets the users decide what's the preferred
 `Registry` is a class template that lets the users decide what's the preferred
 type to represent an entity. Because `std::uint32_t` is large enough for almost
 type to represent an entity. Because `std::uint32_t` is large enough for almost
 all the cases, there exists also an alias named `DefaultRegistry` for
 all the cases, there exists also an alias named `DefaultRegistry` for
@@ -367,7 +367,7 @@ information about the entity itself and its version.
 A registry can be used both to construct and to destroy entities:
 A registry can be used both to construct and to destroy entities:
 
 
 ```cpp
 ```cpp
-// constructs a naked entity with no components ad returns its identifier
+// constructs a naked entity with no components and returns its identifier
 auto entity = registry.create();
 auto entity = registry.create();
 
 
 // constructs an entity and assigns it default-initialized components
 // constructs an entity and assigns it default-initialized components
@@ -400,8 +400,8 @@ calls to member functions of the registry. As for the entities, the registry
 offers also a set of functionalities users can use to work with the components.
 offers also a set of functionalities users can use to work with the components.
 
 
 The `assign` member function template creates, initializes and assigns to an
 The `assign` member function template creates, initializes and assigns to an
-entity the given component. It accepts a variable number of arguments that are
-used to construct the component itself if present:
+entity the given component. It accepts a variable number of arguments to
+construct the component itself if present:
 
 
 ```cpp
 ```cpp
 registry.assign<Position>(entity, 0., 0.);
 registry.assign<Position>(entity, 0., 0.);
@@ -468,7 +468,7 @@ registry.remove<Position>(entity);
 ```
 ```
 
 
 Otherwise consider to use the `reset` member function. It behaves similarly to
 Otherwise consider to use the `reset` member function. It behaves similarly to
-`remove` but with a strictly defined behaviour (and a performance penalty is the
+`remove` but with a strictly defined behavior (and a performance penalty is the
 price to pay for this). In particular it removes the component if and only if it
 price to pay for this). In particular it removes the component if and only if it
 exists, otherwise it returns safely to the caller:
 exists, otherwise it returns safely to the caller:
 
 
@@ -495,7 +495,6 @@ in use and their components are destroyed:
 Finally, references to components can be retrieved simply by doing this:
 Finally, references to components can be retrieved simply by doing this:
 
 
 ```cpp
 ```cpp
-entt::DefaultRegistry registry;
 const auto &cregistry = registry;
 const auto &cregistry = registry;
 
 
 // const and non-const reference
 // const and non-const reference
@@ -548,12 +547,12 @@ bool b = registry.has<PlayingCharacter>();
 References to tags can be retrieved simply by doing this:
 References to tags can be retrieved simply by doing this:
 
 
 ```cpp
 ```cpp
+const auto &cregistry = registry;
+
 // either a non-const reference ...
 // either a non-const reference ...
-entt::DefaultRegistry registry;
 PlayingCharacter &player = registry.get<PlayingCharacter>();
 PlayingCharacter &player = registry.get<PlayingCharacter>();
 
 
 // ... or a const one
 // ... or a const one
-const auto &cregistry = registry;
 const Camera &camera = cregistry.get<Camera>();
 const Camera &camera = cregistry.get<Camera>();
 ```
 ```
 
 
@@ -605,15 +604,16 @@ simple.
 
 
 `EnTT` comes with an example (actually a test) that shows how to integrate
 `EnTT` comes with an example (actually a test) that shows how to integrate
 compile-time and runtime components in a stack based JavaScript environment. It
 compile-time and runtime components in a stack based JavaScript environment. It
-uses [`duktape`](https://github.com/svaarala/duktape) under the hood, mainly
+uses [`Duktape`](https://github.com/svaarala/duktape) under the hood, mainly
 because I wanted to learn how it works at the time I was writing the code.
 because I wanted to learn how it works at the time I was writing the code.
 
 
-It's not production-ready and overall performance can be highly improved.
-However, I sacrificed optimizations in favor of a more readable piece of
-code. I hope I succeeded.<br/>
+The code is not production-ready and overall performance can be highly improved.
+However, I sacrificed optimizations in favor of a more readable piece of code. I
+hope I succeeded.<br/>
 Note also that this isn't neither the only nor (probably) the best way to do it.
 Note also that this isn't neither the only nor (probably) the best way to do it.
 In fact, the right way depends on the scripting language and the problem one is
 In fact, the right way depends on the scripting language and the problem one is
-facing in general.
+facing in general.<br/>
+That being said, feel free to use it at your own risk.
 
 
 The basic idea is that of creating a compile-time component aimed to map all the
 The basic idea is that of creating a compile-time component aimed to map all the
 runtime components assigned to an entity.<br/>
 runtime components assigned to an entity.<br/>
@@ -895,11 +895,33 @@ registry.each([](auto entity) {
 });
 });
 ```
 ```
 
 
+It returns to the caller all the entities that are still in use by means of the
+given function.<br/>
 As a rule of thumb, consider using a view if the goal is to iterate entities
 As a rule of thumb, consider using a view if the goal is to iterate entities
-that have a determinate set of components. A view is usually faster than
+that have a determinate set of components. A view is usually much faster than
 combining this function with a bunch of custom tests.<br/>
 combining this function with a bunch of custom tests.<br/>
 In all the other cases, this is the way to go.
 In all the other cases, this is the way to go.
 
 
+There exists also another member function to use to retrieve orphans. An orphan
+is an entity that is still in use and has neither assigned components nor
+tags.<br/>
+The signature of the function is the same of `each`:
+
+```cpp
+registry.orphans([](auto entity) {
+    // ...
+});
+```
+
+To test the _orphanity_ of a single entity, use the member function `orphan`
+instead. It accepts a valid entity identifer as an argument and returns true in
+case the entity is an orphan, false otherwise.
+
+In general, all these functions can result in poor performance.<br/>
+`each` is fairly slow because of some checks it performs on each and every
+entity. For similar reasons, `orphans` can be even slower. Both functions should
+not be used frequently to avoid the risk of a performance hit.
+
 ## Side notes
 ## Side notes
 
 
 * Entity identifiers are numbers and nothing more. They are not classes and they
 * Entity identifiers are numbers and nothing more. They are not classes and they
@@ -925,7 +947,7 @@ In all the other cases, this is the way to go.
     other entities, destroying them or removing their components isn't
     other entities, destroying them or removing their components isn't
     allowed and it can result in undefined behavior.
     allowed and it can result in undefined behavior.
 
 
-  Iterators are invalidated and the behaviour is undefined if an entity is
+  Iterators are invalidated and the behavior is undefined if an entity is
   modified or destroyed and it's not the one currently returned by the
   modified or destroyed and it's not the one currently returned by the
   view.<br/>
   view.<br/>
   To work around it, possible approaches are:
   To work around it, possible approaches are:

+ 59 - 6
src/entt/entity/registry.hpp

@@ -138,7 +138,6 @@ class Registry {
 
 
         if(!handlers[vtype]) {
         if(!handlers[vtype]) {
             using accumulator_type = int[];
             using accumulator_type = int[];
-
             auto set = std::make_unique<SparseSet<Entity>>();
             auto set = std::make_unique<SparseSet<Entity>>();
 
 
             for(auto entity: view<Component...>()) {
             for(auto entity: view<Component...>()) {
@@ -294,7 +293,7 @@ public:
     bool valid(entity_type entity) const noexcept {
     bool valid(entity_type entity) const noexcept {
         using promotion_type = std::conditional_t<sizeof(size_type) >= sizeof(entity_type), size_type, entity_type>;
         using promotion_type = std::conditional_t<sizeof(size_type) >= sizeof(entity_type), size_type, entity_type>;
         // explicit promotion to avoid warnings with std::uint16_t
         // explicit promotion to avoid warnings with std::uint16_t
-        const entity_type entt = promotion_type{entity} & traits_type::entity_mask;
+        const auto entt = promotion_type{entity} & traits_type::entity_mask;
         return (entt < entities.size() && entities[entt] == entity);
         return (entt < entities.size() && entities[entt] == entity);
     }
     }
 
 
@@ -487,7 +486,7 @@ public:
             tags.resize(ttype + 1);
             tags.resize(ttype + 1);
         }
         }
 
 
-        tags[ttype].reset(new Attaching<Tag>{entity, { std::forward<Args>(args)... }});
+        tags[ttype].reset(new Attaching<Tag>{entity, Tag{ std::forward<Args>(args)... }});
 
 
         return static_cast<Attaching<Tag> *>(tags[ttype].get())->tag;
         return static_cast<Attaching<Tag> *>(tags[ttype].get())->tag;
     }
     }
@@ -906,18 +905,21 @@ public:
     }
     }
 
 
     /**
     /**
-     * @brief Iterate entities and applies them the given function object.
+     * @brief Iterates all the entities that are still in use.
      *
      *
-     * The function object is invoked for each entity still in use.<br/>
+     * The function object is invoked for each entity that is still in use.<br/>
      * The signature of the function should be equivalent to the following:
      * The signature of the function should be equivalent to the following:
      *
      *
      * @code{.cpp}
      * @code{.cpp}
      * void(entity_type);
      * void(entity_type);
      * @endcode
      * @endcode
      *
      *
+     * This function is fairly slow and should not be used frequently.<br/>
      * Consider using a view if the goal is to iterate entities that have a
      * Consider using a view if the goal is to iterate entities that have a
      * determinate set of components. A view is usually faster than combining
      * determinate set of components. A view is usually faster than combining
-     * this function with a bunch of custom tests.
+     * this function with a bunch of custom tests.<br/>
+     * On the other side, this function can be used to iterate all the entities
+     * that are in use, regardless of their components.
      *
      *
      * @tparam Func Type of the function object to invoke.
      * @tparam Func Type of the function object to invoke.
      * @param func A valid function object.
      * @param func A valid function object.
@@ -940,6 +942,57 @@ public:
         }
         }
     }
     }
 
 
+    /**
+     * @brief Checks if an entity is an orphan.
+     *
+     * An orphan is an entity that has neither assigned components nor
+     * tags.
+     *
+     * @param entity A valid entity identifier.
+     * @return True if the entity is an orphan, false otherwise.
+     */
+    bool orphan(entity_type entity) const {
+        assert(valid(entity));
+        bool orphan = true;
+
+        for(std::size_t i = 0; i < pools.size() && orphan; ++i) {
+            const auto &pool = pools[i];
+            orphan = !(pool && pool->has(entity));
+        }
+
+        for(std::size_t i = 0; i < tags.size() && orphan; ++i) {
+            const auto &tag = tags[i];
+            orphan = !(tag && (tag->entity == entity));
+        }
+
+        return orphan;
+    }
+
+    /**
+     * @brief Iterates orphans and applies them the given function object.
+     *
+     * The function object is invoked for each entity that is still in use and
+     * has neither assigned components nor tags.<br/>
+     * The signature of the function should be equivalent to the following:
+     *
+     * @code{.cpp}
+     * void(entity_type);
+     * @endcode
+     *
+     * This function can be very slow and should not be used frequently.
+     *
+     * @tparam Func Type of the function object to invoke.
+     * @param func A valid function object.
+     */
+    template<typename Func>
+    void orphans(Func func) const {
+        each([func = std::move(func), this](auto entity) {
+            if(orphan(entity)) {
+                func(entity);
+            }
+        });
+    }
+
     /**
     /**
      * @brief Returns a standard view for the given components.
      * @brief Returns a standard view for the given components.
      *
      *

+ 68 - 46
test/entt/entity/registry.cpp

@@ -18,11 +18,11 @@ TEST(DefaultRegistry, Functionalities) {
     ASSERT_TRUE(registry.empty<int>());
     ASSERT_TRUE(registry.empty<int>());
     ASSERT_TRUE(registry.empty<char>());
     ASSERT_TRUE(registry.empty<char>());
 
 
-    auto e1 = registry.create();
-    auto e2 = registry.create<int, char>();
+    auto e0 = registry.create();
+    auto e1 = registry.create<int, char>();
 
 
+    ASSERT_TRUE(registry.has<>(e0));
     ASSERT_TRUE(registry.has<>(e1));
     ASSERT_TRUE(registry.has<>(e1));
-    ASSERT_TRUE(registry.has<>(e2));
 
 
     ASSERT_EQ(registry.capacity(), entt::DefaultRegistry::size_type{2});
     ASSERT_EQ(registry.capacity(), entt::DefaultRegistry::size_type{2});
     ASSERT_EQ(registry.size<int>(), entt::DefaultRegistry::size_type{1});
     ASSERT_EQ(registry.size<int>(), entt::DefaultRegistry::size_type{1});
@@ -30,67 +30,67 @@ TEST(DefaultRegistry, Functionalities) {
     ASSERT_FALSE(registry.empty<int>());
     ASSERT_FALSE(registry.empty<int>());
     ASSERT_FALSE(registry.empty<char>());
     ASSERT_FALSE(registry.empty<char>());
 
 
-    ASSERT_NE(e1, e2);
-
-    ASSERT_FALSE(registry.has<int>(e1));
-    ASSERT_TRUE(registry.has<int>(e2));
-    ASSERT_FALSE(registry.has<char>(e1));
-    ASSERT_TRUE(registry.has<char>(e2));
-    ASSERT_FALSE((registry.has<int, char>(e1)));
-    ASSERT_TRUE((registry.has<int, char>(e2)));
-
-    ASSERT_EQ(registry.assign<int>(e1, 42), 42);
-    ASSERT_EQ(registry.assign<char>(e1, 'c'), 'c');
-    ASSERT_NO_THROW(registry.remove<int>(e2));
-    ASSERT_NO_THROW(registry.remove<char>(e2));
+    ASSERT_NE(e0, e1);
 
 
+    ASSERT_FALSE(registry.has<int>(e0));
     ASSERT_TRUE(registry.has<int>(e1));
     ASSERT_TRUE(registry.has<int>(e1));
-    ASSERT_FALSE(registry.has<int>(e2));
+    ASSERT_FALSE(registry.has<char>(e0));
     ASSERT_TRUE(registry.has<char>(e1));
     ASSERT_TRUE(registry.has<char>(e1));
-    ASSERT_FALSE(registry.has<char>(e2));
+    ASSERT_FALSE((registry.has<int, char>(e0)));
     ASSERT_TRUE((registry.has<int, char>(e1)));
     ASSERT_TRUE((registry.has<int, char>(e1)));
-    ASSERT_FALSE((registry.has<int, char>(e2)));
 
 
-    auto e3 = registry.create();
+    ASSERT_EQ(registry.assign<int>(e0, 42), 42);
+    ASSERT_EQ(registry.assign<char>(e0, 'c'), 'c');
+    ASSERT_NO_THROW(registry.remove<int>(e1));
+    ASSERT_NO_THROW(registry.remove<char>(e1));
 
 
-    registry.accomodate<int>(e3, registry.get<int>(e1));
-    registry.accomodate<char>(e3, registry.get<char>(e1));
+    ASSERT_TRUE(registry.has<int>(e0));
+    ASSERT_FALSE(registry.has<int>(e1));
+    ASSERT_TRUE(registry.has<char>(e0));
+    ASSERT_FALSE(registry.has<char>(e1));
+    ASSERT_TRUE((registry.has<int, char>(e0)));
+    ASSERT_FALSE((registry.has<int, char>(e1)));
 
 
-    ASSERT_TRUE(registry.has<int>(e3));
-    ASSERT_TRUE(registry.has<char>(e3));
-    ASSERT_EQ(registry.get<int>(e1), 42);
-    ASSERT_EQ(registry.get<char>(e1), 'c');
+    auto e2 = registry.create();
 
 
-    ASSERT_EQ(std::get<0>(registry.get<int, char>(e1)), 42);
-    ASSERT_EQ(std::get<1>(static_cast<const entt::DefaultRegistry &>(registry).get<int, char>(e1)), 'c');
+    registry.accomodate<int>(e2, registry.get<int>(e0));
+    registry.accomodate<char>(e2, registry.get<char>(e0));
+
+    ASSERT_TRUE(registry.has<int>(e2));
+    ASSERT_TRUE(registry.has<char>(e2));
+    ASSERT_EQ(registry.get<int>(e0), 42);
+    ASSERT_EQ(registry.get<char>(e0), 'c');
 
 
-    ASSERT_EQ(registry.get<int>(e1), registry.get<int>(e3));
-    ASSERT_EQ(registry.get<char>(e1), registry.get<char>(e3));
-    ASSERT_NE(&registry.get<int>(e1), &registry.get<int>(e3));
-    ASSERT_NE(&registry.get<char>(e1), &registry.get<char>(e3));
+    ASSERT_EQ(std::get<0>(registry.get<int, char>(e0)), 42);
+    ASSERT_EQ(std::get<1>(static_cast<const entt::DefaultRegistry &>(registry).get<int, char>(e0)), 'c');
 
 
-    ASSERT_NO_THROW(registry.replace<int>(e1, 0));
-    ASSERT_EQ(registry.get<int>(e1), 0);
+    ASSERT_EQ(registry.get<int>(e0), registry.get<int>(e2));
+    ASSERT_EQ(registry.get<char>(e0), registry.get<char>(e2));
+    ASSERT_NE(&registry.get<int>(e0), &registry.get<int>(e2));
+    ASSERT_NE(&registry.get<char>(e0), &registry.get<char>(e2));
 
 
+    ASSERT_NO_THROW(registry.replace<int>(e0, 0));
+    ASSERT_EQ(registry.get<int>(e0), 0);
+
+    ASSERT_NO_THROW(registry.accomodate<int>(e0, 1));
     ASSERT_NO_THROW(registry.accomodate<int>(e1, 1));
     ASSERT_NO_THROW(registry.accomodate<int>(e1, 1));
-    ASSERT_NO_THROW(registry.accomodate<int>(e2, 1));
+    ASSERT_EQ(static_cast<const entt::DefaultRegistry &>(registry).get<int>(e0), 1);
     ASSERT_EQ(static_cast<const entt::DefaultRegistry &>(registry).get<int>(e1), 1);
     ASSERT_EQ(static_cast<const entt::DefaultRegistry &>(registry).get<int>(e1), 1);
-    ASSERT_EQ(static_cast<const entt::DefaultRegistry &>(registry).get<int>(e2), 1);
 
 
     ASSERT_EQ(registry.size(), entt::DefaultRegistry::size_type{3});
     ASSERT_EQ(registry.size(), entt::DefaultRegistry::size_type{3});
     ASSERT_FALSE(registry.empty());
     ASSERT_FALSE(registry.empty());
 
 
-    ASSERT_EQ(registry.version(e3), entt::DefaultRegistry::version_type{0});
-    ASSERT_EQ(registry.current(e3), entt::DefaultRegistry::version_type{0});
+    ASSERT_EQ(registry.version(e2), entt::DefaultRegistry::version_type{0});
+    ASSERT_EQ(registry.current(e2), entt::DefaultRegistry::version_type{0});
     ASSERT_EQ(registry.capacity(), entt::DefaultRegistry::size_type{3});
     ASSERT_EQ(registry.capacity(), entt::DefaultRegistry::size_type{3});
-    ASSERT_NO_THROW(registry.destroy(e3));
+    ASSERT_NO_THROW(registry.destroy(e2));
     ASSERT_EQ(registry.capacity(), entt::DefaultRegistry::size_type{3});
     ASSERT_EQ(registry.capacity(), entt::DefaultRegistry::size_type{3});
-    ASSERT_EQ(registry.version(e3), entt::DefaultRegistry::version_type{0});
-    ASSERT_EQ(registry.current(e3), entt::DefaultRegistry::version_type{1});
+    ASSERT_EQ(registry.version(e2), entt::DefaultRegistry::version_type{0});
+    ASSERT_EQ(registry.current(e2), entt::DefaultRegistry::version_type{1});
 
 
+    ASSERT_TRUE(registry.valid(e0));
     ASSERT_TRUE(registry.valid(e1));
     ASSERT_TRUE(registry.valid(e1));
-    ASSERT_TRUE(registry.valid(e2));
-    ASSERT_FALSE(registry.valid(e3));
+    ASSERT_FALSE(registry.valid(e2));
 
 
     ASSERT_EQ(registry.size(), entt::DefaultRegistry::size_type{2});
     ASSERT_EQ(registry.size(), entt::DefaultRegistry::size_type{2});
     ASSERT_FALSE(registry.empty());
     ASSERT_FALSE(registry.empty());
@@ -121,11 +121,11 @@ TEST(DefaultRegistry, Functionalities) {
     ASSERT_TRUE(registry.empty<int>());
     ASSERT_TRUE(registry.empty<int>());
     ASSERT_TRUE(registry.empty<char>());
     ASSERT_TRUE(registry.empty<char>());
 
 
-    e1 = registry.create<int>();
-    e2 = registry.create();
+    e0 = registry.create<int>();
+    e1 = registry.create();
 
 
+    ASSERT_NO_THROW(registry.reset<int>(e0));
     ASSERT_NO_THROW(registry.reset<int>(e1));
     ASSERT_NO_THROW(registry.reset<int>(e1));
-    ASSERT_NO_THROW(registry.reset<int>(e2));
 
 
     ASSERT_EQ(registry.size<int>(), entt::DefaultRegistry::size_type{0});
     ASSERT_EQ(registry.size<int>(), entt::DefaultRegistry::size_type{0});
     ASSERT_EQ(registry.size<char>(), entt::DefaultRegistry::size_type{0});
     ASSERT_EQ(registry.size<char>(), entt::DefaultRegistry::size_type{0});
@@ -200,6 +200,28 @@ TEST(DefaultRegistry, Each) {
     registry.each([&](auto) { FAIL(); });
     registry.each([&](auto) { FAIL(); });
 }
 }
 
 
+TEST(DefaultRegistry, Orphans) {
+    entt::DefaultRegistry registry;
+    entt::DefaultRegistry::size_type tot{};
+
+    registry.create<int>();
+    registry.create();
+    registry.create<int>();
+    registry.create();
+
+    registry.orphans([&](auto) { ++tot; });
+    ASSERT_EQ(tot, 2u);
+    tot = 0u;
+
+    registry.each([&](auto entity) { registry.reset<int>(entity); });
+    registry.orphans([&](auto) { ++tot; });
+    ASSERT_EQ(tot, 4u);
+    registry.reset();
+    tot = 0u;
+
+    registry.orphans([&](auto) { ++tot; });
+    ASSERT_EQ(tot, 0u);
+}
 
 
 TEST(DefaultRegistry, Types) {
 TEST(DefaultRegistry, Types) {
     entt::DefaultRegistry registry;
     entt::DefaultRegistry registry;