From 041e31ea784b76be0562e2bcabf5746502567533 Mon Sep 17 00:00:00 2001 From: Michele Caini Date: Wed, 14 Mar 2018 08:38:20 +0100 Subject: [PATCH] removed spaces: not satisfied with the current implementation --- README.md | 114 --------------- src/entt/entity/space.hpp | 268 ----------------------------------- src/entt/entt.hpp | 1 - test/CMakeLists.txt | 1 - test/benchmark/benchmark.cpp | 150 -------------------- test/entt/entity/space.cpp | 154 -------------------- 6 files changed, 688 deletions(-) delete mode 100644 src/entt/entity/space.hpp delete mode 100644 test/entt/entity/space.cpp diff --git a/README.md b/README.md index eb0478d66..7408cac30 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,6 @@ * [Multi component standard view](#multi-component-standard-view) * [Persistent View](#persistent-view) * [Give me everything](#give-me-everything) - * [Spaces](#spaces) * [Side notes](#side-notes) * [Crash Course: core functionalities](#crash-course-core-functionalities) * [Compile-time identifiers](#compile-time-identifiers) @@ -947,119 +946,6 @@ In general, all these functions can result in poor performance.
entity. For similar reasons, `orphans` can be even slower. Both functions should not be used frequently to avoid the risk of a performance hit. -## Spaces - -Spaces are sort of partitions of a registry. They can be used to easily get a -subset of the entities of a view or a registry without recurring to multiple -registries to separate them explicitly.
-To learn more about their intended use, -[here](https://gamedevelopment.tutsplus.com/tutorials/spaces-useful-game-object-containers--gamedev-14091) -is an interesting article that goes deep into the topic. - -Spaces aren't for free. In most of the cases, the cost isn't relevant. However, -keep in mind that they add an extra check during iterations and it could slow -down a bit the whole thing.
-Alternatives to spaces exist, but they have their own problems: - -* Multiple registries: memory usage tends to grow up and some tasks are just - more difficult to accomplish (as an example, putting an entity logically in - more than one registry requires syncing them and it can quickly become a - problem). - -* Dedicated components: memory usage tends to grow up and the number of spaces - is fixed and defined at compile-time (at least, it ought to be for performance - reasons), moreover the solution is much more error-prone. - -Another benefit of spaces defined as an external class is that users of a space -do not have access to the whole registry, thus separation of responsibility is -automatically enforced. In both the alternatives described above, systems have -access to the whole set of entities instead and can easily break the contract -with the callers. - -The `EnTT` framework offers support to spaces out of the box. Spaces are -constructed using a registry to which they refer: - -```cpp -entt::DefaultRegistry registry; -entt::Space space{registry}; -``` - -They offer the classical set of member functions to know the estimated number of -entities and to check if a space has a given entity.
-Refer to the [official documentation](https://skypjack.github.io/entt/) for all -the details. - -In addition, they expose two member functions to create an entity through a -space or to assign to a space an already existent entity, other than member -functions to remove entities from a space: - -```cpp -// creates an entity using a space -auto entity = space.create(); - -// assigns an already existent entity to a space -space.assign(registry.create()); - -// removes an entity from the given space -space.remove(entity); - -// removes all the entities from a space -space.reset(); -``` - -Entities returned through the `create` member function are created directly into -the underlying registry and assigned immediately to the space.
-Removing an entity from a space doesn't mean that it's destroyed within the -underlying registry in any case. - -Spaces and thus the entities they contain can be easily iterated in a range-for -loop: - -```cpp -for(auto entity: space) { - // ... -} -``` - -However, this isn't the best way to iterate entities in a space, mainly because -this member function returns all the entities it contains, no matter what are -their components. To iterate entities that have specific components, spaces -expose two dedicated member functions that differ for the view they use under -the hood: - -```cpp -// uses a standard view internally -space.view([](auto entity, auto &aComponent, auto &anotherComponent) { - // ... -}); - -// uses a persistent view internally -space.persisten([](auto entity, auto &aComponent, auto &anotherComponent) { - // ... -}); -``` - -Spaces get rid of entities that are no longer in use during iterations. They -aren't kept in sync with a registry each and every time an entity is destroyed -so as to avoid penalties in terms of performance. Instead, spaces remove invalid -entities as soon as they are detected during iterations. - -Because of the _lazy clean_ policy, the size of a space could grow up if -destroyed entities are never detected for some reasons. To avoid it, spaces has -a member function named `shrink` that forces a clean up and reduce the size to a -minimum: - -```cpp -// gets rid of all the invalid entities still tracked by a space -space.shrink(); -``` - -Note that the size of a space isn't a problem in terms of performance. Views -rule during iterations, mainly because of the order which may have been imposed -by the user for some reasons and must be respected. Therefore unused entities -are never visited and thus they don't affect iterations. However, memory usage -can be reduced by shrinking spaces every so often. - ## Side notes * Entity identifiers are numbers and nothing more. They are not classes and they diff --git a/src/entt/entity/space.hpp b/src/entt/entity/space.hpp deleted file mode 100644 index 909e17dae..000000000 --- a/src/entt/entity/space.hpp +++ /dev/null @@ -1,268 +0,0 @@ -#ifndef ENTT_ENTITY_SPACE_HPP -#define ENTT_ENTITY_SPACE_HPP - - -#include -#include "sparse_set.hpp" -#include "registry.hpp" - - -namespace entt { - - -/** - * @brief A space is a sort of partition of a registry. - * - * Spaces can be used to create partitions of a registry. They can be useful for - * logically separating menus, world and any other type of scene, while still - * using only one registry.
- * Similar results are obtained either using multiple registries or using - * dedicated components, even though in both cases the memory usage isn't the - * same. On the other side, spaces can introduce performance hits that are - * sometimes unacceptable (mainly if you are working on AAA games or kind of). - * - * For more details about spaces and their use, take a look at this article: - * https://gamedevelopment.tutsplus.com/tutorials/spaces-useful-game-object-containers--gamedev-14091 - * - * @tparam Entity A valid entity type (see entt_traits for more details). - */ -template -class Space: private SparseSet { - using view_type = SparseSet; - - template - inline void each(View view, Func func) { - // use the view to iterate so as to respect order of components if any - view.each([func = std::move(func), this](auto entity, auto &&... components) { - if(this->has(entity)) { - if(this->data()[this->get(entity)] == entity) { - func(entity, std::forward(components)...); - } else { - // lazy destroy to avoid keeping a space in sync - this->destroy(entity); - } - } - }); - } - -public: - /*! @brief Type of registry to which the space refers. */ - using registry_type = Registry; - /*! @brief Underlying entity identifier. */ - using entity_type = typename registry_type::entity_type; - /*! @brief Input iterator type. */ - using iterator_type = typename view_type::iterator_type; - /*! @brief Unsigned integer type. */ - using size_type = typename view_type::size_type; - - /** - * @brief Constructs a space by using the given registry. - * @param registry An entity-component system properly initialized. - */ - Space(registry_type ®istry) - : registry{registry} - {} - - /** - * @brief Returns the number of entities tracked by a space. - * @return Number of entities tracked by the space. - */ - size_type size() const noexcept { - return SparseSet::size(); - } - - /** - * @brief Checks if there exists at least an entity tracked by a space. - * @return True if the space tracks at least an entity, false otherwise. - */ - bool empty() const noexcept { - return SparseSet::empty(); - } - - /** - * @brief Returns an iterator to the first entity tracked by a space. - * - * The returned iterator points to the first entity tracked by the space. If - * the space is empty, the returned iterator will be equal to `end()`. - * - * @return An iterator to the first entity tracked by a space. - */ - iterator_type begin() const noexcept { - return SparseSet::begin(); - } - - /** - * @brief Returns an iterator that is past the last entity tracked by a - * space. - * - * The returned iterator points to the entity following the last entity - * tracked by the space. Attempting to dereference the returned iterator - * results in undefined behavior. - * - * @return An iterator to the entity following the last entity tracked by a - * space. - */ - iterator_type end() const noexcept { - return SparseSet::end(); - } - - /** - * @brief Checks if a space contains an entity. - * @param entity A valid entity identifier. - * @return True if the space contains the given entity, false otherwise. - */ - bool contains(entity_type entity) const noexcept { - return this->has(entity) && this->data()[this->get(entity)] == entity; - } - - /** - * @brief Creates a new entity and returns it. - * - * The space creates an entity from the underlying registry and registers it - * immediately before to return the identifier. Use the `assign` member - * function to register an already existent entity created at a different - * time. - * - * The returned entity has no components assigned. - * - * @return A valid entity identifier. - */ - entity_type create() { - const auto entity = registry.create(); - assign(entity); - return entity; - } - - /** - * @brief Assigns an entity to a space. - * - * The space starts tracking the given entity and will return it during - * iterations whenever required.
- * Entities can be assigned to more than one space at the same time. - * - * @warning - * Attempting to use an invalid entity or to assign an entity that doesn't - * belong to the underlying registry results in undefined behavior.
- * An assertion will abort the execution at runtime in debug mode in case of - * invalid entity or if the registry doesn't own the entity. - * - * @param entity A valid entity identifier. - */ - void assign(entity_type entity) { - assert(registry.valid(entity)); - - if(this->has(entity)) { - this->destroy(entity); - } - - this->construct(entity); - } - - /** - * @brief Removes an entity from a space. - * - * The space stops tracking the given entity and won't return it anymore - * during iterations.
- * In case the entity belongs to more than one space, it won't be removed - * automatically from all the other ones as a consequence of invoking this - * function. - * - * @param entity A valid entity identifier. - */ - void remove(entity_type entity) { - if(this->has(entity)) { - this->destroy(entity); - } - } - - /** - * @brief Iterates entities using a standard view under the hood. - * - * A space does not return directly views to iterate entities because it - * requires to apply a filter to those sets. Instead, it uses a view - * internally and returns only those entities that are tracked by the space - * itself.
- * This member function can be used to iterate a space by means of a - * standard view. Naming the function the same as the type of view used to - * perform the task proved to be a good choice so as not to tricky users. - * - * @note - * Performance tend to degenerate when the number of components to iterate - * grows up and the most of the entities have all the given components.
- * To get a performance boost, consider using the `persistent` member - * function instead. - * - * @tparam Component Type of components used to construct the view. - * @tparam Func Type of the function object to invoke. - * @param func A valid function object. - */ - template - void view(Func func) { - each(registry.template view(), std::move(func)); - } - - /** - * @brief Iterates entities using a persistent view under the hood. - * - * A space does not return directly views to iterate entities because it - * requires to apply a filter to those sets. Instead, it uses a view - * internally and returns only those entities that are tracked by the space - * itself.
- * This member function can be used to iterate a space by means of a - * persistent view. Naming the function the same as the type of view used to - * perform the task proved to be a good choice so as not to tricky users. - * - * @tparam Component Type of components used to construct the view. - * @tparam Func Type of the function object to invoke. - * @param func A valid function object. - */ - template - void persistent(Func func) { - each(registry.template persistent(), std::move(func)); - } - - /** - * @brief Performs a clean up step. - * - * Spaces do a lazy cleanup during iterations to avoid introducing - * performance hits when entities are destroyed.
- * This function can be used to force a clean up step and to get rid of all - * those entities that are still tracked by a space but have been destroyed - * in the underlying registry. - */ - void shrink() { - for(auto entity: *this) { - if(!registry.fast(entity)) { - this->destroy(entity); - } - } - } - - /** - * @brief Resets a whole space. - * - * The space stops tracking all the entities assigned to it so far. After - * calling this function, iterations won't return any entity. - */ - void reset() { - SparseSet::reset(); - } - -private: - Registry ®istry; -}; - - -/** - * @brief Default space class. - * - * The default space is the best choice for almost all the applications.
- * Users should have a really good reason to choose something different. - */ -using DefaultSpace = Space; - - -} - - -#endif // ENTT_ENTITY_SPACE_HPP diff --git a/src/entt/entt.hpp b/src/entt/entt.hpp index 3d40d9c17..b107aa1c5 100644 --- a/src/entt/entt.hpp +++ b/src/entt/entt.hpp @@ -4,7 +4,6 @@ #include "entity/actor.hpp" #include "entity/entt_traits.hpp" #include "entity/registry.hpp" -#include "entity/space.hpp" #include "entity/sparse_set.hpp" #include "entity/view.hpp" #include "locator/locator.hpp" diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index a56bd5b6d..32bd6d39b 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -55,7 +55,6 @@ add_executable( $ entt/entity/actor.cpp entt/entity/registry.cpp - entt/entity/space.cpp entt/entity/sparse_set.cpp entt/entity/view.cpp ) diff --git a/test/benchmark/benchmark.cpp b/test/benchmark/benchmark.cpp index b703917cc..e545525a3 100644 --- a/test/benchmark/benchmark.cpp +++ b/test/benchmark/benchmark.cpp @@ -4,7 +4,6 @@ #include #include #include -#include struct Position { std::uint64_t x; @@ -315,152 +314,3 @@ TEST(Benchmark, SortMulti) { timer.elapsed(); } - -TEST(Benchmark, SpaceConstruct) { - entt::DefaultRegistry registry; - entt::DefaultSpace space{registry}; - - std::cout << "Constructing 1000000 entities" << std::endl; - - Timer timer; - - for(std::uint64_t i = 0; i < 1000000L; i++) { - space.create(); - } - - timer.elapsed(); -} - -TEST(Benchmark, SpaceAssign) { - entt::DefaultRegistry registry; - entt::DefaultSpace space{registry}; - - std::cout << "Assigning 1000000 entities" << std::endl; - - for(std::uint64_t i = 0; i < 1000000L; i++) { - registry.create(); - } - - Timer timer; - - for(auto entity: registry.view()) { - space.assign(entity); - } - - timer.elapsed(); -} - -TEST(Benchmark, SpaceIterateSingleComponent1M) { - entt::DefaultRegistry registry; - entt::DefaultSpace space{registry}; - - std::cout << "Iterating over 1000000 entities, one component" << std::endl; - - for(std::uint64_t i = 0; i < 1000000L; i++) { - const auto entity = registry.create(); - space.assign(entity); - } - - Timer timer; - space.view([](auto, auto &) {}); - timer.elapsed(); -} - -TEST(Benchmark, SpaceIterateTwoComponents1M) { - entt::DefaultRegistry registry; - entt::DefaultSpace space{registry}; - - std::cout << "Iterating over 1000000 entities, two components" << std::endl; - - for(std::uint64_t i = 0; i < 1000000L; i++) { - const auto entity = registry.create(); - space.assign(entity); - } - - Timer timer; - space.view([](auto, auto &...) {}); - timer.elapsed(); -} - -TEST(Benchmark, SpaceIterateTwoComponentsPersistent1M) { - entt::DefaultRegistry registry; - entt::DefaultSpace space{registry}; - registry.prepare(); - - std::cout << "Iterating over 1000000 entities, two components, persistent view" << std::endl; - - for(std::uint64_t i = 0; i < 1000000L; i++) { - const auto entity = registry.create(); - space.assign(entity); - } - - Timer timer; - space.persistent([](auto, auto &...) {}); - timer.elapsed(); -} - -TEST(Benchmark, SpaceIterateFiveComponents1M) { - entt::DefaultRegistry registry; - entt::DefaultSpace space{registry}; - - std::cout << "Iterating over 1000000 entities, five components" << std::endl; - - for(std::uint64_t i = 0; i < 1000000L; i++) { - const auto entity = registry.create, Comp<2>, Comp<3>>(); - space.assign(entity); - } - - Timer timer; - space.view, Comp<2>, Comp<3>>([](auto, auto &...) {}); - timer.elapsed(); -} - -TEST(Benchmark, SpaceIterateFiveComponentsPersistent1M) { - entt::DefaultRegistry registry; - entt::DefaultSpace space{registry}; - registry.prepare, Comp<2>, Comp<3>>(); - - std::cout << "Iterating over 1000000 entities, five components, persistent view" << std::endl; - - for(std::uint64_t i = 0; i < 1000000L; i++) { - const auto entity = registry.create, Comp<2>, Comp<3>>(); - space.assign(entity); - } - - Timer timer; - space.persistent, Comp<2>, Comp<3>>([](auto, auto &...) {}); - timer.elapsed(); -} - -TEST(Benchmark, SpaceIterateTenComponents1M) { - entt::DefaultRegistry registry; - entt::DefaultSpace space{registry}; - - std::cout << "Iterating over 1000000 entities, ten components" << std::endl; - - for(std::uint64_t i = 0; i < 1000000L; i++) { - const auto entity = registry.create, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>(); - space.assign(entity); - } - - Timer timer; - space.view, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>([](auto, auto &...) {}); - timer.elapsed(); -} - -TEST(Benchmark, SpaceIterateTenComponentsPersistent1M) { - entt::DefaultRegistry registry; - entt::DefaultSpace space{registry}; - registry.prepare, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>(); - - std::cout << "Iterating over 1000000 entities, ten components, persistent view" << std::endl; - - for(std::uint64_t i = 0; i < 1000000L; i++) { - const auto entity = registry.create, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>(); - space.assign(entity); - } - - Timer timer; - space.persistent, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>([](auto, auto &...) {}); - timer.elapsed(); -} diff --git a/test/entt/entity/space.cpp b/test/entt/entity/space.cpp deleted file mode 100644 index 2a4a46a8a..000000000 --- a/test/entt/entity/space.cpp +++ /dev/null @@ -1,154 +0,0 @@ -#include -#include -#include - -TEST(Space, SpaceContainsView) { - entt::DefaultRegistry registry; - entt::DefaultSpace space{registry}; - - auto e0 = space.create(); - auto e1 = registry.create(); - - space.assign(e1); - registry.assign(e1); - - ASSERT_TRUE(space.contains(e0)); - ASSERT_TRUE(space.contains(e1)); - - space.view([e0, e1](auto entity, auto&&...) { - ASSERT_NE(entity, e0); - ASSERT_EQ(entity, e1); - }); - - space.view([e0, e1](auto, auto&&...) { - FAIL(); - }); - - auto count = 0; - - for(auto entity: space) { - (void)entity; - ++count; - } - - ASSERT_EQ(count, 2); - - const auto view = registry.view(); - - ASSERT_EQ(view.size(), typename decltype(view)::size_type{1}); - ASSERT_EQ(space.size(), entt::DefaultSpace::size_type{2}); - ASSERT_FALSE(space.empty()); - - registry.reset(); - space.reset(); - - ASSERT_TRUE(space.empty()); - - for(auto i = 0; i < 5; ++i) { - registry.destroy(space.create()); - registry.create(); - } - - ASSERT_EQ(space.size(), entt::DefaultSpace::size_type{5}); - ASSERT_FALSE(space.empty()); - - space.view([](auto, auto&&...) { - FAIL(); - }); - - ASSERT_EQ(space.size(), entt::DefaultSpace::size_type{0}); - ASSERT_TRUE(space.empty()); -} - -TEST(Space, ViewContainsSpace) { - entt::DefaultRegistry registry; - entt::DefaultSpace space{registry}; - - auto e0 = registry.create(); - auto e1 = space.create(); - - registry.assign(e0); - registry.assign(e1); - - ASSERT_FALSE(space.contains(e0)); - ASSERT_TRUE(space.contains(e1)); - - space.view([e0, e1](auto entity, auto&&...) { - ASSERT_NE(entity, e0); - ASSERT_EQ(entity, e1); - }); - - space.view([e0, e1](auto, auto&&...) { - FAIL(); - }); - - auto count = 0; - - for(auto entity: space) { - (void)entity; - ++count; - } - - ASSERT_EQ(count, 1); - - const auto view = registry.view(); - - ASSERT_EQ(view.size(), typename decltype(view)::size_type{2}); - ASSERT_EQ(space.size(), entt::DefaultSpace::size_type{1}); - ASSERT_FALSE(space.empty()); - - registry.reset(); - space.reset(); - - ASSERT_TRUE(space.empty()); - - for(auto i = 0; i < 5; ++i) { - registry.destroy(space.create()); - registry.create(); - registry.create(); - } - - ASSERT_EQ(space.size(), entt::DefaultSpace::size_type{5}); - ASSERT_FALSE(space.empty()); - - space.view([](auto, auto&&...) { - FAIL(); - }); - - ASSERT_EQ(space.size(), entt::DefaultSpace::size_type{0}); - ASSERT_TRUE(space.empty()); -} - -TEST(Space, AssignRemove) { - entt::DefaultRegistry registry; - entt::DefaultSpace space{registry}; - - ASSERT_TRUE(space.empty()); - - space.remove(space.create()); - - ASSERT_TRUE(space.empty()); -} - -TEST(Space, Shrink) { - entt::DefaultRegistry registry; - entt::DefaultSpace space{registry}; - - for(auto i = 0; i < 5; ++i) { - space.create(); - } - - for(auto entity: space) { - registry.destroy(entity); - } - - space.create(); - - ASSERT_EQ(space.size(), entt::DefaultSpace::size_type{5}); - ASSERT_FALSE(space.empty()); - - space.shrink(); - - ASSERT_EQ(space.size(), entt::DefaultSpace::size_type{1}); - ASSERT_FALSE(space.empty()); -}