Skip to content

Commit

Permalink
POC: space
Browse files Browse the repository at this point in the history
  • Loading branch information
skypjack committed Mar 10, 2018
1 parent a2e243d commit 2fbfedc
Show file tree
Hide file tree
Showing 12 changed files with 800 additions and 43 deletions.
30 changes: 23 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -928,6 +940,10 @@ 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

TODO

## Side notes

* Entity identifiers are numbers and nothing more. They are not classes and they
Expand Down
3 changes: 1 addition & 2 deletions TODO
Original file line number Diff line number Diff line change
Expand Up @@ -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
28 changes: 26 additions & 2 deletions src/entt/entity/registry.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -286,15 +286,39 @@ class Registry {
}

/**
* @brief Verifies if an entity identifier still refers to a valid entity.
* @brief Checks if an entity identifier refers to a valid entity.
* @param entity An entity identifier, either valid or not.
* @return True if the identifier is still valid, false otherwise.
* @return True if the identifier is valid, false otherwise.
*/
bool valid(entity_type entity) const noexcept {
const auto pos = size_type(entity & traits_type::entity_mask);
return (pos < entities.size() && entities[pos] == entity);
}

/**
* @brief Checks if an entity identifier refers to a valid entity.
*
* Alternative version of `valid`. It accesses the internal data structures
* without bounds checking and thus it's both unsafe and risky to use.<br/>
* You should not invoke directly this function unless you know exactly what
* you are doing. Prefer the `valid` member function instead.
*
* @warning
* Attempting to use an entity that doesn't belong to the registry can
* result in undefined behavior.<br/>
* An assertion will abort the execution at runtime in debug mode in case of
* bounds violation.
*
* @param entity A valid entity identifier.
* @return True if the identifier is valid, false otherwise.
*/
bool fast(entity_type entity) const noexcept {
const auto pos = size_type(entity & traits_type::entity_mask);
assert(pos < entities.size());
// the in-use control bit permits to avoid accessing the direct vector
return (entities[pos] == entity);
}

/**
* @brief Returns the version stored along with an entity identifier.
* @param entity An entity identifier, either valid or not.
Expand Down
273 changes: 273 additions & 0 deletions src/entt/entity/space.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,273 @@
#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();
}

/**
* @brief Casts a space to its underlying registry.
*/
operator const registry_type & () const noexcept {
return registry;
}

/**
* @brief Casts a space to its underlying registry.
*/
operator registry_type & () noexcept {
return registry;
}

private:
Registry<Entity> &registry;
};


}


#endif // ENTT_ENTITY_SPACE_HPP
Loading

0 comments on commit 2fbfedc

Please sign in to comment.