This commit is contained in:
Michele Caini
2018-03-11 23:07:31 +01:00
parent 1dd9da4dff
commit d295c88474
7 changed files with 730 additions and 28 deletions

163
README.md
View File

@@ -25,6 +25,7 @@
* [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)
@@ -76,7 +77,7 @@ Requests for feature, PR, suggestions ad feedback are highly appreciated.
If you find you can help me and want to contribute to the `EnTT` framework with
your experience or you do want to get part of the project for some other
reason, feel free to contact me directly (you can find the mail in the
reasons, feel free to contact me directly (you can find the mail in the
[profile](https://github.com/skypjack)).<br/>
I can't promise that each and every contribution will be accepted, but I can
assure that I'll do my best to take them all seriously.
@@ -91,6 +92,7 @@ compile-time or at runtime).
* An incredibly fast entity-component system based on sparse sets, with its own
views and a _pay for what you use_ policy to adjust performance and memory usage
according to the users' requirements.
* Spaces, a nice and easy way to create partitions between entities.
* Actor class for those who aren't confident with entity-component systems.
* The smallest and most basic implementation of a service locator ever seen.
* A cooperative scheduler for processes of any type.
@@ -175,8 +177,7 @@ case.<br/>
In the end, I did it, but it wasn't much satisfying. Actually it wasn't
satisfying at all. The fastest and nothing more, fairly little indeed. When I
realized it, I tried hard to keep intact the great performance of `EnTT` and to
add all the features I wanted to see in *my* entity-component system at the same
time.
add all the features I wanted to see in *my own library* at the same time.
Today `EnTT` is finally what I was looking for: still faster than its
_competitors_, lower memory usage in the average case, a really good API and an
@@ -194,21 +195,32 @@ Dell XPS 13 out of the mid 2014):
| Create 1M entities | 0.0167s | **0.0046s** |
| Destroy 1M entities | 0.0053s | **0.0022s** |
| Standard view, 1M entities, one component | 0.0012s | **1.9e-07s** |
| Standard view, 1M entities, two components | 0.0012s | **0.0010s** |
| Standard view, 1M entities, two components<br/>Half of the entities have all the components | 0.0009s | **0.0006s** |
| Standard view, 1M entities, two components | 0.0012s | **3.8e-07s** |
| Standard view, 1M entities, two components<br/>Half of the entities have all the components | 0.0009s | **3.8e-07s** |
| Standard view, 1M entities, two components<br/>One of the entities has all the components | 0.0008s | **1.0e-06s** |
| Persistent view, 1M entities, two components | 0.0012s | **2.8e-07s** |
| Standard view, 1M entities, five components | **0.0010s** | 0.0024s |
| Standard view, 1M entities, five components | 0.0010s | **7.0e-07s** |
| Persistent view, 1M entities, five components | 0.0010s | **2.8e-07s** |
| Standard view, 1M entities, ten components | **0.0011s** | 0.0058s |
| Standard view, 1M entities, ten components<br/>Half of the entities have all the components | **0.0010s** | 0.0032s |
| Standard view, 1M entities, ten components<br/>One of the entities has all the components | 0.0008s | **1.7e-06s** |
| Standard view, 1M entities, ten components | 0.0011s | **1.2e-06s** |
| Standard view, 1M entities, ten components<br/>Half of the entities have all the components | 0.0010s | **1.2e-06s** |
| Standard view, 1M entities, ten components<br/>One of the entities has all the components | 0.0008s | **1.2e-06s** |
| Persistent view, 1M entities, ten components | 0.0011s | **3.0e-07s** |
| Sort 150k entities, one component<br/>Arrays are in reverse order | - | **0.0036s** |
| Sort 150k entities, enforce permutation<br/>Arrays are in reverse order | - | **0.0005s** |
Note: The default version of `EntityX` (`master` branch) wasn't added to the
comparison because it's already much slower than its compile-time counterpart.
comparison because it's already much slower than its compile-time
counterpart.
Pretty interesting, aren't them? In fact, these benchmarks are the same used by
`EntityX` to show _how good it is_. To be honest, they aren't so good and these
results shouldn't be taken much seriously.<br/>
The proposed entity-component system is incredibly fast to iterate entities and
the compiler can make a lot of optimizations as long as components aren't used.
Similarly, its extra level of indirection pulls in a lot of interesting features
(as an example, it's possible to create/destroy entities and components during
iterations) with the risk of slowing down everything if users do not use it
carefully and choose the right tool (namely the best _view_) in each case.
`EnTT` includes its own tests and benchmarks. See
[benchmark.cpp](https://github.com/skypjack/entt/blob/master/test/benchmark.cpp)
@@ -333,7 +345,7 @@ The `Registry` to store, the `View` to iterate. That's all.
An entity (the _E_ of an _ECS_) is an opaque identifier that users should just
use as-is and store around if needed. Do not try to inspect an entity
identifier, its type can change in future and a registry offers all the
identifier, its format can change in future and a registry offers all the
functionalities to query them out-of-the-box. The underlying type of an entity
(either `std::uint16_t`, `std::uint32_t` or `std::uint64_t`) can be specified
when defining a registry (actually the `DefaultRegistry` is nothing more than a
@@ -652,6 +664,14 @@ In fact, there are two functions that respond to slightly different needs:
## View: to persist or not to persist?
First of all, it is worth answering an obvious question: why views?<br/>
Roughly speaking, they are a good tool to enforce single responsibility. A
system that has access to a registry can create and destroy entities, as well as
assign and remove components. On the other side, a system that has access to a
view can only iterate entities and their components as well as modify their data
members.<br/>
It is a subtle difference that can help designing a better software sometimes.
There are mainly two kinds of views: standard (also known as `View`) and
persistent (also known as `PersistentView`).<br/>
Both of them have pros and cons to take in consideration. In particular:
@@ -729,7 +749,7 @@ terms of performance in all the situation. This kind of views can access the
underlying data structures directly and avoid superfluous checks.<br/>
They offer a bunch of functionalities to get the number of entities they are
going to return and a raw access to the entity list as well as to the component
list.<br/>
list. It's also possible to ask a view if it contains a given entity.<br/>
Refer to the [official documentation](https://skypjack.github.io/entt/) for all
the details.
@@ -774,7 +794,8 @@ set of candidates in order to speed up iterations.<br/>
They offer fewer functionalities than their companion views for single
component. In particular, a multi component standard view exposes utility
functions to reset its internal state (optimization purposes) and to get the
estimated number of entities it is going to return.<br/>
estimated number of entities it is going to return. It's also possible to ask a
view if it contains a given entity.<br/>
Refer to the [official documentation](https://skypjack.github.io/entt/) for all
the details.
@@ -850,7 +871,8 @@ immediately and does nothing.
A persistent view offers a bunch of functionalities to get the number of
entities it's going to return, a raw access to the entity list and the
possibility to sort the underlying data structures according to the order of one
of the components for which it has been constructed.<br/>
of the components for which it has been constructed. It's also possible to ask a
view if it contains a given entity.<br/>
Refer to the [official documentation](https://skypjack.github.io/entt/) for all
the details.
@@ -928,6 +950,119 @@ In general, all these functions can result in poor performance.<br/>
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.<br/>
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.<br/>
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<typename entt::DefaultRegistry::entity_type> 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.<br/>
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.<br/>
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<AComponent, AnotherComponent>([](auto entity, auto &aComponent, auto &anotherComponent) {
// ...
});
// uses a persistent view internally
space.persisten<AComponent, AnotherComponent>([](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

3
TODO
View File

@@ -3,8 +3,7 @@
* to analyze, long term feature: systems organizer based on dependency graphs for implicit parallelism (I don't want to think anymore in future :-))
* save/restore functionalities - see #27
* parent-child relationships between entities directly managed by the registry. is it possible to do that in a clean and safe way?
* blueprint registry - external tool, kind of factory to create entitites template for initialization
* scene management (I prefer the concept of spaces, that is a kind of scene anyway)
* blueprint registry - kind of factory to create entitites template for initialization (get rid of the extra versions of Registry::create)
* raw view (affects sparse sets - custom iterator in derived one - and introduces a new kind of view): single component view to iterate components only instead of entities (in the right order!!)
it should speed up systems like rendering or whatever requires a single component and isn't interested in the entity, for it avoids the double check of the get
* AOB

268
src/entt/entity/space.hpp Normal file
View File

@@ -0,0 +1,268 @@
#ifndef ENTT_ENTITY_SPACE_HPP
#define ENTT_ENTITY_SPACE_HPP
#include <utility>
#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.<br/>
* 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<typename Entity>
class Space: private SparseSet<Entity> {
using view_type = SparseSet<Entity>;
template<typename View, typename Func>
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<decltype(components)>(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<Entity>;
/*! @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 &registry)
: 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<Entity>::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<Entity>::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<Entity>::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<Entity>::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.<br/>
* 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.<br/>
* 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.<br/>
* 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.<br/>
* 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.<br/>
* 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<typename... Component, typename Func>
void view(Func func) {
each(registry.template view<Component...>(), 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.<br/>
* 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<typename... Component, typename Func>
void persistent(Func func) {
each(registry.template persistent<Component...>(), 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.<br/>
* 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<Entity>::reset();
}
private:
Registry<Entity> &registry;
};
/**
* @brief Default space class.
*
* The default space is the best choice for almost all the applications.<br/>
* Users should have a really good reason to choose something different.
*/
using DefaultSpace = Space<DefaultRegistry::entity_type>;
}
#endif // ENTT_ENTITY_SPACE_HPP

View File

@@ -4,6 +4,7 @@
#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"

View File

@@ -55,6 +55,7 @@ add_executable(
$<TARGET_OBJECTS:odr>
entt/entity/actor.cpp
entt/entity/registry.cpp
entt/entity/space.cpp
entt/entity/sparse_set.cpp
entt/entity/view.cpp
)

View File

@@ -1,10 +1,10 @@
#include <gtest/gtest.h>
#include <iostream>
#include <cstddef>
#include <cstdint>
#include <chrono>
#include <vector>
#include <gtest/gtest.h>
#include <entt/entity/registry.hpp>
#include <entt/entity/space.hpp>
struct Position {
std::uint64_t x;
@@ -17,7 +17,7 @@ struct Velocity {
};
template<std::size_t>
struct Comp {};
struct Comp { int x; };
struct Timer final {
Timer(): start{std::chrono::system_clock::now()} {}
@@ -47,17 +47,16 @@ TEST(Benchmark, Construct) {
TEST(Benchmark, Destroy) {
entt::DefaultRegistry registry;
std::vector<entt::DefaultRegistry::entity_type> entities{};
std::cout << "Destroying 1000000 entities" << std::endl;
for(std::uint64_t i = 0; i < 1000000L; i++) {
entities.push_back(registry.create());
registry.create<int>();
}
Timer timer;
for(auto entity: entities) {
for(auto entity: registry.view<int>()) {
registry.destroy(entity);
}
@@ -281,13 +280,11 @@ TEST(Benchmark, IterateTenComponentsPersistent1M) {
TEST(Benchmark, SortSingle) {
entt::DefaultRegistry registry;
std::vector<entt::DefaultRegistry::entity_type> entities{};
std::cout << "Sort 150000 entities, one component" << std::endl;
for(std::uint64_t i = 0; i < 150000L; i++) {
auto entity = registry.create<Position>({ i, i });
entities.push_back(entity);
registry.create<Position>({ i, i });
}
Timer timer;
@@ -301,13 +298,11 @@ TEST(Benchmark, SortSingle) {
TEST(Benchmark, SortMulti) {
entt::DefaultRegistry registry;
std::vector<entt::DefaultRegistry::entity_type> entities{};
std::cout << "Sort 150000 entities, two components" << std::endl;
for(std::uint64_t i = 0; i < 150000L; i++) {
auto entity = registry.create<Position, Velocity>({ i, i }, { i, i });
entities.push_back(entity);
registry.create<Position, Velocity>({ i, i }, { i, i });
}
registry.sort<Position>([](const auto &lhs, const auto &rhs) {
@@ -320,3 +315,152 @@ 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<int>();
}
Timer timer;
for(auto entity: registry.view<int>()) {
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<Position>();
space.assign(entity);
}
Timer timer;
space.view<Position>([](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<Position, Velocity>();
space.assign(entity);
}
Timer timer;
space.view<Position, Velocity>([](auto, auto &...) {});
timer.elapsed();
}
TEST(Benchmark, SpaceIterateTwoComponentsPersistent1M) {
entt::DefaultRegistry registry;
entt::DefaultSpace space{registry};
registry.prepare<Position, Velocity>();
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<Position, Velocity>();
space.assign(entity);
}
Timer timer;
space.persistent<Position, Velocity>([](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<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>();
space.assign(entity);
}
Timer timer;
space.view<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>([](auto, auto &...) {});
timer.elapsed();
}
TEST(Benchmark, SpaceIterateFiveComponentsPersistent1M) {
entt::DefaultRegistry registry;
entt::DefaultSpace space{registry};
registry.prepare<Position, Velocity, Comp<1>, 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<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>();
space.assign(entity);
}
Timer timer;
space.persistent<Position, Velocity, Comp<1>, 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<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
space.assign(entity);
}
Timer timer;
space.view<Position, Velocity, Comp<1>, 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<Position, Velocity, Comp<1>, 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<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>();
space.assign(entity);
}
Timer timer;
space.persistent<Position, Velocity, Comp<1>, Comp<2>, Comp<3>, Comp<4>, Comp<5>, Comp<6>, Comp<7>, Comp<8>>([](auto, auto &...) {});
timer.elapsed();
}

154
test/entt/entity/space.cpp Normal file
View File

@@ -0,0 +1,154 @@
#include <gtest/gtest.h>
#include <entt/entity/registry.hpp>
#include <entt/entity/space.hpp>
TEST(Space, SpaceContainsView) {
entt::DefaultRegistry registry;
entt::DefaultSpace space{registry};
auto e0 = space.create();
auto e1 = registry.create();
space.assign(e1);
registry.assign<int>(e1);
ASSERT_TRUE(space.contains(e0));
ASSERT_TRUE(space.contains(e1));
space.view<int>([e0, e1](auto entity, auto&&...) {
ASSERT_NE(entity, e0);
ASSERT_EQ(entity, e1);
});
space.view<double>([e0, e1](auto, auto&&...) {
FAIL();
});
auto count = 0;
for(auto entity: space) {
(void)entity;
++count;
}
ASSERT_EQ(count, 2);
const auto view = registry.view<int>();
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<int>();
}
ASSERT_EQ(space.size(), entt::DefaultSpace::size_type{5});
ASSERT_FALSE(space.empty());
space.view<int>([](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<int>(e0);
registry.assign<int>(e1);
ASSERT_FALSE(space.contains(e0));
ASSERT_TRUE(space.contains(e1));
space.view<int>([e0, e1](auto entity, auto&&...) {
ASSERT_NE(entity, e0);
ASSERT_EQ(entity, e1);
});
space.view<double>([e0, e1](auto, auto&&...) {
FAIL();
});
auto count = 0;
for(auto entity: space) {
(void)entity;
++count;
}
ASSERT_EQ(count, 1);
const auto view = registry.view<int>();
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<int>();
registry.create<int>();
}
ASSERT_EQ(space.size(), entt::DefaultSpace::size_type{5});
ASSERT_FALSE(space.empty());
space.view<int>([](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());
}