Просмотр исходного кода

orphans/orphan + minor changes

Michele Caini 8 лет назад
Родитель
Сommit
d54594f11d
3 измененных файлов с 165 добавлено и 68 удалено
  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
 
-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
 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
@@ -367,7 +367,7 @@ information about the entity itself and its version.
 A registry can be used both to construct and to destroy entities:
 
 ```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();
 
 // 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.
 
 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
 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
-`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
 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:
 
 ```cpp
-entt::DefaultRegistry registry;
 const auto &cregistry = registry;
 
 // const and non-const reference
@@ -548,12 +547,12 @@ bool b = registry.has<PlayingCharacter>();
 References to tags can be retrieved simply by doing this:
 
 ```cpp
+const auto &cregistry = registry;
+
 // either a non-const reference ...
-entt::DefaultRegistry registry;
 PlayingCharacter &player = registry.get<PlayingCharacter>();
 
 // ... or a const one
-const auto &cregistry = registry;
 const Camera &camera = cregistry.get<Camera>();
 ```
 
@@ -605,15 +604,16 @@ simple.
 
 `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
-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.
 
-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.
 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
 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
-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/>
 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
 
 * 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
     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
   view.<br/>
   To work around it, possible approaches are:

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

@@ -138,7 +138,6 @@ class Registry {
 
         if(!handlers[vtype]) {
             using accumulator_type = int[];
-
             auto set = std::make_unique<SparseSet<Entity>>();
 
             for(auto entity: view<Component...>()) {
@@ -294,7 +293,7 @@ public:
     bool valid(entity_type entity) const noexcept {
         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
-        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);
     }
 
@@ -487,7 +486,7 @@ public:
             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;
     }
@@ -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:
      *
      * @code{.cpp}
      * void(entity_type);
      * @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
      * 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.
      * @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.
      *

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

@@ -18,11 +18,11 @@ TEST(DefaultRegistry, Functionalities) {
     ASSERT_TRUE(registry.empty<int>());
     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<>(e2));
 
     ASSERT_EQ(registry.capacity(), entt::DefaultRegistry::size_type{2});
     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<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_FALSE(registry.has<int>(e2));
+    ASSERT_FALSE(registry.has<char>(e0));
     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_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>(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>(e2), 1);
 
     ASSERT_EQ(registry.size(), entt::DefaultRegistry::size_type{3});
     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_NO_THROW(registry.destroy(e3));
+    ASSERT_NO_THROW(registry.destroy(e2));
     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(e2));
-    ASSERT_FALSE(registry.valid(e3));
+    ASSERT_FALSE(registry.valid(e2));
 
     ASSERT_EQ(registry.size(), entt::DefaultRegistry::size_type{2});
     ASSERT_FALSE(registry.empty());
@@ -121,11 +121,11 @@ TEST(DefaultRegistry, Functionalities) {
     ASSERT_TRUE(registry.empty<int>());
     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>(e2));
 
     ASSERT_EQ(registry.size<int>(), 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(); });
 }
 
+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) {
     entt::DefaultRegistry registry;