runtime view

This commit is contained in:
Michele Caini
2018-06-28 14:31:04 +02:00
parent a66fa9d844
commit 10a7c54364
7 changed files with 1465 additions and 362 deletions

138
README.md
View File

@@ -36,6 +36,7 @@
* [Multi component standard view](#multi-component-standard-view)
* [Persistent View](#persistent-view)
* [Raw View](#raw-view)
* [Runtime View](#runtime-view)
* [Give me everything](#give-me-everything)
* [Iterations: what is allowed and what is not](#iterations-what-is-allowed-and-what-is-not)
* [Multithreading](#multithreading)
@@ -211,18 +212,14 @@ Dell XPS 13 out of the mid 2014):
|-----------|-------------|-------------|
| Create 1M entities | 0.0147s | **0.0046s** |
| Destroy 1M entities | 0.0053s | **0.0045s** |
| Standard view, 1M entities, one component | 0.0012s | **1.9e-07s** |
| 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 | **7.0e-07s** |
| Persistent view, 1M entities, five components | 0.0010s | **2.8e-07s** |
| 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** |
| Raw view, 1M entities | - | **2.2e-07s** |
| 1M entities, one component | 0.0012s | **1.9e-07s** |
| 1M entities, two components | 0.0012s | **3.8e-07s** |
| 1M entities, two components<br/>Half of the entities have all the components | 0.0009s | **3.8e-07s** |
| 1M entities, two components<br/>One of the entities has all the components | 0.0008s | **1.0e-06s** |
| 1M entities, five components | 0.0010s | **7.0e-07s** |
| 1M entities, ten components | 0.0011s | **1.2e-06s** |
| 1M entities, ten components<br/>Half of the entities have all the components | 0.0010s | **1.2e-06s** |
| 1M entities, ten components<br/>One of the entities has all the components | 0.0008s | **1.2e-06s** |
| 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** |
| Sort 150k entities, one component<br/>Arrays are almost sorted, std::sort | - | **0.0035s** |
@@ -715,9 +712,9 @@ created or destroyed.
### Runtime components
Defining components at runtime is useful to support plugins and mods in general.
However, it seems impossible with a tool designed around a bunch of templates.
Indeed it's not that difficult.<br/>
Defining components at runtime is useful to support plugin systems and mods in
general. However, it seems impossible with a tool designed around a bunch of
templates. Indeed it's not that difficult.<br/>
Of course, some features cannot be easily exported into a runtime
environment. As an example, sorting a group of components defined at runtime
isn't for free if compared to most of the other operations. However, the basic
@@ -1175,9 +1172,9 @@ view can only iterate entities and their components, then read or update the
data members of the latter.<br/>
It is a subtle difference that can help designing a better software sometimes.
There are mainly three kinds of views: standard (also known as `View`),
persistent (also known as `PersistentView`) and raw (also known as
`RawView`).<br/>
There are mainly four kinds of views: standard (also known as `View`),
persistent (also known as `PersistentView`), raw (also known as `RawView`) and
runtime (also known as `RuntimeView`).<br/>
All of them have pros and cons to take in consideration. In particular:
* Standard views:
@@ -1232,6 +1229,21 @@ All of them have pros and cons to take in consideration. In particular:
* They can be used to iterate only one type of component at a time.
* They don't return the entity to which a component belongs to the caller.
* Runtime views:
Pros:
* Their lists of components are defined at runtime and not at compile-time.
* Creating and destroying them isn't expensive at all because they don't have
any type of initialization.
* They are the best tool for things like plugin systems and mods in general.
* They don't affect any other operations of the registry.
Cons:
* Their performances are definitely lower than those of all the other views,
although they are still usable and sufficient for most of the purposes.
To sum up and as a rule of thumb:
* Use a raw view to iterate components only (no entities) for a given type.
@@ -1250,6 +1262,8 @@ To sum up and as a rule of thumb:
entities but the intersection between the sets of entities is small.
* Prepare and use a persistent view in all the cases where a standard view
wouldn't fit well otherwise.
* Finally, in case you don't know at compile-time what are the components to
use, choose a runtime view and set them during execution.
To easily iterate entities and components, all the views offer the common
`begin` and `end` member functions that allow users to use a view in a typical
@@ -1288,7 +1302,7 @@ There is no need to store views around for they are extremely cheap to
construct, even though they can be copied without problems and reused freely. In
fact, they return newly created and correctly initialized iterators whenever
`begin` or `end` are invoked.<br/>
To iterate a single component standard view, either use it in range-for loop:
To iterate a single component standard view, either use it in a range-for loop:
```cpp
auto view = registry.view<Renderable>();
@@ -1309,8 +1323,8 @@ registry.view<Renderable>().each([](auto entity, auto &renderable) {
});
```
Performance are more or less the same. The best approach depends mainly on
whether all the components have to be accessed or not.
The `each` member function is highly optimized. Unless users want to iterate
only entities, using `each` should be the preferred approach.
**Note**: prefer the `get` member function of a view instead of the `get` member
function template of a registry during iterations, if possible. However, keep in
@@ -1333,7 +1347,7 @@ There is no need to store views around for they are extremely cheap to
construct, even though they can be copied without problems and reused freely. In
fact, they return newly created and correctly initialized iterators whenever
`begin` or `end` are invoked.<br/>
To iterate a multi component standard view, either use it in range-for loop:
To iterate a multi component standard view, either use it in a range-for loop:
```cpp
auto view = registry.view<Position, Velocity>();
@@ -1359,8 +1373,9 @@ registry.view<Position, Velocity>().each([](auto entity, auto &position, auto &v
});
```
Performance are more or less the same. The best approach depends mainly on
whether all the components have to be accessed or not.
The `each` member function is highly optimized. Unless users want to iterate
only entities or get only some of the components, using `each` should be the
preferred approach.
**Note**: prefer the `get` member function of a view instead of the `get` member
function template of a registry during iterations, if possible. However, keep in
@@ -1406,7 +1421,7 @@ 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 inline documentation for all the details.
To iterate a persistent view, either use it in range-for loop:
To iterate a persistent view, either use it in a range-for loop:
```cpp
auto view = registry.view<Position, Velocity>(entt::persistent_t{});
@@ -1461,7 +1476,7 @@ There is no need to store views around for they are extremely cheap to
construct, even though they can be copied without problems and reused freely. In
fact, they return newly created and correctly initialized iterators whenever
`begin` or `end` are invoked.<br/>
To iterate a raw view, use it in range-for loop:
To iterate a raw view, use it in a range-for loop:
```cpp
auto view = registry.view<Renderable>(entt::raw_t{});
@@ -1471,9 +1486,74 @@ for(auto &&component: raw) {
}
```
**Note**: raw views don't have the `each` nor the `get` member function for
obvious reasons. The former would only return the components and therefore it
would be redundant, the latter isn't required at all.
Or rely on the `each` member function:
```cpp
registry.view<Renderable>(entt::raw_t{}).each([](auto &renderable) {
// ...
});
```
Performance are exactly the same in both cases.
**Note**: raw views don't have a `get` member function for obvious reasons.
### Runtime View
Runtime views iterate entities that have at least all the given components in
their bags. During construction, these views look at the number of entities
available for each component and pick up a reference to the smallest
set of candidates in order to speed up iterations.<br/>
They offer more or less the same functionalities of a multi component standard
view. However, they don't expose a `get` member function and users should refer
to the registry that generated the view to access components. In particular, a
runtime view exposes utility functions to get the estimated number of entities
it is going to return and to know whether it's empty or not. It's also possible
to ask a view if it contains a given entity.<br/>
Refer to the inline documentation for all the details.
Runtime view are extremely cheap to construct and should not be stored around in
any case. They should be used immediately after creation and then they should be
thrown away. The reasons for this go far beyond the scope of this document.<br/>
To iterate a runtime view, either use it in a range-for loop:
```cpp
using component_type = typename decltype(registry)::component_type;
component_type types[] = { registry.type<Position>(), registry.type<Velocity>() };
auto view = registry.view(std::cbegin(types), std::cend(types));
for(auto entity: view) {
// a component at a time ...
Position &position = registry.get<Position>(entity);
Velocity &velocity = registry.get<Velocity>(entity);
// ... or multiple components at once
std::tuple<Position &, Velocity &> tup = view.get<Position, Velocity>(entity);
// ...
}
```
Or rely on the `each` member function to iterate entities:
```cpp
using component_type = typename decltype(registry)::component_type;
component_type types[] = { registry.type<Position>(), registry.type<Velocity>() };
auto view = registry.view(std::cbegin(types), std::cend(types)).each([](auto entity) {
// ...
});
```
Performance are exactly the same in both cases.
**Note**: runtime views are meant for all those cases where users don't know at
compile-time what components to use to iterate entities. This is particularly
well suited to plugin systems and mods in general. Where possible, don't use
runtime views, as their performance are slightly inferior to those of the other
views.
### Give me everything

3
TODO
View File

@@ -2,14 +2,11 @@
* scene management (I prefer the concept of spaces, that is a kind of scene anyway)
* review doc: separate it in multiple md/dox files, reduce the readme to a minimum and provide users with links to the online documentation on gh-pages
* debugging tools (#60): the issue online already contains interesting tips on this, look at it
* dynamic view, useful for runtime ecs, can be filled with the desired pool at runtime and are not constrained to a compile-time list of components
* define basic reactive systems (track entities to which component is attached, track entities from which component is removed, and so on)
* define systems as composable mixins (initializazion, reactive, update, whatever) with flexible auto-detected arguments (registry, views, etc)
* create dedicated flat map based on types implementation (sort of "type map") for types to use within the registry and so on...
* ease the assignment of tags as string (type-less assign member function + user defined literal for hashed strings)
* break snapshot <-> registry dependency (and get rid of utility.hpp)
* family -> std::uint32_t (or some other fixed and known size type)
* work stealing job system (see #100)
* more on runtime stuff
* C++17. That's all.
* AOB

View File

@@ -9,6 +9,7 @@
#include <cstddef>
#include <cstdint>
#include <cassert>
#include <iterator>
#include <algorithm>
#include <type_traits>
#include "../config/config.h"
@@ -74,13 +75,13 @@ class Registry {
};
template<typename Component>
bool managed() const ENTT_NOEXCEPT {
inline bool managed() const ENTT_NOEXCEPT {
const auto ctype = component_family::type<Component>();
return ctype < pools.size() && std::get<0>(pools[ctype]);
}
template<typename Component>
const SparseSet<Entity, Component> & pool() const ENTT_NOEXCEPT {
inline const SparseSet<Entity, Component> & pool() const ENTT_NOEXCEPT {
assert(managed<Component>());
const auto ctype = component_family::type<Component>();
return static_cast<SparseSet<Entity, Component> &>(*std::get<0>(pools[ctype]));
@@ -1298,6 +1299,7 @@ public:
* @see View<Entity, Component>
* @see PersistentView
* @see RawView
* @see RuntimeView
*
* @tparam Component Type of components used to construct the view.
* @return A newly created standard view.
@@ -1432,6 +1434,7 @@ public:
* @see View<Entity, Component>
* @see PersistentView
* @see RawView
* @see RuntimeView
*
* @tparam Component Types of components used to construct the view.
* @return A newly created persistent view.
@@ -1461,6 +1464,7 @@ public:
* @see View<Entity, Component>
* @see PersistentView
* @see RawView
* @see RuntimeView
*
* @tparam Component Type of component used to construct the view.
* @return A newly created raw view.
@@ -1471,6 +1475,44 @@ public:
return RawView<Entity, Component>{pool<Component>()};
}
/**
* @brief Returns a runtime view for the given components.
*
* This kind of views are created on the fly and share with the registry its
* internal data structures.<br/>
* Users should throw away the view after use. Fortunately, creating and
* destroying a view is an incredibly cheap operation because they do not
* require any type of initialization.<br/>
* As a rule of thumb, storing a view should never be an option.
*
* Runtime views are well suited when users want to construct a view from
* some external inputs and don't know at compile-time what are the required
* components.<br/>
* This is particularly well suited to plugin systems and mods in general.
*
* @see View
* @see View<Entity, Component>
* @see PersistentView
* @see RawView
* @see RuntimeView
*
* @tparam It Type of forward iterator.
* @param first An iterator to the first element of the range of components.
* @param last An iterator past the last element of the range of components.
* @return A newly created runtime view.
*/
template<typename It>
RuntimeView<Entity> view(It first, It last) {
static_assert(std::is_convertible<typename std::iterator_traits<It>::value_type, component_type>::value, "!");
std::vector<const SparseSet<Entity> *> set(last - first);
std::transform(first, last, set.begin(), [this](const component_type ctype) {
return ctype < pools.size() ? std::get<0>(pools[ctype]).get() : nullptr;
});
return RuntimeView<Entity>{std::move(set)};
}
/**
* @brief Returns a temporary object to use to create snapshots.
*

View File

@@ -6,6 +6,7 @@
#include <cassert>
#include <array>
#include <tuple>
#include <vector>
#include <utility>
#include <algorithm>
#include <type_traits>
@@ -61,6 +62,7 @@ class Registry;
* @sa View
* @sa View<Entity, Component>
* @sa RawView
* @sa RuntimeView
*
* @tparam Entity A valid entity type (see entt_traits for more details).
* @tparam Component Types of components iterated by the view.
@@ -455,6 +457,7 @@ private:
* @sa View<Entity, Component>
* @sa PersistentView
* @sa RawView
* @sa RuntimeView
*
* @tparam Entity A valid entity type (see entt_traits for more details).
* @tparam Component Types of components iterated by the view.
@@ -481,7 +484,7 @@ class View final {
class Iterator {
friend class View<Entity, Component...>;
using size_type = typename view_type::size_type;
using extent_type = typename view_type::size_type;
Iterator(unchecked_type unchecked, underlying_iterator_type begin, underlying_iterator_type end) ENTT_NOEXCEPT
: unchecked{unchecked},
@@ -495,7 +498,7 @@ class View final {
}
template<std::size_t... Indexes>
size_type min(std::index_sequence<Indexes...>) const ENTT_NOEXCEPT {
extent_type min(std::index_sequence<Indexes...>) const ENTT_NOEXCEPT {
return std::min({ std::get<Indexes>(unchecked)->extent()... });
}
@@ -549,7 +552,7 @@ class View final {
unchecked_type unchecked;
underlying_iterator_type begin;
underlying_iterator_type end;
size_type extent;
extent_type extent;
};
View(pool_type<Component> &... pools) ENTT_NOEXCEPT
@@ -658,7 +661,7 @@ public:
*/
const_iterator_type cbegin() const ENTT_NOEXCEPT {
const auto *view = candidate();
return iterator_type{ unchecked(view), view->cbegin(), view->cend() };
return const_iterator_type{unchecked(view), view->cbegin(), view->cend()};
}
/**
@@ -714,7 +717,7 @@ public:
*/
const_iterator_type cend() const ENTT_NOEXCEPT {
const auto *view = candidate();
return iterator_type{ unchecked(view), view->cend(), view->cend() };
return const_iterator_type{unchecked(view), view->cend(), view->cend()};
}
/**
@@ -944,6 +947,7 @@ private:
* @sa View
* @sa PersistentView
* @sa RawView
* @sa RuntimeView
*
* @tparam Entity A valid entity type (see entt_traits for more details).
* @tparam Component Type of component iterated by the view.
@@ -1287,6 +1291,7 @@ private:
* @sa View
* @sa View<Entity, Component>
* @sa PersistentView
* @sa RuntimeView
*
* @tparam Entity A valid entity type (see entt_traits for more details).
* @tparam Component Type of component iterated by the view.
@@ -1523,12 +1528,12 @@ public:
/**
* @brief Iterates components and applies the given function object to them.
*
* The function object is provided with a const reference to each component
* of the view.<br/>
* The function object is provided with a reference to each component of the
* view.<br/>
* The signature of the function should be equivalent to the following:
*
* @code{.cpp}
* void(const Component &);
* void(Component &);
* @endcode
*
* @tparam Func Type of the function object to invoke.
@@ -1544,6 +1549,345 @@ private:
};
/**
* @brief Runtime view.
*
* Runtime views iterate over those entities that have at least all the given
* components in their bags. During initialization, a runtime view looks at the
* number of entities available for each component and picks up a reference to
* the smallest set of candidate entities in order to get a performance boost
* when iterate.<br/>
* Order of elements during iterations are highly dependent on the order of the
* underlying data structures. See SparseSet and its specializations for more
* details.
*
* @b Important
*
* Iterators aren't invalidated if:
*
* * New instances of the given components are created and assigned to entities.
* * The entity currently pointed is modified (as an example, if one of the
* given components is removed from the entity to which the iterator points).
*
* In all the other cases, modifying the pools of the given components in any
* way invalidates all the iterators and using them results in undefined
* behavior.
*
* @note
* Views share references to the underlying data structures with the Registry
* that generated them. Therefore any change to the entities and to the
* components made by means of the registry are immediately reflected by views,
* unless a pool wasn't missing when the view was built (in this case, the view
* won't have a valid reference and won't be updated accordingly).
*
* @warning
* Lifetime of a view must overcome the one of the registry that generated it.
* In any other case, attempting to use a view results in undefined behavior.
*
* @sa View
* @sa View<Entity, Component>
* @sa PersistentView
* @sa RawView
*
* @tparam Entity A valid entity type (see entt_traits for more details).
*/
template<typename Entity>
class RuntimeView {
/*! @brief A registry is allowed to create views. */
friend class Registry<Entity>;
using view_type = SparseSet<Entity>;
using underlying_iterator_type = typename view_type::const_iterator_type;
using pattern_type = std::vector<const view_type *>;
using extent_type = typename view_type::size_type;
using traits_type = entt_traits<Entity>;
class Iterator {
friend class RuntimeView<Entity>;
Iterator(underlying_iterator_type begin, underlying_iterator_type end, const view_type * const *first, const view_type * const *last, extent_type extent) ENTT_NOEXCEPT
: begin{begin},
end{end},
first{first},
last{last},
extent{extent}
{
if(begin != end && !valid()) {
++(*this);
}
}
bool valid() const ENTT_NOEXCEPT {
const auto entity = *begin;
const auto sz = size_type(entity & traits_type::entity_mask);
return sz < extent && std::all_of(first, last, [entity](const auto *view) {
return view->fast(entity);
});
}
public:
using difference_type = typename underlying_iterator_type::difference_type;
using value_type = typename underlying_iterator_type::value_type;
using pointer = typename underlying_iterator_type::pointer;
using reference = typename underlying_iterator_type::reference;
using iterator_category = std::forward_iterator_tag;
Iterator() ENTT_NOEXCEPT = default;
Iterator(const Iterator &) ENTT_NOEXCEPT = default;
Iterator & operator=(const Iterator &) ENTT_NOEXCEPT = default;
Iterator & operator++() ENTT_NOEXCEPT {
return (++begin != end && !valid()) ? ++(*this) : *this;
}
Iterator operator++(int) ENTT_NOEXCEPT {
Iterator orig = *this;
return ++(*this), orig;
}
bool operator==(const Iterator &other) const ENTT_NOEXCEPT {
return other.begin == begin;
}
inline bool operator!=(const Iterator &other) const ENTT_NOEXCEPT {
return !(*this == other);
}
pointer operator->() const ENTT_NOEXCEPT {
return begin.operator->();
}
inline reference operator*() const ENTT_NOEXCEPT {
return *operator->();
}
private:
underlying_iterator_type begin;
underlying_iterator_type end;
const view_type * const *first;
const view_type * const *last;
extent_type extent;
};
RuntimeView(pattern_type others) ENTT_NOEXCEPT
: pools{std::move(others)}
{
const auto it = std::min_element(pools.begin(), pools.end(), [](const auto *lhs, const auto *rhs) {
return !lhs || (rhs && (lhs->size() < rhs->size()));
});
// brings the best candidate (if any) on front of the vector
std::rotate(pools.begin(), it, pools.end());
}
extent_type min() const ENTT_NOEXCEPT {
extent_type extent{};
if(valid()) {
const auto it = std::min_element(pools.cbegin(), pools.cend(), [](const auto *lhs, const auto *rhs) {
return lhs->extent() < rhs->extent();
});
extent = (*it)->extent();
}
return extent;
}
inline bool valid() const ENTT_NOEXCEPT {
return !pools.empty() && pools.front();
}
public:
/*! @brief Underlying entity identifier. */
using entity_type = typename view_type::entity_type;
/*! @brief Unsigned integer type. */
using size_type = typename view_type::size_type;
/*! @brief Input iterator type. */
using iterator_type = Iterator;
/*! @brief Constant input iterator type. */
using const_iterator_type = Iterator;
/**
* @brief Estimates the number of entities that have the given components.
* @return Estimated number of entities that have the given components.
*/
size_type size() const ENTT_NOEXCEPT {
return valid() ? pools.front()->size() : size_type{};
}
/**
* @brief Checks if the view is definitely empty.
* @return True if the view is definitely empty, false otherwise.
*/
bool empty() const ENTT_NOEXCEPT {
return !valid() || pools.front()->empty();
}
/**
* @brief Returns an iterator to the first entity that has the given
* components.
*
* The returned iterator points to the first entity that has the given
* components. If the view is empty, the returned iterator will be equal to
* `end()`.
*
* @note
* Input iterators stay true to the order imposed to the underlying data
* structures.
*
* @return An iterator to the first entity that has the given components.
*/
const_iterator_type cbegin() const ENTT_NOEXCEPT {
const_iterator_type it{};
if(valid()) {
const auto &pool = *pools.front();
const auto * const *data = pools.data();
it = { pool.cbegin(), pool.cend(), data + 1, data + pools.size(), min() };
}
return it;
}
/**
* @brief Returns an iterator to the first entity that has the given
* components.
*
* The returned iterator points to the first entity that has the given
* components. If the view is empty, the returned iterator will be equal to
* `end()`.
*
* @note
* Input iterators stay true to the order imposed to the underlying data
* structures.
*
* @return An iterator to the first entity that has the given components.
*/
inline const_iterator_type begin() const ENTT_NOEXCEPT {
return cbegin();
}
/**
* @brief Returns an iterator to the first entity that has the given
* components.
*
* The returned iterator points to the first entity that has the given
* components. If the view is empty, the returned iterator will be equal to
* `end()`.
*
* @note
* Input iterators stay true to the order imposed to the underlying data
* structures.
*
* @return An iterator to the first entity that has the given components.
*/
inline iterator_type begin() ENTT_NOEXCEPT {
return cbegin();
}
/**
* @brief Returns an iterator that is past the last entity that has the
* given components.
*
* The returned iterator points to the entity following the last entity that
* has the given components. Attempting to dereference the returned iterator
* results in undefined behavior.
*
* @note
* Input iterators stay true to the order imposed to the underlying data
* structures.
*
* @return An iterator to the entity following the last entity that has the
* given components.
*/
const_iterator_type cend() const ENTT_NOEXCEPT {
const_iterator_type it{};
if(valid()) {
const auto &pool = *pools.front();
it = { pool.cend(), pool.cend(), nullptr, nullptr, min() };
}
return it;
}
/**
* @brief Returns an iterator that is past the last entity that has the
* given components.
*
* The returned iterator points to the entity following the last entity that
* has the given components. Attempting to dereference the returned iterator
* results in undefined behavior.
*
* @note
* Input iterators stay true to the order imposed to the underlying data
* structures.
*
* @return An iterator to the entity following the last entity that has the
* given components.
*/
inline const_iterator_type end() const ENTT_NOEXCEPT {
return cend();
}
/**
* @brief Returns an iterator that is past the last entity that has the
* given components.
*
* The returned iterator points to the entity following the last entity that
* has the given components. Attempting to dereference the returned iterator
* results in undefined behavior.
*
* @note
* Input iterators stay true to the order imposed to the underlying data
* structures.
*
* @return An iterator to the entity following the last entity that has the
* given components.
*/
inline iterator_type end() ENTT_NOEXCEPT {
return cend();
}
/**
* @brief Checks if a view contains an entity.
* @param entity A valid entity identifier.
* @return True if the view contains the given entity, false otherwise.
*/
bool contains(const entity_type entity) const ENTT_NOEXCEPT {
return valid() && std::all_of(pools.cbegin(), pools.cend(), [entity](const auto *view) {
return view->has(entity) && view->data()[view->get(entity)] == entity;
});
}
/**
* @brief Iterates entities and applies the given function object to them.
*
* The function object is invoked for each entity. It is provided only with
* the entity itself. To get the components, users can use the registry with
* which the view was built.<br/>
* The signature of the function should be equivalent to the following:
*
* @code{.cpp}
* void(const entity_type);
* @endcode
*
* @tparam Func Type of the function object to invoke.
* @param func A valid function object.
*/
template<typename Func>
void each(Func func) const {
std::for_each(cbegin(), cend(), func);
}
private:
pattern_type pools;
};
}

View File

@@ -2,6 +2,7 @@
#include <cstddef>
#include <cstdint>
#include <chrono>
#include <iterator>
#include <gtest/gtest.h>
#include <entt/entity/registry.hpp>
@@ -135,6 +136,31 @@ TEST(Benchmark, IterateSingleComponentRaw1M) {
});
}
TEST(Benchmark, IterateSingleComponentRuntime1M) {
entt::DefaultRegistry registry;
std::cout << "Iterating over 1000000 entities, one component, runtime view" << std::endl;
for(std::uint64_t i = 0; i < 1000000L; i++) {
const auto entity = registry.create();
registry.assign<Position>(entity);
}
auto test = [&registry](auto func) {
using component_type = typename entt::DefaultRegistry::component_type;
component_type types[] = { registry.type<Position>() };
Timer timer;
registry.view(std::begin(types), std::end(types)).each(func);
timer.elapsed();
};
test([](auto) {});
test([&registry](auto entity) {
registry.get<Position>(entity).x = {};
});
}
TEST(Benchmark, IterateTwoComponents1M) {
entt::DefaultRegistry registry;
@@ -242,6 +268,94 @@ TEST(Benchmark, IterateTwoComponentsPersistent1M) {
});
}
TEST(Benchmark, IterateTwoComponentsRuntime1M) {
entt::DefaultRegistry registry;
registry.prepare<Position, Velocity>();
std::cout << "Iterating over 1000000 entities, two components, runtime view" << std::endl;
for(std::uint64_t i = 0; i < 1000000L; i++) {
const auto entity = registry.create();
registry.assign<Position>(entity);
registry.assign<Velocity>(entity);
}
auto test = [&registry](auto func) {
using component_type = typename entt::DefaultRegistry::component_type;
component_type types[] = { registry.type<Position>(), registry.type<Velocity>() };
Timer timer;
registry.view(std::begin(types), std::end(types)).each(func);
timer.elapsed();
};
test([](auto) {});
test([&registry](auto entity) {
registry.get<Position>(entity).x = {};
registry.get<Velocity>(entity).x = {};
});
}
TEST(Benchmark, IterateTwoComponentsRuntime1MHalf) {
entt::DefaultRegistry registry;
std::cout << "Iterating over 1000000 entities, two components, half of the entities have all the components, runtime view" << std::endl;
for(std::uint64_t i = 0; i < 1000000L; i++) {
const auto entity = registry.create();
registry.assign<Velocity>(entity);
if(i % 2) {
registry.assign<Position>(entity);
}
}
auto test = [&registry](auto func) {
using component_type = typename entt::DefaultRegistry::component_type;
component_type types[] = { registry.type<Position>(), registry.type<Velocity>() };
Timer timer;
registry.view(std::begin(types), std::end(types)).each(func);
timer.elapsed();
};
test([](auto) {});
test([&registry](auto entity) {
registry.get<Position>(entity).x = {};
registry.get<Velocity>(entity).x = {};
});
}
TEST(Benchmark, IterateTwoComponentsRuntime1MOne) {
entt::DefaultRegistry registry;
std::cout << "Iterating over 1000000 entities, two components, only one entity has all the components, runtime view" << std::endl;
for(std::uint64_t i = 0; i < 1000000L; i++) {
const auto entity = registry.create();
registry.assign<Velocity>(entity);
if(i == 5000000L) {
registry.assign<Position>(entity);
}
}
auto test = [&registry](auto func) {
using component_type = typename entt::DefaultRegistry::component_type;
component_type types[] = { registry.type<Position>(), registry.type<Velocity>() };
Timer timer;
registry.view(std::begin(types), std::end(types)).each(func);
timer.elapsed();
};
test([](auto) {});
test([&registry](auto entity) {
registry.get<Position>(entity).x = {};
registry.get<Velocity>(entity).x = {};
});
}
TEST(Benchmark, IterateFiveComponents1M) {
entt::DefaultRegistry registry;
@@ -361,6 +475,130 @@ TEST(Benchmark, IterateFiveComponentsPersistent1M) {
});
}
TEST(Benchmark, IterateFiveComponentsRuntime1M) {
entt::DefaultRegistry registry;
registry.prepare<Position, Velocity, Comp<1>, Comp<2>, Comp<3>>();
std::cout << "Iterating over 1000000 entities, five components, runtime view" << std::endl;
for(std::uint64_t i = 0; i < 1000000L; i++) {
const auto entity = registry.create();
registry.assign<Position>(entity);
registry.assign<Velocity>(entity);
registry.assign<Comp<1>>(entity);
registry.assign<Comp<2>>(entity);
registry.assign<Comp<3>>(entity);
}
auto test = [&registry](auto func) {
using component_type = typename entt::DefaultRegistry::component_type;
component_type types[] = {
registry.type<Position>(),
registry.type<Velocity>(),
registry.type<Comp<1>>(),
registry.type<Comp<2>>(),
registry.type<Comp<3>>()
};
Timer timer;
registry.view(std::begin(types), std::end(types)).each(func);
timer.elapsed();
};
test([](auto) {});
test([&registry](auto entity) {
registry.get<Position>(entity).x = {};
registry.get<Velocity>(entity).x = {};
registry.get<Comp<1>>(entity).x = {};
registry.get<Comp<2>>(entity).x = {};
registry.get<Comp<3>>(entity).x = {};
});
}
TEST(Benchmark, IterateFiveComponentsRuntime1MHalf) {
entt::DefaultRegistry registry;
std::cout << "Iterating over 1000000 entities, five components, half of the entities have all the components, runtime view" << std::endl;
for(std::uint64_t i = 0; i < 1000000L; i++) {
const auto entity = registry.create();
registry.assign<Velocity>(entity);
registry.assign<Comp<1>>(entity);
registry.assign<Comp<2>>(entity);
registry.assign<Comp<3>>(entity);
if(i % 2) {
registry.assign<Position>(entity);
}
}
auto test = [&registry](auto func) {
using component_type = typename entt::DefaultRegistry::component_type;
component_type types[] = {
registry.type<Position>(),
registry.type<Velocity>(),
registry.type<Comp<1>>(),
registry.type<Comp<2>>(),
registry.type<Comp<3>>()
};
Timer timer;
registry.view(std::begin(types), std::end(types)).each(func);
timer.elapsed();
};
test([](auto) {});
test([&registry](auto entity) {
registry.get<Position>(entity).x = {};
registry.get<Velocity>(entity).x = {};
registry.get<Comp<1>>(entity).x = {};
registry.get<Comp<2>>(entity).x = {};
registry.get<Comp<3>>(entity).x = {};
});
}
TEST(Benchmark, IterateFiveComponentsRuntime1MOne) {
entt::DefaultRegistry registry;
std::cout << "Iterating over 1000000 entities, five components, only one entity has all the components, runtime view" << std::endl;
for(std::uint64_t i = 0; i < 1000000L; i++) {
const auto entity = registry.create();
registry.assign<Velocity>(entity);
registry.assign<Comp<1>>(entity);
registry.assign<Comp<2>>(entity);
registry.assign<Comp<3>>(entity);
if(i == 5000000L) {
registry.assign<Position>(entity);
}
}
auto test = [&registry](auto func) {
using component_type = typename entt::DefaultRegistry::component_type;
component_type types[] = {
registry.type<Position>(),
registry.type<Velocity>(),
registry.type<Comp<1>>(),
registry.type<Comp<2>>(),
registry.type<Comp<3>>()
};
Timer timer;
registry.view(std::begin(types), std::end(types)).each(func);
timer.elapsed();
};
test([](auto) {});
test([&registry](auto entity) {
registry.get<Position>(entity).x = {};
registry.get<Velocity>(entity).x = {};
registry.get<Comp<1>>(entity).x = {};
registry.get<Comp<2>>(entity).x = {};
registry.get<Comp<3>>(entity).x = {};
});
}
TEST(Benchmark, IterateTenComponents1M) {
entt::DefaultRegistry registry;
@@ -500,6 +738,175 @@ TEST(Benchmark, IterateTenComponentsPersistent1M) {
});
}
TEST(Benchmark, IterateTenComponentsRuntime1M) {
entt::DefaultRegistry 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, runtime view" << std::endl;
for(std::uint64_t i = 0; i < 1000000L; i++) {
const auto entity = registry.create();
registry.assign<Position>(entity);
registry.assign<Velocity>(entity);
registry.assign<Comp<1>>(entity);
registry.assign<Comp<2>>(entity);
registry.assign<Comp<3>>(entity);
registry.assign<Comp<4>>(entity);
registry.assign<Comp<5>>(entity);
registry.assign<Comp<6>>(entity);
registry.assign<Comp<7>>(entity);
registry.assign<Comp<8>>(entity);
}
auto test = [&registry](auto func) {
using component_type = typename entt::DefaultRegistry::component_type;
component_type types[] = {
registry.type<Position>(),
registry.type<Velocity>(),
registry.type<Comp<1>>(),
registry.type<Comp<2>>(),
registry.type<Comp<3>>(),
registry.type<Comp<4>>(),
registry.type<Comp<5>>(),
registry.type<Comp<6>>(),
registry.type<Comp<7>>(),
registry.type<Comp<8>>()
};
Timer timer;
registry.view(std::begin(types), std::end(types)).each(func);
timer.elapsed();
};
test([](auto) {});
test([&registry](auto entity) {
registry.get<Position>(entity).x = {};
registry.get<Velocity>(entity).x = {};
registry.get<Comp<1>>(entity).x = {};
registry.get<Comp<2>>(entity).x = {};
registry.get<Comp<3>>(entity).x = {};
registry.get<Comp<4>>(entity).x = {};
registry.get<Comp<5>>(entity).x = {};
registry.get<Comp<6>>(entity).x = {};
registry.get<Comp<7>>(entity).x = {};
registry.get<Comp<8>>(entity).x = {};
});
}
TEST(Benchmark, IterateTenComponentsRuntime1MHalf) {
entt::DefaultRegistry registry;
std::cout << "Iterating over 1000000 entities, ten components, half of the entities have all the components, runtime view" << std::endl;
for(std::uint64_t i = 0; i < 1000000L; i++) {
const auto entity = registry.create();
registry.assign<Velocity>(entity);
registry.assign<Comp<1>>(entity);
registry.assign<Comp<2>>(entity);
registry.assign<Comp<3>>(entity);
registry.assign<Comp<4>>(entity);
registry.assign<Comp<5>>(entity);
registry.assign<Comp<6>>(entity);
registry.assign<Comp<7>>(entity);
registry.assign<Comp<8>>(entity);
if(i % 2) {
registry.assign<Position>(entity);
}
}
auto test = [&registry](auto func) {
using component_type = typename entt::DefaultRegistry::component_type;
component_type types[] = {
registry.type<Position>(),
registry.type<Velocity>(),
registry.type<Comp<1>>(),
registry.type<Comp<2>>(),
registry.type<Comp<3>>(),
registry.type<Comp<4>>(),
registry.type<Comp<5>>(),
registry.type<Comp<6>>(),
registry.type<Comp<7>>(),
registry.type<Comp<8>>()
};
Timer timer;
registry.view(std::begin(types), std::end(types)).each(func);
timer.elapsed();
};
test([](auto) {});
test([&registry](auto entity) {
registry.get<Position>(entity).x = {};
registry.get<Velocity>(entity).x = {};
registry.get<Comp<1>>(entity).x = {};
registry.get<Comp<2>>(entity).x = {};
registry.get<Comp<3>>(entity).x = {};
registry.get<Comp<4>>(entity).x = {};
registry.get<Comp<5>>(entity).x = {};
registry.get<Comp<6>>(entity).x = {};
registry.get<Comp<7>>(entity).x = {};
registry.get<Comp<8>>(entity).x = {};
});
}
TEST(Benchmark, IterateTenComponentsRuntime1MOne) {
entt::DefaultRegistry registry;
std::cout << "Iterating over 1000000 entities, ten components, only one entity has all the components, runtime view" << std::endl;
for(std::uint64_t i = 0; i < 1000000L; i++) {
const auto entity = registry.create();
registry.assign<Velocity>(entity);
registry.assign<Comp<1>>(entity);
registry.assign<Comp<2>>(entity);
registry.assign<Comp<3>>(entity);
registry.assign<Comp<4>>(entity);
registry.assign<Comp<5>>(entity);
registry.assign<Comp<6>>(entity);
registry.assign<Comp<7>>(entity);
registry.assign<Comp<8>>(entity);
if(i == 5000000L) {
registry.assign<Position>(entity);
}
}
auto test = [&registry](auto func) {
using component_type = typename entt::DefaultRegistry::component_type;
component_type types[] = {
registry.type<Position>(),
registry.type<Velocity>(),
registry.type<Comp<1>>(),
registry.type<Comp<2>>(),
registry.type<Comp<3>>(),
registry.type<Comp<4>>(),
registry.type<Comp<5>>(),
registry.type<Comp<6>>(),
registry.type<Comp<7>>(),
registry.type<Comp<8>>()
};
Timer timer;
registry.view(std::begin(types), std::end(types)).each(func);
timer.elapsed();
};
test([](auto) {});
test([&registry](auto entity) {
registry.get<Position>(entity).x = {};
registry.get<Velocity>(entity).x = {};
registry.get<Comp<1>>(entity).x = {};
registry.get<Comp<2>>(entity).x = {};
registry.get<Comp<3>>(entity).x = {};
registry.get<Comp<4>>(entity).x = {};
registry.get<Comp<5>>(entity).x = {};
registry.get<Comp<6>>(entity).x = {};
registry.get<Comp<7>>(entity).x = {};
registry.get<Comp<8>>(entity).x = {};
});
}
TEST(Benchmark, SortSingle) {
entt::DefaultRegistry registry;

View File

@@ -1,307 +1,9 @@
#include <utility>
#include <iterator>
#include <gtest/gtest.h>
#include <entt/entity/registry.hpp>
#include <entt/entity/view.hpp>
TEST(SingleComponentView, Functionalities) {
entt::Registry<std::uint64_t> registry;
auto view = registry.view<char>();
const auto &cview = view;
const auto e0 = registry.create();
const auto e1 = registry.create();
ASSERT_TRUE(view.empty());
registry.assign<int>(e1);
registry.assign<char>(e1);
ASSERT_NO_THROW(registry.view<char>().begin()++);
ASSERT_NO_THROW(++registry.view<char>().begin());
ASSERT_NE(view.begin(), view.end());
ASSERT_NE(cview.begin(), cview.end());
ASSERT_EQ(view.size(), typename decltype(view)::size_type{1});
ASSERT_FALSE(view.empty());
registry.assign<char>(e0);
ASSERT_EQ(view.size(), typename decltype(view)::size_type{2});
view.get(e0) = '1';
view.get(e1) = '2';
for(auto entity: view) {
const auto &cview = static_cast<const decltype(view) &>(view);
ASSERT_TRUE(cview.get(entity) == '1' || cview.get(entity) == '2');
}
ASSERT_EQ(*(view.data() + 0), e1);
ASSERT_EQ(*(view.data() + 1), e0);
ASSERT_EQ(*(view.raw() + 0), '2');
ASSERT_EQ(*(static_cast<const decltype(view) &>(view).raw() + 1), '1');
registry.remove<char>(e0);
registry.remove<char>(e1);
ASSERT_EQ(view.begin(), view.end());
ASSERT_EQ(view.cbegin(), view.cend());
ASSERT_TRUE(view.empty());
}
TEST(SingleComponentView, ElementAccess) {
entt::Registry<std::uint64_t> registry;
auto view = registry.view<int>();
const auto &cview = view;
const auto e0 = registry.create();
registry.assign<int>(e0);
const auto e1 = registry.create();
registry.assign<int>(e1);
for(typename decltype(view)::size_type i{}; i < view.size(); ++i) {
//ASSERT_EQ(view[i], i ? e0 : e1);
ASSERT_EQ(cview[i], i ? e0 : e1);
}
}
TEST(SingleComponentView, Contains) {
entt::Registry<std::uint64_t> registry;
const auto e0 = registry.create();
registry.assign<int>(e0);
const auto e1 = registry.create();
registry.assign<int>(e1);
registry.destroy(e0);
auto view = registry.view<int>();
ASSERT_FALSE(view.contains(e0));
ASSERT_TRUE(view.contains(e1));
}
TEST(SingleComponentView, Empty) {
entt::Registry<std::uint64_t> registry;
const auto e0 = registry.create();
registry.assign<char>(e0);
registry.assign<double>(e0);
const auto e1 = registry.create();
registry.assign<char>(e1);
auto view = registry.view<int>();
ASSERT_EQ(view.size(), entt::Registry<std::uint64_t>::size_type{0});
for(auto entity: view) {
(void)entity;
FAIL();
}
}
TEST(SingleComponentView, Each) {
entt::Registry<std::uint64_t> registry;
registry.assign<int>(registry.create());
registry.assign<int>(registry.create());
auto view = registry.view<int>();
const auto &cview = static_cast<const decltype(view) &>(view);
std::size_t cnt = 0;
view.each([&cnt](auto, int &) { ++cnt; });
ASSERT_EQ(cnt, std::size_t{2});
cview.each([&cnt](auto, const int &) { --cnt; });
ASSERT_EQ(cnt, std::size_t{0});
}
TEST(MultipleComponentView, Functionalities) {
entt::Registry<std::uint64_t> registry;
auto view = registry.view<int, char>();
const auto &cview = view;
ASSERT_TRUE(view.empty());
const auto e0 = registry.create();
registry.assign<char>(e0);
const auto e1 = registry.create();
registry.assign<int>(e1);
ASSERT_FALSE(view.empty());
registry.assign<char>(e1);
auto it = registry.view<char>().begin();
ASSERT_EQ(*it, e1);
ASSERT_EQ(*(it+1), e0);
ASSERT_EQ(it += 2, registry.view<char>().end());
ASSERT_NO_THROW((registry.view<int, char>().begin()++));
ASSERT_NO_THROW((++registry.view<int, char>().begin()));
ASSERT_NE(view.begin(), view.end());
ASSERT_NE(cview.begin(), cview.end());
ASSERT_EQ(view.size(), decltype(view.size()){1});
registry.get<char>(e0) = '1';
registry.get<char>(e1) = '2';
registry.get<int>(e1) = 42;
for(auto entity: view) {
const auto &cview = static_cast<const decltype(view) &>(view);
ASSERT_EQ(std::get<0>(cview.get<int, char>(entity)), 42);
ASSERT_EQ(std::get<1>(view.get<int, char>(entity)), '2');
ASSERT_EQ(cview.get<char>(entity), '2');
}
registry.remove<char>(e0);
registry.remove<char>(e1);
}
TEST(MultipleComponentView, Iterator) {
using iterator_type = typename decltype(std::declval<entt::Registry<std::uint64_t>>().view<int, char>())::iterator_type;
entt::Registry<std::uint64_t> registry;
const auto entity = registry.create();
registry.assign<int>(entity);
registry.assign<char>(entity);
const auto view = registry.view<int, char>();
iterator_type end{view.begin()};
iterator_type begin{};
begin = view.end();
std::swap(begin, end);
ASSERT_EQ(begin, view.begin());
ASSERT_EQ(end, view.end());
ASSERT_NE(begin, end);
ASSERT_EQ(view.begin()++, view.begin());
ASSERT_EQ(++view.begin(), view.end());
}
TEST(MultipleComponentView, ConstIterator) {
using iterator_type = typename decltype(std::declval<entt::Registry<std::uint64_t>>().view<int, char>())::const_iterator_type;
entt::Registry<std::uint64_t> registry;
const auto entity = registry.create();
registry.assign<int>(entity);
registry.assign<char>(entity);
const auto view = registry.view<int, char>();
iterator_type cend{view.cbegin()};
iterator_type cbegin{};
cbegin = view.cend();
std::swap(cbegin, cend);
ASSERT_EQ(cbegin, view.cbegin());
ASSERT_EQ(cend, view.cend());
ASSERT_NE(cbegin, cend);
ASSERT_EQ(view.cbegin()++, view.cbegin());
ASSERT_EQ(++view.cbegin(), view.cend());
}
TEST(MultipleComponentView, Contains) {
entt::Registry<std::uint64_t> registry;
const auto e0 = registry.create();
registry.assign<int>(e0);
registry.assign<char>(e0);
const auto e1 = registry.create();
registry.assign<int>(e1);
registry.assign<char>(e1);
registry.destroy(e0);
auto view = registry.view<int, char>();
ASSERT_FALSE(view.contains(e0));
ASSERT_TRUE(view.contains(e1));
}
TEST(MultipleComponentView, Empty) {
entt::Registry<std::uint64_t> registry;
const auto e0 = registry.create();
registry.assign<double>(e0);
registry.assign<int>(e0);
registry.assign<float>(e0);
const auto e1 = registry.create();
registry.assign<char>(e1);
registry.assign<float>(e1);
auto view = registry.view<char, int, float>();
for(auto entity: view) {
(void)entity;
FAIL();
}
}
TEST(MultipleComponentView, Each) {
entt::Registry<std::uint64_t> registry;
const auto e0 = registry.create();
registry.assign<int>(e0);
registry.assign<char>(e0);
const auto e1 = registry.create();
registry.assign<int>(e1);
registry.assign<char>(e1);
auto view = registry.view<int, char>();
const auto &cview = static_cast<const decltype(view) &>(view);
std::size_t cnt = 0;
view.each([&cnt](auto, int &, char &) { ++cnt; });
ASSERT_EQ(cnt, std::size_t{2});
cview.each([&cnt](auto, const int &, const char &) { --cnt; });
ASSERT_EQ(cnt, std::size_t{0});
}
TEST(MultipleComponentView, EachWithHoles) {
entt::Registry<std::uint64_t> registry;
const auto e0 = registry.create();
const auto e1 = registry.create();
const auto e2 = registry.create();
registry.assign<char>(e0, '0');
registry.assign<char>(e1, '1');
registry.assign<int>(e0, 0);
registry.assign<int>(e2, 2);
auto view = registry.view<char, int>();
view.each([e0](auto entity, const char &c, const int &i) {
if(e0 == entity) {
ASSERT_EQ(c, '0');
ASSERT_EQ(i, 0);
} else {
FAIL();
}
});
}
TEST(PersistentView, Prepare) {
entt::Registry<std::uint64_t> registry;
registry.prepare<int, char>();
@@ -524,6 +226,299 @@ TEST(PersistentView, Sort) {
}
}
TEST(SingleComponentView, Functionalities) {
entt::Registry<std::uint64_t> registry;
auto view = registry.view<char>();
const auto &cview = view;
const auto e0 = registry.create();
const auto e1 = registry.create();
ASSERT_TRUE(view.empty());
registry.assign<int>(e1);
registry.assign<char>(e1);
ASSERT_NO_THROW(registry.view<char>().begin()++);
ASSERT_NO_THROW(++registry.view<char>().begin());
ASSERT_NE(view.begin(), view.end());
ASSERT_NE(cview.begin(), cview.end());
ASSERT_EQ(view.size(), typename decltype(view)::size_type{1});
ASSERT_FALSE(view.empty());
registry.assign<char>(e0);
ASSERT_EQ(view.size(), typename decltype(view)::size_type{2});
view.get(e0) = '1';
view.get(e1) = '2';
for(auto entity: view) {
const auto &cview = static_cast<const decltype(view) &>(view);
ASSERT_TRUE(cview.get(entity) == '1' || cview.get(entity) == '2');
}
ASSERT_EQ(*(view.data() + 0), e1);
ASSERT_EQ(*(view.data() + 1), e0);
ASSERT_EQ(*(view.raw() + 0), '2');
ASSERT_EQ(*(static_cast<const decltype(view) &>(view).raw() + 1), '1');
registry.remove<char>(e0);
registry.remove<char>(e1);
ASSERT_EQ(view.begin(), view.end());
ASSERT_EQ(view.cbegin(), view.cend());
ASSERT_TRUE(view.empty());
}
TEST(SingleComponentView, ElementAccess) {
entt::Registry<std::uint64_t> registry;
auto view = registry.view<int>();
const auto &cview = view;
const auto e0 = registry.create();
registry.assign<int>(e0);
const auto e1 = registry.create();
registry.assign<int>(e1);
for(typename decltype(view)::size_type i{}; i < view.size(); ++i) {
ASSERT_EQ(view[i], i ? e0 : e1);
ASSERT_EQ(cview[i], i ? e0 : e1);
}
}
TEST(SingleComponentView, Contains) {
entt::Registry<std::uint64_t> registry;
const auto e0 = registry.create();
registry.assign<int>(e0);
const auto e1 = registry.create();
registry.assign<int>(e1);
registry.destroy(e0);
auto view = registry.view<int>();
ASSERT_FALSE(view.contains(e0));
ASSERT_TRUE(view.contains(e1));
}
TEST(SingleComponentView, Empty) {
entt::Registry<std::uint64_t> registry;
const auto e0 = registry.create();
registry.assign<char>(e0);
registry.assign<double>(e0);
const auto e1 = registry.create();
registry.assign<char>(e1);
auto view = registry.view<int>();
ASSERT_EQ(view.size(), entt::Registry<std::uint64_t>::size_type{0});
for(auto entity: view) {
(void)entity;
FAIL();
}
}
TEST(SingleComponentView, Each) {
entt::Registry<std::uint64_t> registry;
registry.assign<int>(registry.create());
registry.assign<int>(registry.create());
auto view = registry.view<int>();
const auto &cview = static_cast<const decltype(view) &>(view);
std::size_t cnt = 0;
view.each([&cnt](auto, int &) { ++cnt; });
ASSERT_EQ(cnt, std::size_t{2});
cview.each([&cnt](auto, const int &) { --cnt; });
ASSERT_EQ(cnt, std::size_t{0});
}
TEST(MultipleComponentView, Functionalities) {
entt::Registry<std::uint64_t> registry;
auto view = registry.view<int, char>();
const auto &cview = view;
ASSERT_TRUE(view.empty());
const auto e0 = registry.create();
registry.assign<char>(e0);
const auto e1 = registry.create();
registry.assign<int>(e1);
ASSERT_FALSE(view.empty());
registry.assign<char>(e1);
auto it = registry.view<int, char>().begin();
ASSERT_EQ(*it, e1);
ASSERT_EQ(++it, (registry.view<int, char>().end()));
ASSERT_NO_THROW((registry.view<int, char>().begin()++));
ASSERT_NO_THROW((++registry.view<int, char>().begin()));
ASSERT_NE(view.begin(), view.end());
ASSERT_NE(cview.begin(), cview.end());
ASSERT_EQ(view.size(), decltype(view.size()){1});
registry.get<char>(e0) = '1';
registry.get<char>(e1) = '2';
registry.get<int>(e1) = 42;
for(auto entity: view) {
const auto &cview = static_cast<const decltype(view) &>(view);
ASSERT_EQ(std::get<0>(cview.get<int, char>(entity)), 42);
ASSERT_EQ(std::get<1>(view.get<int, char>(entity)), '2');
ASSERT_EQ(cview.get<char>(entity), '2');
}
}
TEST(MultipleComponentView, Iterator) {
entt::Registry<std::uint64_t> registry;
const auto entity = registry.create();
registry.assign<int>(entity);
registry.assign<char>(entity);
const auto view = registry.view<int, char>();
using iterator_type = typename decltype(view)::iterator_type;
iterator_type end{view.begin()};
iterator_type begin{};
begin = view.end();
std::swap(begin, end);
ASSERT_EQ(begin, view.begin());
ASSERT_EQ(end, view.end());
ASSERT_NE(begin, end);
ASSERT_EQ(view.begin()++, view.begin());
ASSERT_EQ(++view.begin(), view.end());
}
TEST(MultipleComponentView, ConstIterator) {
entt::Registry<std::uint64_t> registry;
const auto entity = registry.create();
registry.assign<int>(entity);
registry.assign<char>(entity);
const auto view = registry.view<int, char>();
using iterator_type = typename decltype(view)::iterator_type;
iterator_type cend{view.cbegin()};
iterator_type cbegin{};
cbegin = view.cend();
std::swap(cbegin, cend);
ASSERT_EQ(cbegin, view.cbegin());
ASSERT_EQ(cend, view.cend());
ASSERT_NE(cbegin, cend);
ASSERT_EQ(view.cbegin()++, view.cbegin());
ASSERT_EQ(++view.cbegin(), view.cend());
}
TEST(MultipleComponentView, Contains) {
entt::Registry<std::uint64_t> registry;
const auto e0 = registry.create();
registry.assign<int>(e0);
registry.assign<char>(e0);
const auto e1 = registry.create();
registry.assign<int>(e1);
registry.assign<char>(e1);
registry.destroy(e0);
auto view = registry.view<int, char>();
ASSERT_FALSE(view.contains(e0));
ASSERT_TRUE(view.contains(e1));
}
TEST(MultipleComponentView, Empty) {
entt::Registry<std::uint64_t> registry;
const auto e0 = registry.create();
registry.assign<double>(e0);
registry.assign<int>(e0);
registry.assign<float>(e0);
const auto e1 = registry.create();
registry.assign<char>(e1);
registry.assign<float>(e1);
auto view = registry.view<char, int, float>();
for(auto entity: view) {
(void)entity;
FAIL();
}
}
TEST(MultipleComponentView, Each) {
entt::Registry<std::uint64_t> registry;
const auto e0 = registry.create();
registry.assign<int>(e0);
registry.assign<char>(e0);
const auto e1 = registry.create();
registry.assign<int>(e1);
registry.assign<char>(e1);
auto view = registry.view<int, char>();
const auto &cview = static_cast<const decltype(view) &>(view);
std::size_t cnt = 0;
view.each([&cnt](auto, int &, char &) { ++cnt; });
ASSERT_EQ(cnt, std::size_t{2});
cview.each([&cnt](auto, const int &, const char &) { --cnt; });
ASSERT_EQ(cnt, std::size_t{0});
}
TEST(MultipleComponentView, EachWithHoles) {
entt::Registry<std::uint64_t> registry;
const auto e0 = registry.create();
const auto e1 = registry.create();
const auto e2 = registry.create();
registry.assign<char>(e0, '0');
registry.assign<char>(e1, '1');
registry.assign<int>(e0, 0);
registry.assign<int>(e2, 2);
auto view = registry.view<char, int>();
view.each([e0](auto entity, const char &c, const int &i) {
if(e0 == entity) {
ASSERT_EQ(c, '0');
ASSERT_EQ(i, 0);
} else {
FAIL();
}
});
}
TEST(RawView, Functionalities) {
entt::Registry<std::uint64_t> registry;
auto view = registry.view<char>(entt::raw_t{});
@@ -634,3 +629,234 @@ TEST(RawView, Each) {
ASSERT_EQ(cnt, std::size_t{0});
}
TEST(RuntimeView, Functionalities) {
entt::Registry<std::uint64_t> registry;
using component_type = typename decltype(registry)::component_type;
// forces the creation of the pools
registry.reserve<int>(0);
registry.reserve<char>(0);
component_type types[] = { registry.type<int>(), registry.type<char>() };
auto view = registry.view(std::begin(types), std::end(types));
const auto &cview = view;
ASSERT_TRUE(view.empty());
const auto e0 = registry.create();
registry.assign<char>(e0);
const auto e1 = registry.create();
registry.assign<int>(e1);
ASSERT_FALSE(view.empty());
registry.assign<char>(e1);
auto it = registry.view(std::begin(types), std::end(types)).begin();
ASSERT_EQ(*it, e1);
ASSERT_EQ(++it, (registry.view(std::begin(types), std::end(types)).end()));
ASSERT_NO_THROW((registry.view(std::begin(types), std::end(types)).begin()++));
ASSERT_NO_THROW((++registry.view(std::begin(types), std::end(types)).begin()));
ASSERT_NE(view.begin(), view.end());
ASSERT_NE(cview.begin(), cview.end());
ASSERT_EQ(view.size(), decltype(view.size()){1});
registry.get<char>(e0) = '1';
registry.get<char>(e1) = '2';
registry.get<int>(e1) = 42;
for(auto entity: view) {
ASSERT_EQ(registry.get<int>(entity), 42);
ASSERT_EQ(registry.get<char>(entity), '2');
}
}
TEST(RuntimeView, Iterator) {
entt::Registry<std::uint64_t> registry;
using component_type = typename decltype(registry)::component_type;
const auto entity = registry.create();
registry.assign<int>(entity);
registry.assign<char>(entity);
component_type types[] = { registry.type<int>(), registry.type<char>() };
auto view = registry.view(std::begin(types), std::end(types));
using iterator_type = typename decltype(view)::iterator_type;
iterator_type end{view.begin()};
iterator_type begin{};
begin = view.end();
std::swap(begin, end);
ASSERT_EQ(begin, view.begin());
ASSERT_EQ(end, view.end());
ASSERT_NE(begin, end);
ASSERT_EQ(view.begin()++, view.begin());
ASSERT_EQ(++view.begin(), view.end());
}
TEST(RuntimeView, ConstIterator) {
entt::Registry<std::uint64_t> registry;
using component_type = typename decltype(registry)::component_type;
const auto entity = registry.create();
registry.assign<int>(entity);
registry.assign<char>(entity);
component_type types[] = { registry.type<int>(), registry.type<char>() };
auto view = registry.view(std::begin(types), std::end(types));
using iterator_type = typename decltype(view)::iterator_type;
iterator_type cend{view.cbegin()};
iterator_type cbegin{};
cbegin = view.cend();
std::swap(cbegin, cend);
ASSERT_EQ(cbegin, view.cbegin());
ASSERT_EQ(cend, view.cend());
ASSERT_NE(cbegin, cend);
ASSERT_EQ(view.cbegin()++, view.cbegin());
ASSERT_EQ(++view.cbegin(), view.cend());
}
TEST(RuntimeView, Contains) {
entt::Registry<std::uint64_t> registry;
using component_type = typename decltype(registry)::component_type;
const auto e0 = registry.create();
registry.assign<int>(e0);
registry.assign<char>(e0);
const auto e1 = registry.create();
registry.assign<int>(e1);
registry.assign<char>(e1);
registry.destroy(e0);
component_type types[] = { registry.type<int>(), registry.type<char>() };
auto view = registry.view(std::begin(types), std::end(types));
ASSERT_FALSE(view.contains(e0));
ASSERT_TRUE(view.contains(e1));
}
TEST(RuntimeView, Empty) {
entt::Registry<std::uint64_t> registry;
using component_type = typename decltype(registry)::component_type;
const auto e0 = registry.create();
registry.assign<double>(e0);
registry.assign<int>(e0);
registry.assign<float>(e0);
const auto e1 = registry.create();
registry.assign<char>(e1);
registry.assign<float>(e1);
component_type types[] = { registry.type<char>(), registry.type<int>(), registry.type<float>() };
auto view = registry.view(std::begin(types), std::end(types));
for(auto entity: view) {
(void)entity;
FAIL();
}
}
TEST(RuntimeView, Each) {
entt::Registry<std::uint64_t> registry;
using component_type = typename decltype(registry)::component_type;
const auto e0 = registry.create();
registry.assign<int>(e0);
registry.assign<char>(e0);
const auto e1 = registry.create();
registry.assign<int>(e1);
registry.assign<char>(e1);
component_type types[] = { registry.type<int>(), registry.type<char>() };
auto view = registry.view(std::begin(types), std::end(types));
std::size_t cnt = 0;
view.each([&cnt](auto) { ++cnt; });
ASSERT_EQ(cnt, std::size_t{2});
}
TEST(RuntimeView, EachWithHoles) {
entt::Registry<std::uint64_t> registry;
using component_type = typename decltype(registry)::component_type;
const auto e0 = registry.create();
const auto e1 = registry.create();
const auto e2 = registry.create();
registry.assign<char>(e0, '0');
registry.assign<char>(e1, '1');
registry.assign<int>(e0, 0);
registry.assign<int>(e2, 2);
component_type types[] = { registry.type<int>(), registry.type<char>() };
auto view = registry.view(std::begin(types), std::end(types));
view.each([e0](auto entity) {
ASSERT_EQ(e0, entity);
});
}
TEST(RuntimeView, MissingPool) {
entt::Registry<std::uint64_t> registry;
using component_type = typename decltype(registry)::component_type;
const auto e0 = registry.create();
registry.assign<int>(e0);
component_type types[] = { registry.type<int>(), registry.type<char>() };
auto view = registry.view(std::begin(types), std::end(types));
ASSERT_TRUE(view.empty());
ASSERT_EQ(view.size(), decltype(view.size()){0});
registry.assign<char>(e0);
ASSERT_TRUE(view.empty());
ASSERT_EQ(view.size(), decltype(view.size()){0});
ASSERT_FALSE(view.contains(e0));
view.each([](auto) { FAIL(); });
for(auto entity: view) {
(void)entity;
FAIL();
}
}
TEST(RuntimeView, EmptyRange) {
entt::Registry<std::uint64_t> registry;
using component_type = typename decltype(registry)::component_type;
const auto e0 = registry.create();
registry.assign<int>(e0);
const component_type *ptr = nullptr;
auto view = registry.view(ptr, ptr);
ASSERT_TRUE(view.empty());
ASSERT_EQ(view.size(), decltype(view.size()){0});
ASSERT_FALSE(view.contains(e0));
view.each([](auto) { FAIL(); });
for(auto entity: view) {
(void)entity;
FAIL();
}
}

View File

@@ -235,34 +235,41 @@ public:
duk_push_array(ctx);
dreg.registry.each([ctx, nargs, &pos, &dreg](auto entity) {
auto &registry = dreg.registry;
auto &func = dreg.func;
bool match = true;
std::vector<typename entt::DefaultRegistry::component_type> components;
std::vector<typename entt::DefaultRegistry::component_type> runtime;
for (duk_idx_t arg = 0; match && arg < nargs; arg++) {
auto type = duk_require_uint(ctx, arg);
for(duk_idx_t arg = 0; arg < nargs; arg++) {
auto type = duk_require_uint(ctx, arg);
if(type < udef) {
assert(func.find(type) != func.cend());
match = (registry.*func[type].test)(entity);
} else {
const auto ctype = registry.type<DuktapeRuntime>();
assert(func.find(ctype) != func.cend());
match = (registry.*func[ctype].test)(entity);
if(match) {
auto &components = registry.get<DuktapeRuntime>(entity).components;
match = (components.find(type) != components.cend());
}
if(type < udef) {
components.push_back(type);
} else {
if(runtime.empty()) {
components.push_back(dreg.registry.type<DuktapeRuntime>());
}
}
if(match) {
runtime.push_back(type);
}
}
auto view = dreg.registry.view(components.cbegin(), components.cend());
for(const auto entity: view) {
if(runtime.empty()) {
duk_push_uint(ctx, entity);
duk_put_prop_index(ctx, -2, pos++);
} else {
const auto &components = dreg.registry.get<DuktapeRuntime>(entity).components;
const auto match = std::all_of(runtime.cbegin(), runtime.cend(), [&components](const auto type) {
return components.find(type) != components.cend();
});
if(match) {
duk_push_uint(ctx, entity);
duk_put_prop_index(ctx, -2, pos++);
}
}
});
}
return 1;
}