entity:
* allocator support for sparse set and storage * pointer stability when assigning components * removed ::raw functions (registry, view, group) * to_entity returns null for invalid components
This commit is contained in:
@@ -7,10 +7,9 @@
|
||||
|
||||
* [Introduction](#introduction)
|
||||
* [Design decisions](#design-decisions)
|
||||
* [A bitset-free entity-component system](#a-bitset-free-entity-component-system)
|
||||
* [Type-less and bitset-free](#type-less-and-bitset-free)
|
||||
* [Build your own](#build-your-own)
|
||||
* [Pay per use](#pay-per-use)
|
||||
* [All or nothing](#all-or-nothing)
|
||||
* [Stateless systems](#stateless-systems)
|
||||
* [Vademecum](#vademecum)
|
||||
* [Pools](#pools)
|
||||
* [The Registry, the Entity and the Component](#the-registry-the-entity-and-the-component)
|
||||
@@ -63,7 +62,7 @@ used mostly in game development.
|
||||
|
||||
# Design decisions
|
||||
|
||||
## A bitset-free entity-component system
|
||||
## Type-less and bitset-free
|
||||
|
||||
`EnTT` offers a _bitset-free_ entity-component system that doesn't require users
|
||||
to specify the set of components neither at compile-time nor at runtime.<br/>
|
||||
@@ -82,6 +81,23 @@ entt::registry<comp_0, comp_1, ..., comp_n> registry;
|
||||
Furthermore, it isn't necessary to announce the existence of a component type.
|
||||
When the time comes, just use it and that's all.
|
||||
|
||||
## Build your own
|
||||
|
||||
`EnTT` is designed as a container that can be used at any time just as a vector
|
||||
or any other tool would be used. It doesn't attempt in any way to take over on
|
||||
the user code base, nor to control its main loop or process scheduling.<br/>
|
||||
Unlike other more or less known models, it makes use of independent pools. This
|
||||
has some advantages and disadvantages. The main purpose is to provide a fully
|
||||
customizable tool, where users have the freedom to define pools and opaque
|
||||
proxies for types with specific requirements.
|
||||
|
||||
The library provides a default implementation for many things and a mixin model
|
||||
that allows users to completely replace or even just enrich the pool dedicated
|
||||
to one or more components.<br/>
|
||||
The built-in signal support is an example of that: defined as a mixin, it's
|
||||
easily disabled if not needed. Similarly, poly storage is another example of how
|
||||
everything is customizable down to the smallest detail.
|
||||
|
||||
## Pay per use
|
||||
|
||||
`EnTT` is entirely designed around the principle that users have to pay only for
|
||||
@@ -102,28 +118,6 @@ performance where needed.
|
||||
So far, this choice has proven to be a good one and I really hope it can be for
|
||||
many others besides me.
|
||||
|
||||
## All or nothing
|
||||
|
||||
`EnTT` is such that at every moment a pair `(T *, size)` is available to
|
||||
directly access all the instances of a given component type `T`.<br/>
|
||||
This was a guideline and a design decision that influenced many choices, for
|
||||
better and for worse. I cannot say whether it will be useful or not to the
|
||||
reader, but it's worth to mention it since it's one of the corner stones of
|
||||
this library.
|
||||
|
||||
Many of the tools described below give the possibility to get this information
|
||||
and have been designed around this need.<br/>
|
||||
The rest is experimentation and the desire to invent something new, hoping to
|
||||
have succeeded.
|
||||
|
||||
## Stateless systems
|
||||
|
||||
`EnTT` is designed so that it can work with _stateless systems_. In other words,
|
||||
all systems can be free functions and there is no need to define them as classes
|
||||
(although nothing prevents users from doing so).<br/>
|
||||
This is possible because the main class with which the users will work provides
|
||||
all what is needed to act as the sole _source of truth_ of an application.
|
||||
|
||||
# Vademecum
|
||||
|
||||
The registry to store, the views and the groups to iterate. That's all.
|
||||
@@ -631,9 +625,7 @@ instance of a component and returns the entity associated with the latter:
|
||||
const auto entity = entt::to_entity(registry, position);
|
||||
```
|
||||
|
||||
This utility doesn't perform any check on the validity of the component.
|
||||
Therefore, trying to take the entity of an invalid element or of an instance
|
||||
that isn't associated with the given registry can result in undefined behavior.
|
||||
A null entity is returned in case the component doesn't belong to the registry.
|
||||
|
||||
### Dependencies
|
||||
|
||||
@@ -947,7 +939,7 @@ copying an entity will be as easy as:
|
||||
|
||||
```cpp
|
||||
registry.visit(entity, [&](const auto info) {
|
||||
auto storage = registry.storage(info);
|
||||
auto &&storage = registry.storage(info);
|
||||
storage->emplace(registry, other, storage->get(entity));
|
||||
});
|
||||
```
|
||||
@@ -957,8 +949,7 @@ Similarly, copying entire pools between different registries can look like this:
|
||||
|
||||
```cpp
|
||||
registry.visit([&](const auto info) {
|
||||
auto storage = registry.storage(info);
|
||||
other.storage(info)->insert(other, storage->data(), storage->raw(), storage->size());
|
||||
registry.storage(info)->copy_to(other);
|
||||
});
|
||||
```
|
||||
|
||||
@@ -1249,9 +1240,8 @@ data structures directly and avoid superfluous checks. There is nothing as fast
|
||||
as a single component view. In fact, they walk through a packed array of
|
||||
components and return them one at a time.<br/>
|
||||
Single component views 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. It's also possible to ask a view if it contains a given
|
||||
entity.<br/>
|
||||
entities they are going to return and a raw access to the entity list. It's also
|
||||
possible to ask a view if it contains a given entity.<br/>
|
||||
Refer to the inline documentation for all the details.
|
||||
|
||||
Multi component views iterate entities that have at least all the given
|
||||
@@ -1368,8 +1358,8 @@ the above type order rules apply sequentially.
|
||||
|
||||
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/>
|
||||
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 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
|
||||
@@ -1445,9 +1435,8 @@ However, it's unlikely that users will be able to appreciate the impact of
|
||||
groups on the other functionalities of a registry.
|
||||
|
||||
Groups 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 for owned components. It's also possible to ask a group if it contains a
|
||||
given entity.<br/>
|
||||
going to return and a raw access to the entity list. It's also possible to ask a
|
||||
group if it contains a given entity.<br/>
|
||||
Refer to the inline documentation for all the details.
|
||||
|
||||
There is no need to store groups aside for they are extremely cheap to
|
||||
@@ -1760,17 +1749,20 @@ not be used frequently to avoid the risk of a performance hit.
|
||||
## What is allowed and what is not
|
||||
|
||||
Most of the _ECS_ available out there don't allow to create and destroy entities
|
||||
and components during iterations.<br/>
|
||||
and components during iterations, nor to have pointer stability.<br/>
|
||||
`EnTT` partially solves the problem with a few limitations:
|
||||
|
||||
* Creating entities and components is allowed during iterations in most cases.
|
||||
* Creating entities and components is allowed during iterations in most cases
|
||||
and it never invalidates already existing references.
|
||||
|
||||
* Deleting the current entity or removing its components is allowed during
|
||||
iterations. For all the other entities, destroying them or removing their
|
||||
components isn't allowed and can result in undefined behavior.
|
||||
iterations but it could invalidate references. For all the other entities,
|
||||
destroying them or removing their components isn't allowed and can result in
|
||||
undefined behavior.
|
||||
|
||||
In these cases, iterators aren't invalidated. To be clear, it doesn't mean that
|
||||
also references will continue to be valid.<br/>
|
||||
In other terms, iterators are never invalidated. Also, component references
|
||||
aren't invalidated when a new element is added while they could be invalidated
|
||||
upon deletion, due to the _swap-and-pop_ policy.<br/>
|
||||
Consider the following example:
|
||||
|
||||
```cpp
|
||||
@@ -1780,13 +1772,15 @@ registry.view<position>([&](const auto entity, auto &pos) {
|
||||
});
|
||||
```
|
||||
|
||||
The `each` member function won't break (because iterators aren't invalidated)
|
||||
but there are no guarantees on references. Use a common range-for loop and get
|
||||
components directly from the view or move the creation of components at the end
|
||||
of the function to avoid dangling pointers.
|
||||
The `each` member function won't break (because iterators remain valid) nor will
|
||||
any reference be invalidated. Instead, more attention should be paid to the
|
||||
destruction of entities or the removal of components.<br/>
|
||||
Use a common range-for loop and get components directly from the view or move
|
||||
the deletion of entities and components at the end of the function to avoid
|
||||
dangling pointers.
|
||||
|
||||
Iterators are invalidated instead and the behavior is undefined if an entity is
|
||||
modified or destroyed and it's not the one currently returned by the iterator
|
||||
Conversely, iterators are invalidated and the behavior is undefined if an entity
|
||||
is modified or destroyed and it's not the one currently returned by the iterator
|
||||
nor a newly created one.<br/>
|
||||
To work around it, possible approaches are:
|
||||
|
||||
@@ -1852,9 +1846,9 @@ When an empty type is detected, it's not instantiated in any case. Therefore,
|
||||
only the entities to which it's assigned are made available.<br/>
|
||||
There doesn't exist a way to _get_ empty types from a registry, views and groups
|
||||
will never return instances for them (for example, during a call to `each`) and
|
||||
some functions such as `try_get` or the raw access to the list of components
|
||||
won't be available. Finally, the `sort` functionality will onlyaccepts callbacks
|
||||
that require to return entities rather than components:
|
||||
some functions such as `try_get` aren't available for empty types. Finally, the
|
||||
`sort` functionality will only accepts callbacks that require to return entities
|
||||
rather than components:
|
||||
|
||||
```cpp
|
||||
registry.sort<empty_type>([](const entt::entity lhs, const entt::entity rhs) {
|
||||
|
||||
@@ -2,17 +2,18 @@
|
||||
#define ENTT_ENTITY_FWD_HPP
|
||||
|
||||
|
||||
#include <memory>
|
||||
#include "../core/fwd.hpp"
|
||||
|
||||
|
||||
namespace entt {
|
||||
|
||||
|
||||
template<typename>
|
||||
template<typename Entity, typename = std::allocator<Entity>>
|
||||
class basic_sparse_set;
|
||||
|
||||
|
||||
template<typename, typename, typename>
|
||||
template<typename, typename Type, typename = std::allocator<Type>, typename = void>
|
||||
class basic_storage;
|
||||
|
||||
|
||||
|
||||
@@ -680,26 +680,6 @@ public:
|
||||
return !*this || !*length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Direct access to the raw representation offered by the storage.
|
||||
*
|
||||
* For fully contiguous storage classes, the returned pointer is such that
|
||||
* range `[raw<Component>(), raw<Component>() + size())` is always a valid
|
||||
* range, even if the container is empty.
|
||||
*
|
||||
* @warning
|
||||
* This function is only available for owned types.
|
||||
*
|
||||
* @tparam Component Type of component in which one is interested.
|
||||
* @return A pointer to the array of components.
|
||||
*/
|
||||
template<typename Component>
|
||||
[[nodiscard]] auto raw() const ENTT_NOEXCEPT {
|
||||
static_assert((std::is_same_v<Component, Owned> || ...), "Non-owned type");
|
||||
auto *cpool = std::get<storage_type<Component> *>(pools);
|
||||
return cpool ? cpool->raw() : decltype(cpool->raw()){};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Direct access to the list of entities.
|
||||
*
|
||||
@@ -940,7 +920,7 @@ public:
|
||||
template<typename... Component, typename Compare, typename Sort = std_sort, typename... Args>
|
||||
void sort(Compare compare, Sort algo = Sort{}, Args &&... args) const {
|
||||
auto *cpool = std::get<0>(pools);
|
||||
|
||||
|
||||
if constexpr(sizeof...(Component) == 0) {
|
||||
static_assert(std::is_invocable_v<Compare, const entity_type, const entity_type>, "Invalid comparison function");
|
||||
cpool->sort_n(*length, std::move(compare), std::move(algo), std::forward<Args>(args)...);
|
||||
@@ -953,7 +933,7 @@ public:
|
||||
return compare(std::forward_as_tuple(std::get<storage_type<Component> *>(pools)->get(lhs)...), std::forward_as_tuple(std::get<storage_type<Component> *>(pools)->get(rhs)...));
|
||||
}, std::move(algo), std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
|
||||
[this](auto *head, auto *... other) {
|
||||
for(auto next = *length; next; --next) {
|
||||
const auto pos = next - 1;
|
||||
|
||||
@@ -135,16 +135,29 @@ void invoke(basic_registry<Entity> ®, const Entity entt) {
|
||||
|
||||
/**
|
||||
* @brief Returns the entity associated with a given component.
|
||||
*
|
||||
* @warning
|
||||
* Currently, this function only works correctly with the default pool as it
|
||||
* makes assumptions about how the components are laid out.
|
||||
*
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
* @tparam Component Type of component.
|
||||
* @param reg A registry that contains the given entity and its components.
|
||||
* @param component A valid component instance.
|
||||
* @param instance A valid component instance.
|
||||
* @return The entity associated with the given component.
|
||||
*/
|
||||
template<typename Entity, typename Component>
|
||||
Entity to_entity(const basic_registry<Entity> ®, const Component &component) {
|
||||
Entity to_entity(const basic_registry<Entity> ®, const Component &instance) {
|
||||
const auto view = reg.template view<const Component>();
|
||||
return *(view.data() + (&component - view.raw()));
|
||||
const auto *addr = std::addressof(instance);
|
||||
|
||||
for(auto it = view.rbegin(), last = view.rend(); it < last; it += ENTT_PAGE_SIZE) {
|
||||
if(const auto dist = (addr - std::addressof(view.template get<const Component>(*it))); dist >= 0 && dist < ENTT_PAGE_SIZE) {
|
||||
return *(it + dist);
|
||||
}
|
||||
}
|
||||
|
||||
return entt::null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -2,12 +2,11 @@
|
||||
#define ENTT_ENTITY_SPARSE_SET_HPP
|
||||
|
||||
|
||||
#include <iterator>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <cstddef>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include "../config/config.h"
|
||||
#include "../core/algorithm.hpp"
|
||||
#include "entity.hpp"
|
||||
@@ -38,22 +37,34 @@ namespace entt {
|
||||
* a sparse set. Do not make assumption on the order in any case.
|
||||
*
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
* @tparam Allocator Type of allocator used to manage memory and elements.
|
||||
*/
|
||||
template<typename Entity>
|
||||
template<typename Entity, typename Allocator>
|
||||
class basic_sparse_set {
|
||||
static constexpr auto growth_factor = 1.5;
|
||||
static constexpr auto page_size = ENTT_PAGE_SIZE;
|
||||
|
||||
using traits_type = entt_traits<Entity>;
|
||||
using page_type = std::unique_ptr<Entity[]>;
|
||||
|
||||
using alloc_type = typename std::allocator_traits<Allocator>::template rebind_alloc<Entity>;
|
||||
using alloc_traits = std::allocator_traits<alloc_type>;
|
||||
using alloc_pointer = typename alloc_traits::pointer;
|
||||
using alloc_const_pointer = typename alloc_traits::const_pointer;
|
||||
|
||||
using bucket_alloc_type = typename std::allocator_traits<Allocator>::template rebind_alloc<alloc_pointer>;
|
||||
using bucket_alloc_traits = std::allocator_traits<bucket_alloc_type>;
|
||||
using bucket_alloc_pointer = typename bucket_alloc_traits::pointer;
|
||||
|
||||
static_assert(alloc_traits::propagate_on_container_move_assignment::value);
|
||||
static_assert(bucket_alloc_traits::propagate_on_container_move_assignment::value);
|
||||
|
||||
class sparse_set_iterator final {
|
||||
friend class basic_sparse_set<Entity>;
|
||||
|
||||
using packed_type = std::vector<Entity>;
|
||||
using index_type = typename traits_type::difference_type;
|
||||
|
||||
sparse_set_iterator(const packed_type &ref, const index_type idx) ENTT_NOEXCEPT
|
||||
: packed{&ref}, index{idx}
|
||||
sparse_set_iterator(const alloc_const_pointer *ref, const index_type idx) ENTT_NOEXCEPT
|
||||
: packed{ref}, index{idx}
|
||||
{}
|
||||
|
||||
public:
|
||||
@@ -144,32 +155,107 @@ class basic_sparse_set {
|
||||
}
|
||||
|
||||
private:
|
||||
const packed_type *packed;
|
||||
const alloc_const_pointer *packed;
|
||||
index_type index;
|
||||
};
|
||||
|
||||
[[nodiscard]] auto page(const Entity entt) const ENTT_NOEXCEPT {
|
||||
[[nodiscard]] static auto page(const Entity entt) ENTT_NOEXCEPT {
|
||||
return size_type{(to_integral(entt) & traits_type::entity_mask) / page_size};
|
||||
}
|
||||
|
||||
[[nodiscard]] auto offset(const Entity entt) const ENTT_NOEXCEPT {
|
||||
[[nodiscard]] static auto offset(const Entity entt) ENTT_NOEXCEPT {
|
||||
return size_type{to_integral(entt) & (page_size - 1)};
|
||||
}
|
||||
|
||||
[[nodiscard]] page_type & assure(const std::size_t pos) {
|
||||
if(!(pos < sparse.size())) {
|
||||
sparse.resize(pos+1);
|
||||
}
|
||||
[[nodiscard]] auto assure_page(const std::size_t idx) {
|
||||
if(!(idx < bucket)) {
|
||||
const size_type sz = idx + 1u;
|
||||
const auto old = std::exchange(sparse, bucket_alloc_traits::allocate(bucket_allocator, sz));
|
||||
std::uninitialized_fill(sparse + bucket, sparse + sz, alloc_pointer{});
|
||||
|
||||
if(!sparse[pos]) {
|
||||
sparse[pos].reset(new entity_type[page_size]);
|
||||
// null is safe in all cases for our purposes
|
||||
for(auto *first = sparse[pos].get(), *last = first + page_size; first != last; ++first) {
|
||||
*first = null;
|
||||
if(const auto curr = std::exchange(bucket, sz); curr) {
|
||||
for(size_type pos{}; pos < curr; ++pos) {
|
||||
bucket_alloc_traits::construct(bucket_allocator, std::addressof(sparse[pos]), std::move(old[pos]));
|
||||
bucket_alloc_traits::destroy(bucket_allocator, std::addressof(old[pos]));
|
||||
}
|
||||
|
||||
bucket_alloc_traits::deallocate(bucket_allocator, old, curr);
|
||||
}
|
||||
}
|
||||
|
||||
return sparse[pos];
|
||||
if(!sparse[idx]) {
|
||||
sparse[idx] = alloc_traits::allocate(allocator, page_size);
|
||||
std::uninitialized_fill(sparse[idx], sparse[idx] + page_size, null);
|
||||
}
|
||||
|
||||
return sparse[idx];
|
||||
}
|
||||
|
||||
void resize_packed(const std::size_t req) {
|
||||
ENTT_ASSERT(req && !(req < count), "Invalid request");
|
||||
auto old = std::exchange(packed, alloc_traits::allocate(allocator, req));
|
||||
|
||||
if(const auto length = std::exchange(reserved, req); length) {
|
||||
for(size_type pos{}; pos < count; ++pos) {
|
||||
alloc_traits::construct(allocator, std::addressof(packed[pos]), std::move(old[pos]));
|
||||
alloc_traits::destroy(allocator, std::addressof(old[pos]));
|
||||
}
|
||||
|
||||
alloc_traits::deallocate(allocator, old, length);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename It>
|
||||
void push_back(It first, It last) {
|
||||
if(const std::size_t req = count + std::distance(first, last); reserved < req) {
|
||||
const std::size_t sz = reserved * growth_factor + !reserved;
|
||||
resize_packed(sz < req ? req : sz);
|
||||
}
|
||||
|
||||
for(; first != last; ++first) {
|
||||
ENTT_ASSERT(!contains(*first), "Set already contains entity");
|
||||
assure_page(page(*first))[offset(*first)] = entity_type{static_cast<typename traits_type::entity_type>(count)};
|
||||
alloc_traits::construct(allocator, std::addressof(packed[count++]), *first);
|
||||
}
|
||||
}
|
||||
|
||||
void pop(const Entity entt, void *ud) {
|
||||
ENTT_ASSERT(contains(entt), "Set does not contain entity");
|
||||
// last chance to use the entity for derived classes and mixins, if any
|
||||
about_to_erase(entt, ud);
|
||||
|
||||
auto &ref = sparse[page(entt)][offset(entt)];
|
||||
const auto pos = size_type{to_integral(ref)};
|
||||
|
||||
const auto last = --count;
|
||||
packed[pos] = std::exchange(packed[last], entt);
|
||||
sparse[page(packed[pos])][offset(packed[pos])] = ref;
|
||||
// no risks when pos == count, accessing packed is no longer required
|
||||
alloc_traits::destroy(allocator, std::addressof(packed[last]));
|
||||
ref = null;
|
||||
|
||||
// don't expect exceptions here, instead allow for nosy destructors
|
||||
swap_and_pop(pos);
|
||||
}
|
||||
|
||||
void reset_to_empty() {
|
||||
if(const auto length = std::exchange(reserved, 0u); length) {
|
||||
std::destroy(packed, packed + std::exchange(count, 0u));
|
||||
alloc_traits::deallocate(allocator, packed, length);
|
||||
}
|
||||
|
||||
if(const auto length = std::exchange(bucket, 0u); length) {
|
||||
for(size_type pos{}; pos < length; ++pos) {
|
||||
if(sparse[pos]) {
|
||||
std::destroy(sparse[pos], sparse[pos] + page_size);
|
||||
alloc_traits::deallocate(allocator, sparse[pos], page_size);
|
||||
}
|
||||
|
||||
bucket_alloc_traits::destroy(bucket_allocator, std::addressof(sparse[pos]));
|
||||
}
|
||||
|
||||
bucket_alloc_traits::deallocate(bucket_allocator, sparse, std::exchange(bucket, 0u));
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
@@ -194,26 +280,67 @@ protected:
|
||||
virtual void about_to_erase([[maybe_unused]] const Entity entity, [[maybe_unused]] void *ud) {}
|
||||
|
||||
public:
|
||||
/*! @brief Allocator type. */
|
||||
using allocator_type = alloc_type;
|
||||
/*! @brief Underlying entity identifier. */
|
||||
using entity_type = Entity;
|
||||
/*! @brief Unsigned integer type. */
|
||||
using size_type = std::size_t;
|
||||
/*! @brief Pointer type to contained entities. */
|
||||
using pointer = alloc_const_pointer;
|
||||
/*! @brief Random access iterator type. */
|
||||
using iterator = sparse_set_iterator;
|
||||
/*! @brief Reverse iterator type. */
|
||||
using reverse_iterator = const entity_type *;
|
||||
using reverse_iterator = pointer;
|
||||
|
||||
/*! @brief Default constructor. */
|
||||
basic_sparse_set() = default;
|
||||
/**
|
||||
* @brief Default constructor.
|
||||
* @param alloc Allocator to use (possibly default-constructed).
|
||||
*/
|
||||
explicit basic_sparse_set(const allocator_type &alloc = {})
|
||||
: allocator{alloc},
|
||||
bucket_allocator{alloc},
|
||||
sparse{},
|
||||
packed{},
|
||||
bucket{},
|
||||
count{},
|
||||
reserved{}
|
||||
{}
|
||||
|
||||
/*! @brief Default move constructor. */
|
||||
basic_sparse_set(basic_sparse_set &&) = default;
|
||||
/**
|
||||
* @brief Move constructor.
|
||||
* @param other The instance to move from.
|
||||
*/
|
||||
basic_sparse_set(basic_sparse_set &&other) ENTT_NOEXCEPT
|
||||
: allocator{std::move(other.allocator)},
|
||||
bucket_allocator{std::move(other.bucket_allocator)},
|
||||
sparse{std::exchange(other.sparse, bucket_alloc_pointer{})},
|
||||
packed{std::exchange(other.packed, alloc_pointer{})},
|
||||
bucket{std::exchange(other.bucket, 0u)},
|
||||
count{std::exchange(other.count, 0u)},
|
||||
reserved{std::exchange(other.reserved, 0u)}
|
||||
{}
|
||||
|
||||
/*! @brief Default destructor. */
|
||||
virtual ~basic_sparse_set() = default;
|
||||
virtual ~basic_sparse_set() {
|
||||
reset_to_empty();
|
||||
}
|
||||
|
||||
/*! @brief Default move assignment operator. @return This sparse set. */
|
||||
basic_sparse_set & operator=(basic_sparse_set &&) = default;
|
||||
/**
|
||||
* @brief Move assignment operator.
|
||||
* @param other The instance to move from.
|
||||
* @return This sparse set.
|
||||
*/
|
||||
basic_sparse_set & operator=(basic_sparse_set &&other) ENTT_NOEXCEPT {
|
||||
allocator = std::move(other.allocator);
|
||||
bucket_allocator = std::move(other.bucket_allocator);
|
||||
sparse = std::exchange(other.sparse, bucket_alloc_pointer{});
|
||||
packed = std::exchange(other.packed, alloc_pointer{});
|
||||
bucket = std::exchange(other.bucket, 0u);
|
||||
count = std::exchange(other.count, 0u);
|
||||
reserved = std::exchange(other.reserved, 0u);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Increases the capacity of a sparse set.
|
||||
@@ -224,7 +351,9 @@ public:
|
||||
* @param cap Desired capacity.
|
||||
*/
|
||||
void reserve(const size_type cap) {
|
||||
packed.reserve(cap);
|
||||
if(cap > reserved) {
|
||||
resize_packed(cap);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -233,18 +362,16 @@ public:
|
||||
* @return Capacity of the sparse set.
|
||||
*/
|
||||
[[nodiscard]] size_type capacity() const ENTT_NOEXCEPT {
|
||||
return packed.capacity();
|
||||
return reserved;
|
||||
}
|
||||
|
||||
/*! @brief Requests the removal of unused capacity. */
|
||||
void shrink_to_fit() {
|
||||
// conservative approach
|
||||
if(packed.empty()) {
|
||||
sparse.clear();
|
||||
if(!count) {
|
||||
reset_to_empty();
|
||||
} else if(reserved > count) {
|
||||
resize_packed(count);
|
||||
}
|
||||
|
||||
sparse.shrink_to_fit();
|
||||
packed.shrink_to_fit();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -258,7 +385,7 @@ public:
|
||||
* @return Extent of the sparse set.
|
||||
*/
|
||||
[[nodiscard]] size_type extent() const ENTT_NOEXCEPT {
|
||||
return sparse.size() * page_size;
|
||||
return bucket * page_size;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -272,7 +399,7 @@ public:
|
||||
* @return Number of elements.
|
||||
*/
|
||||
[[nodiscard]] size_type size() const ENTT_NOEXCEPT {
|
||||
return packed.size();
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -280,23 +407,15 @@ public:
|
||||
* @return True if the sparse set is empty, false otherwise.
|
||||
*/
|
||||
[[nodiscard]] bool empty() const ENTT_NOEXCEPT {
|
||||
return packed.empty();
|
||||
return (count == size_type{});
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Direct access to the internal packed array.
|
||||
*
|
||||
* The returned pointer is such that range `[data(), data() + size())` is
|
||||
* always a valid range, even if the container is empty.
|
||||
*
|
||||
* @note
|
||||
* Entities are in the reverse order as returned by the `begin`/`end`
|
||||
* iterators.
|
||||
*
|
||||
* @return A pointer to the internal packed array.
|
||||
*/
|
||||
[[nodiscard]] const entity_type * data() const ENTT_NOEXCEPT {
|
||||
return packed.data();
|
||||
[[nodiscard]] pointer data() const ENTT_NOEXCEPT {
|
||||
return packed;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -309,8 +428,7 @@ public:
|
||||
* @return An iterator to the first entity of the internal packed array.
|
||||
*/
|
||||
[[nodiscard]] iterator begin() const ENTT_NOEXCEPT {
|
||||
const typename traits_type::difference_type pos = packed.size();
|
||||
return iterator{packed, pos};
|
||||
return iterator{&packed, static_cast<typename traits_type::difference_type>(count)};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -324,7 +442,7 @@ public:
|
||||
* internal packed array.
|
||||
*/
|
||||
[[nodiscard]] iterator end() const ENTT_NOEXCEPT {
|
||||
return iterator{packed, {}};
|
||||
return iterator{&packed, {}};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -338,7 +456,7 @@ public:
|
||||
* array.
|
||||
*/
|
||||
[[nodiscard]] reverse_iterator rbegin() const ENTT_NOEXCEPT {
|
||||
return packed.data();
|
||||
return data();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -352,7 +470,7 @@ public:
|
||||
* reversed internal packed array.
|
||||
*/
|
||||
[[nodiscard]] reverse_iterator rend() const ENTT_NOEXCEPT {
|
||||
return rbegin() + packed.size();
|
||||
return rbegin() + count;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -373,7 +491,7 @@ public:
|
||||
[[nodiscard]] bool contains(const entity_type entt) const {
|
||||
const auto curr = page(entt);
|
||||
// testing against null permits to avoid accessing the packed array
|
||||
return (curr < sparse.size() && sparse[curr] && sparse[curr][offset(entt)] != null);
|
||||
return (curr < bucket && sparse[curr] && sparse[curr][offset(entt)] != null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -397,7 +515,7 @@ public:
|
||||
* @return The entity at specified location if any, a null entity otherwise.
|
||||
*/
|
||||
[[nodiscard]] entity_type at(const size_type pos) const {
|
||||
return pos < packed.size() ? packed[pos] : null;
|
||||
return pos < count ? packed[pos] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -406,7 +524,7 @@ public:
|
||||
* @return The entity at specified location.
|
||||
*/
|
||||
[[nodiscard]] entity_type operator[](const size_type pos) const {
|
||||
ENTT_ASSERT(pos < packed.size(), "Position is out of bounds");
|
||||
ENTT_ASSERT(pos < count, "Position is out of bounds");
|
||||
return packed[pos];
|
||||
}
|
||||
|
||||
@@ -420,9 +538,8 @@ public:
|
||||
* @param entt A valid entity identifier.
|
||||
*/
|
||||
void emplace(const entity_type entt) {
|
||||
ENTT_ASSERT(!contains(entt), "Set already contains entity");
|
||||
assure(page(entt))[offset(entt)] = entity_type{static_cast<typename traits_type::entity_type>(packed.size())};
|
||||
packed.push_back(entt);
|
||||
entity_type arr[1u]{entt};
|
||||
push_back(arr, arr + 1u);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -438,13 +555,7 @@ public:
|
||||
*/
|
||||
template<typename It>
|
||||
void insert(It first, It last) {
|
||||
auto next = static_cast<typename traits_type::entity_type>(packed.size());
|
||||
packed.insert(packed.end(), first, last);
|
||||
|
||||
for(; first != last; ++first) {
|
||||
ENTT_ASSERT(!contains(*first), "Set already contains entity");
|
||||
assure(page(*first))[offset(*first)] = entity_type{next++};
|
||||
}
|
||||
push_back(first, last);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -458,24 +569,7 @@ public:
|
||||
* @param ud Optional user data that are forwarded as-is to derived classes.
|
||||
*/
|
||||
void erase(const entity_type entt, void *ud = nullptr) {
|
||||
ENTT_ASSERT(contains(entt), "Set does not contain entity");
|
||||
|
||||
// last chance to use the entity for derived classes and mixins, if any
|
||||
about_to_erase(entt, ud);
|
||||
|
||||
auto &ref = sparse[page(entt)][offset(entt)];
|
||||
const auto pos = size_type{to_integral(ref)};
|
||||
|
||||
const auto other = packed.back();
|
||||
sparse[page(other)][offset(other)] = ref;
|
||||
ref = null;
|
||||
|
||||
// if it looks weird, imagine what the subtle bugs it prevents are
|
||||
ENTT_ASSERT((packed.back() = entt, true), "");
|
||||
packed[pos] = other;
|
||||
packed.pop_back();
|
||||
|
||||
swap_and_pop(pos);
|
||||
pop(entt, ud);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -488,7 +582,7 @@ public:
|
||||
template<typename It>
|
||||
void erase(It first, It last, void *ud = nullptr) {
|
||||
for(; first != last; ++first) {
|
||||
erase(*first, ud);
|
||||
pop(*first, ud);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -499,7 +593,7 @@ public:
|
||||
* @return True if the entity is actually removed, false otherwise.
|
||||
*/
|
||||
bool remove(const entity_type entt, void *ud = nullptr) {
|
||||
return contains(entt) ? (erase(entt, ud), true) : false;
|
||||
return contains(entt) ? (pop(entt, ud), true) : false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -512,13 +606,13 @@ public:
|
||||
*/
|
||||
template<typename It>
|
||||
size_type remove(It first, It last, void *ud = nullptr) {
|
||||
size_type count{};
|
||||
size_type found{};
|
||||
|
||||
for(; first != last; ++first) {
|
||||
count += remove(*first, ud);
|
||||
found += remove(*first, ud);
|
||||
}
|
||||
|
||||
return count;
|
||||
return found;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -537,9 +631,12 @@ public:
|
||||
void swap(const entity_type lhs, const entity_type rhs) {
|
||||
const auto from = index(lhs);
|
||||
const auto to = index(rhs);
|
||||
|
||||
// derived classes first for a bare-minimum exception safety guarantee
|
||||
swap_at(from, to);
|
||||
|
||||
std::swap(sparse[page(lhs)][offset(lhs)], sparse[page(rhs)][offset(rhs)]);
|
||||
std::swap(packed[from], packed[to]);
|
||||
swap_at(from, to);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -567,16 +664,18 @@ public:
|
||||
* @tparam Compare Type of comparison function object.
|
||||
* @tparam Sort Type of sort function object.
|
||||
* @tparam Args Types of arguments to forward to the sort function object.
|
||||
* @param count Number of elements to sort.
|
||||
* @param length Number of elements to sort.
|
||||
* @param compare A valid comparison function object.
|
||||
* @param algo A valid sort function object.
|
||||
* @param args Arguments to forward to the sort function object, if any.
|
||||
*/
|
||||
template<typename Compare, typename Sort = std_sort, typename... Args>
|
||||
void sort_n(const size_type count, Compare compare, Sort algo = Sort{}, Args &&... args) {
|
||||
algo(packed.rend() - count, packed.rend(), std::move(compare), std::forward<Args>(args)...);
|
||||
void sort_n(const size_type length, Compare compare, Sort algo = Sort{}, Args &&... args) {
|
||||
ENTT_ASSERT(!(length > count), "Length exceeds the number of elements");
|
||||
|
||||
for(size_type pos{}, last = (size() < count ? size() : count); pos < last; ++pos) {
|
||||
algo(std::make_reverse_iterator(packed + length), std::make_reverse_iterator(packed), std::move(compare), std::forward<Args>(args)...);
|
||||
|
||||
for(size_type pos{}; pos < length; ++pos) {
|
||||
auto curr = pos;
|
||||
auto next = index(packed[curr]);
|
||||
|
||||
@@ -607,7 +706,7 @@ public:
|
||||
*/
|
||||
template<typename Compare, typename Sort = std_sort, typename... Args>
|
||||
void sort(Compare compare, Sort algo = Sort{}, Args &&... args) {
|
||||
sort_n(size(), std::move(compare), std::move(algo), std::forward<Args>(args)...);
|
||||
sort_n(count, std::move(compare), std::move(algo), std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -629,7 +728,7 @@ public:
|
||||
const auto to = other.end();
|
||||
auto from = other.begin();
|
||||
|
||||
size_type pos = packed.size() - 1;
|
||||
size_type pos = count - 1;
|
||||
|
||||
while(pos && from != to) {
|
||||
if(contains(*from)) {
|
||||
@@ -653,8 +752,13 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<page_type> sparse;
|
||||
std::vector<entity_type> packed;
|
||||
alloc_type allocator;
|
||||
bucket_alloc_type bucket_allocator;
|
||||
bucket_alloc_pointer sparse;
|
||||
alloc_pointer packed;
|
||||
std::size_t bucket;
|
||||
std::size_t count;
|
||||
std::size_t reserved;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -2,13 +2,12 @@
|
||||
#define ENTT_ENTITY_STORAGE_HPP
|
||||
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include "../config/config.h"
|
||||
#include "../core/algorithm.hpp"
|
||||
#include "../core/type_traits.hpp"
|
||||
@@ -30,9 +29,7 @@ namespace entt {
|
||||
* to the entities.
|
||||
*
|
||||
* @note
|
||||
* Entities and objects have the same order. It's guaranteed both in case of raw
|
||||
* access (either to entities or objects) and when using random or input access
|
||||
* iterators.
|
||||
* Entities and objects have the same order.
|
||||
*
|
||||
* @note
|
||||
* Internal data structures arrange elements to maximize performance. There are
|
||||
@@ -47,27 +44,40 @@ namespace entt {
|
||||
*
|
||||
* @tparam Entity A valid entity type (see entt_traits for more details).
|
||||
* @tparam Type Type of objects assigned to the entities.
|
||||
* @tparam Allocator Type of allocator used to manage memory and elements.
|
||||
*/
|
||||
template<typename Entity, typename Type, typename = void>
|
||||
class basic_storage: public basic_sparse_set<Entity> {
|
||||
template<typename Entity, typename Type, typename Allocator, typename>
|
||||
class basic_storage: public basic_sparse_set<Entity, typename std::allocator_traits<Allocator>::template rebind_alloc<Entity>> {
|
||||
static_assert(std::is_move_constructible_v<Type> && std::is_move_assignable_v<Type>, "The managed type must be at least move constructible and assignable");
|
||||
|
||||
static constexpr auto growth_factor = 1.5;
|
||||
static constexpr auto page_size = ENTT_PAGE_SIZE;
|
||||
|
||||
using underlying_type = basic_sparse_set<Entity>;
|
||||
using traits_type = entt_traits<Entity>;
|
||||
|
||||
using alloc_type = typename std::allocator_traits<Allocator>::template rebind_alloc<Type>;
|
||||
using alloc_traits = std::allocator_traits<alloc_type>;
|
||||
using alloc_pointer = typename alloc_traits::pointer;
|
||||
|
||||
using bucket_alloc_type = typename std::allocator_traits<Allocator>::template rebind_alloc<alloc_pointer>;
|
||||
using bucket_alloc_traits = std::allocator_traits<bucket_alloc_type>;
|
||||
using bucket_alloc_pointer = typename bucket_alloc_traits::pointer;
|
||||
using bucket_alloc_const_pointer = typename bucket_alloc_traits::const_pointer;
|
||||
|
||||
static_assert(alloc_traits::propagate_on_container_move_assignment::value);
|
||||
static_assert(bucket_alloc_traits::propagate_on_container_move_assignment::value);
|
||||
|
||||
template<typename Value>
|
||||
class storage_iterator final {
|
||||
friend class basic_storage<Entity, Type>;
|
||||
|
||||
using instance_type = constness_as_t<std::vector<Type>, Value>;
|
||||
using index_type = typename traits_type::difference_type;
|
||||
|
||||
storage_iterator(instance_type &ref, const index_type idx) ENTT_NOEXCEPT
|
||||
: instances{&ref}, index{idx}
|
||||
storage_iterator(const bucket_alloc_const_pointer *ref, const typename traits_type::difference_type idx) ENTT_NOEXCEPT
|
||||
: packed{ref}, index{idx}
|
||||
{}
|
||||
|
||||
public:
|
||||
using difference_type = index_type;
|
||||
using difference_type = typename traits_type::difference_type;
|
||||
using value_type = Value;
|
||||
using pointer = value_type *;
|
||||
using reference = value_type &;
|
||||
@@ -117,7 +127,7 @@ class basic_storage: public basic_sparse_set<Entity> {
|
||||
|
||||
[[nodiscard]] reference operator[](const difference_type value) const ENTT_NOEXCEPT {
|
||||
const auto pos = size_type(index-value-1);
|
||||
return (*instances)[pos];
|
||||
return (*packed)[page(pos)][offset(pos)];
|
||||
}
|
||||
|
||||
[[nodiscard]] bool operator==(const storage_iterator &other) const ENTT_NOEXCEPT {
|
||||
@@ -146,7 +156,7 @@ class basic_storage: public basic_sparse_set<Entity> {
|
||||
|
||||
[[nodiscard]] pointer operator->() const ENTT_NOEXCEPT {
|
||||
const auto pos = size_type(index-1u);
|
||||
return &(*instances)[pos];
|
||||
return &(*packed)[page(pos)][offset(pos)];
|
||||
}
|
||||
|
||||
[[nodiscard]] reference operator*() const ENTT_NOEXCEPT {
|
||||
@@ -154,25 +164,104 @@ class basic_storage: public basic_sparse_set<Entity> {
|
||||
}
|
||||
|
||||
private:
|
||||
instance_type *instances;
|
||||
index_type index;
|
||||
const bucket_alloc_const_pointer *packed;
|
||||
difference_type index;
|
||||
};
|
||||
|
||||
[[nodiscard]] static auto page(const std::size_t pos) ENTT_NOEXCEPT {
|
||||
return pos / page_size;
|
||||
}
|
||||
|
||||
[[nodiscard]] static auto offset(const std::size_t pos) ENTT_NOEXCEPT {
|
||||
return pos & (page_size - 1);
|
||||
}
|
||||
|
||||
void maybe_resize_packed(const std::size_t req) {
|
||||
ENTT_ASSERT(req && !(req < count), "Invalid request");
|
||||
if(const auto length = std::exchange(bucket, (req + page_size - 1u) / page_size); bucket != length) {
|
||||
const auto old = std::exchange(packed, bucket_alloc_traits::allocate(bucket_allocator, bucket));
|
||||
|
||||
if(bucket > length) {
|
||||
if(length) {
|
||||
for(size_type pos{}; pos < length; ++pos) {
|
||||
bucket_alloc_traits::construct(bucket_allocator, std::addressof(packed[pos]), old[pos]);
|
||||
bucket_alloc_traits::destroy(bucket_allocator, std::addressof(old[pos]));
|
||||
}
|
||||
|
||||
bucket_alloc_traits::deallocate(bucket_allocator, old, length);
|
||||
}
|
||||
|
||||
for(auto pos = length; pos < bucket; ++pos) {
|
||||
bucket_alloc_traits::construct(bucket_allocator, std::addressof(packed[pos]), alloc_traits::allocate(allocator, page_size));
|
||||
}
|
||||
} else if(bucket < length) {
|
||||
for(size_type pos{}; pos < bucket; ++pos) {
|
||||
bucket_alloc_traits::construct(bucket_allocator, std::addressof(packed[pos]), old[pos]);
|
||||
bucket_alloc_traits::destroy(bucket_allocator, std::addressof(old[pos]));
|
||||
}
|
||||
|
||||
for(auto pos = bucket; pos < length; ++pos) {
|
||||
alloc_traits::deallocate(allocator, old[pos], page_size);
|
||||
bucket_alloc_traits::destroy(bucket_allocator, std::addressof(old[pos]));
|
||||
}
|
||||
|
||||
bucket_alloc_traits::deallocate(bucket_allocator, old, length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
Type & push_back(Args &&... args) {
|
||||
ENTT_ASSERT(count != (bucket * page_size), "No more space left");
|
||||
|
||||
if constexpr(std::is_aggregate_v<value_type>) {
|
||||
alloc_traits::construct(allocator, std::addressof(*(packed[page(count)] + offset(count))), Type{std::forward<Args>(args)...});
|
||||
} else {
|
||||
alloc_traits::construct(allocator, std::addressof(*(packed[page(count)] + offset(count))), std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
const auto last = count++;
|
||||
return packed[page(last)][offset(last)];
|
||||
}
|
||||
|
||||
void reset_to_empty() {
|
||||
if(const auto length = std::exchange(bucket, 0u); length) {
|
||||
for(size_type pos{}; pos < length; ++pos) {
|
||||
if(count) {
|
||||
const auto sz = count > page_size ? page_size : count;
|
||||
std::destroy(packed[pos], packed[pos] + sz);
|
||||
count -= sz;
|
||||
}
|
||||
|
||||
alloc_traits::deallocate(allocator, packed[pos], page_size);
|
||||
bucket_alloc_traits::destroy(bucket_allocator, std::addressof(packed[pos]));
|
||||
}
|
||||
|
||||
bucket_alloc_traits::deallocate(bucket_allocator, packed, length);
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
/*! @copydoc basic_sparse_set::swap_at */
|
||||
void swap_at(const std::size_t lhs, const std::size_t rhs) final {
|
||||
std::swap(instances[lhs], instances[rhs]);
|
||||
std::swap(packed[page(lhs)][offset(lhs)], packed[page(rhs)][offset(rhs)]);
|
||||
}
|
||||
|
||||
/*! @copydoc basic_sparse_set::swap_and_pop */
|
||||
void swap_and_pop(const std::size_t pos) final {
|
||||
// required because pop_back isn't guaranteed to shrink before removing the element
|
||||
[[maybe_unused]] auto other = std::move(instances[pos]);
|
||||
instances[pos] = std::move(instances.back());
|
||||
instances.pop_back();
|
||||
auto &&elem = packed[page(pos)][offset(pos)];
|
||||
[[maybe_unused]] auto other = std::move(elem);
|
||||
|
||||
auto &&last = packed[page(count - 1)][offset(count - 1)];
|
||||
elem = std::move(last);
|
||||
alloc_traits::destroy(allocator, std::addressof(last));
|
||||
|
||||
--count;
|
||||
}
|
||||
|
||||
public:
|
||||
/*! @brief Allocator type. */
|
||||
using allocator_type = alloc_type;
|
||||
/*! @brief Type of the objects assigned to entities. */
|
||||
using value_type = Type;
|
||||
/*! @brief Underlying entity identifier. */
|
||||
@@ -184,9 +273,52 @@ public:
|
||||
/*! @brief Constant random access iterator type. */
|
||||
using const_iterator = storage_iterator<const Type>;
|
||||
/*! @brief Reverse iterator type. */
|
||||
using reverse_iterator = Type *;
|
||||
using reverse_iterator = std::reverse_iterator<iterator>;
|
||||
/*! @brief Constant reverse iterator type. */
|
||||
using const_reverse_iterator = const Type *;
|
||||
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
|
||||
|
||||
/**
|
||||
* @brief Default constructor.
|
||||
* @param alloc Allocator to use (possibly default-constructed).
|
||||
*/
|
||||
explicit basic_storage(const allocator_type &alloc = {})
|
||||
: allocator{alloc},
|
||||
bucket_allocator{alloc},
|
||||
packed{},
|
||||
bucket{},
|
||||
count{}
|
||||
{}
|
||||
|
||||
/**
|
||||
* @brief Move constructor.
|
||||
* @param other The instance to move from.
|
||||
*/
|
||||
basic_storage(basic_storage &&other) ENTT_NOEXCEPT
|
||||
: allocator{std::move(other.allocator)},
|
||||
bucket_allocator{std::move(other.bucket_allocator)},
|
||||
packed{std::exchange(other.packed, bucket_alloc_pointer{})},
|
||||
bucket{std::exchange(other.bucket, 0u)},
|
||||
count{std::exchange(other.count, 0u)}
|
||||
{}
|
||||
|
||||
/*! @brief Default destructor. */
|
||||
~basic_storage() override {
|
||||
reset_to_empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Move assignment operator.
|
||||
* @param other The instance to move from.
|
||||
* @return This sparse set.
|
||||
*/
|
||||
basic_storage & operator=(basic_storage &&other) ENTT_NOEXCEPT {
|
||||
allocator = std::move(other.allocator);
|
||||
bucket_allocator = std::move(other.bucket_allocator);
|
||||
packed = std::exchange(other.packed, bucket_alloc_pointer{});
|
||||
bucket = std::exchange(other.bucket, 0u);
|
||||
count = std::exchange(other.count, 0u);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Increases the capacity of a storage.
|
||||
@@ -198,34 +330,25 @@ public:
|
||||
*/
|
||||
void reserve(const size_type cap) {
|
||||
underlying_type::reserve(cap);
|
||||
instances.reserve(cap);
|
||||
|
||||
if(cap > (bucket * page_size)) {
|
||||
maybe_resize_packed(cap);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the number of elements that a storage has currently
|
||||
* allocated space for.
|
||||
* @return Capacity of the storage.
|
||||
*/
|
||||
[[nodiscard]] size_type capacity() const ENTT_NOEXCEPT {
|
||||
return bucket * page_size;
|
||||
}
|
||||
|
||||
/*! @brief Requests the removal of unused capacity. */
|
||||
void shrink_to_fit() {
|
||||
underlying_type::shrink_to_fit();
|
||||
instances.shrink_to_fit();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Direct access to the array of objects.
|
||||
*
|
||||
* The returned pointer is such that range `[raw(), raw() + size())` is
|
||||
* always a valid range, even if the container is empty.
|
||||
*
|
||||
* @note
|
||||
* Objects are in the reverse order as returned by the `begin`/`end`
|
||||
* iterators.
|
||||
*
|
||||
* @return A pointer to the array of objects.
|
||||
*/
|
||||
[[nodiscard]] const value_type * raw() const ENTT_NOEXCEPT {
|
||||
return instances.data();
|
||||
}
|
||||
|
||||
/*! @copydoc raw */
|
||||
[[nodiscard]] value_type * raw() ENTT_NOEXCEPT {
|
||||
return const_cast<value_type *>(std::as_const(*this).raw());
|
||||
count ? maybe_resize_packed(count) : reset_to_empty();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -238,7 +361,7 @@ public:
|
||||
*/
|
||||
[[nodiscard]] const_iterator cbegin() const ENTT_NOEXCEPT {
|
||||
const typename traits_type::difference_type pos = underlying_type::size();
|
||||
return const_iterator{instances, pos};
|
||||
return const_iterator{&packed, pos};
|
||||
}
|
||||
|
||||
/*! @copydoc cbegin */
|
||||
@@ -249,7 +372,7 @@ public:
|
||||
/*! @copydoc begin */
|
||||
[[nodiscard]] iterator begin() ENTT_NOEXCEPT {
|
||||
const typename traits_type::difference_type pos = underlying_type::size();
|
||||
return iterator{instances, pos};
|
||||
return iterator{&packed, pos};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -263,7 +386,7 @@ public:
|
||||
* internal array.
|
||||
*/
|
||||
[[nodiscard]] const_iterator cend() const ENTT_NOEXCEPT {
|
||||
return const_iterator{instances, {}};
|
||||
return const_iterator{&packed, {}};
|
||||
}
|
||||
|
||||
/*! @copydoc cend */
|
||||
@@ -273,7 +396,7 @@ public:
|
||||
|
||||
/*! @copydoc end */
|
||||
[[nodiscard]] iterator end() ENTT_NOEXCEPT {
|
||||
return iterator{instances, {}};
|
||||
return iterator{&packed, {}};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -286,7 +409,7 @@ public:
|
||||
* @return An iterator to the first instance of the reversed internal array.
|
||||
*/
|
||||
[[nodiscard]] const_reverse_iterator crbegin() const ENTT_NOEXCEPT {
|
||||
return instances.data();
|
||||
return std::make_reverse_iterator(cend());
|
||||
}
|
||||
|
||||
/*! @copydoc crbegin */
|
||||
@@ -296,7 +419,7 @@ public:
|
||||
|
||||
/*! @copydoc rbegin */
|
||||
[[nodiscard]] reverse_iterator rbegin() ENTT_NOEXCEPT {
|
||||
return instances.data();
|
||||
return std::make_reverse_iterator(end());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -310,7 +433,7 @@ public:
|
||||
* reversed internal array.
|
||||
*/
|
||||
[[nodiscard]] const_reverse_iterator crend() const ENTT_NOEXCEPT {
|
||||
return crbegin() + instances.size();
|
||||
return std::make_reverse_iterator(cbegin());
|
||||
}
|
||||
|
||||
/*! @copydoc crend */
|
||||
@@ -320,7 +443,7 @@ public:
|
||||
|
||||
/*! @copydoc rend */
|
||||
[[nodiscard]] reverse_iterator rend() ENTT_NOEXCEPT {
|
||||
return rbegin() + instances.size();
|
||||
return std::make_reverse_iterator(begin());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -334,7 +457,8 @@ public:
|
||||
* @return The object assigned to the entity.
|
||||
*/
|
||||
[[nodiscard]] const value_type & get(const entity_type entt) const {
|
||||
return instances[underlying_type::index(entt)];
|
||||
const auto idx = underlying_type::index(entt);
|
||||
return packed[page(idx)][offset(idx)];
|
||||
}
|
||||
|
||||
/*! @copydoc get */
|
||||
@@ -360,15 +484,11 @@ public:
|
||||
*/
|
||||
template<typename... Args>
|
||||
value_type & emplace(const entity_type entt, Args &&... args) {
|
||||
if constexpr(std::is_aggregate_v<value_type>) {
|
||||
instances.push_back(Type{std::forward<Args>(args)...});
|
||||
} else {
|
||||
instances.emplace_back(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
maybe_resize_packed(count + 1u);
|
||||
auto &value = push_back(std::forward<Args>(args)...);
|
||||
// entity goes after component in case constructor throws
|
||||
underlying_type::emplace(entt);
|
||||
return instances.back();
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -380,9 +500,10 @@ public:
|
||||
*/
|
||||
template<typename... Func>
|
||||
decltype(auto) patch(const entity_type entity, Func &&... func) {
|
||||
auto &&instance = instances[this->index(entity)];
|
||||
(std::forward<Func>(func)(instance), ...);
|
||||
return instance;
|
||||
const auto idx = underlying_type::index(entity);
|
||||
auto &&elem = packed[page(idx)][offset(idx)];
|
||||
(std::forward<Func>(func)(elem), ...);
|
||||
return elem;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -400,9 +521,15 @@ public:
|
||||
*/
|
||||
template<typename It>
|
||||
void insert(It first, It last, const value_type &value = {}) {
|
||||
instances.insert(instances.end(), std::distance(first, last), value);
|
||||
// entities go after components in case constructors throw
|
||||
underlying_type::insert(first, last);
|
||||
if(const auto length = std::distance(first, last); length) {
|
||||
maybe_resize_packed(count + length);
|
||||
|
||||
for(auto pos = length; pos; --pos) {
|
||||
push_back(value);
|
||||
}
|
||||
|
||||
underlying_type::insert(first, last);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -420,9 +547,15 @@ public:
|
||||
*/
|
||||
template<typename EIt, typename CIt>
|
||||
void insert(EIt first, EIt last, CIt from, CIt to) {
|
||||
instances.insert(instances.end(), from, to);
|
||||
// entities go after components in case constructors throw
|
||||
underlying_type::insert(first, last);
|
||||
if(const auto length = std::distance(from, to); length) {
|
||||
maybe_resize_packed(count + length);
|
||||
|
||||
for(; from != to; ++from) {
|
||||
push_back(*from);
|
||||
}
|
||||
|
||||
underlying_type::insert(first, last);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -455,19 +588,20 @@ public:
|
||||
* @tparam Compare Type of comparison function object.
|
||||
* @tparam Sort Type of sort function object.
|
||||
* @tparam Args Types of arguments to forward to the sort function object.
|
||||
* @param count Number of elements to sort.
|
||||
* @param length Number of elements to sort.
|
||||
* @param compare A valid comparison function object.
|
||||
* @param algo A valid sort function object.
|
||||
* @param args Arguments to forward to the sort function object, if any.
|
||||
*/
|
||||
template<typename Compare, typename Sort = std_sort, typename... Args>
|
||||
void sort_n(const size_type count, Compare compare, Sort algo = Sort{}, Args &&... args) {
|
||||
void sort_n(const size_type length, Compare compare, Sort algo = Sort{}, Args &&... args) {
|
||||
if constexpr(std::is_invocable_v<Compare, const value_type &, const value_type &>) {
|
||||
underlying_type::sort_n(count, [this, compare = std::move(compare)](const auto lhs, const auto rhs) {
|
||||
return compare(std::as_const(instances[underlying_type::index(lhs)]), std::as_const(instances[underlying_type::index(rhs)]));
|
||||
underlying_type::sort_n(length, [this, compare = std::move(compare)](const auto lhs, const auto rhs) {
|
||||
const auto ilhs = underlying_type::index(lhs), irhs = underlying_type::index(rhs);
|
||||
return compare(std::as_const(packed[page(ilhs)][offset(ilhs)]), std::as_const(packed[page(irhs)][offset(irhs)]));
|
||||
}, std::move(algo), std::forward<Args>(args)...);
|
||||
} else {
|
||||
underlying_type::sort_n(count, std::move(compare), std::move(algo), std::forward<Args>(args)...);
|
||||
underlying_type::sort_n(length, std::move(compare), std::move(algo), std::forward<Args>(args)...);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -485,17 +619,21 @@ public:
|
||||
*/
|
||||
template<typename Compare, typename Sort = std_sort, typename... Args>
|
||||
void sort(Compare compare, Sort algo = Sort{}, Args &&... args) {
|
||||
sort_n(this->size(), std::move(compare), std::move(algo), std::forward<Args>(args)...);
|
||||
sort_n(underlying_type::size(), std::move(compare), std::move(algo), std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<value_type> instances;
|
||||
alloc_type allocator;
|
||||
bucket_alloc_type bucket_allocator;
|
||||
bucket_alloc_pointer packed;
|
||||
std::size_t bucket;
|
||||
std::size_t count;
|
||||
};
|
||||
|
||||
|
||||
/*! @copydoc basic_storage */
|
||||
template<typename Entity, typename Type>
|
||||
class basic_storage<Entity, Type, std::enable_if_t<is_empty_v<Type>>>: public basic_sparse_set<Entity> {
|
||||
template<typename Entity, typename Type, typename Allocator>
|
||||
class basic_storage<Entity, Type, Allocator, std::enable_if_t<is_empty_v<Type>>>: public basic_sparse_set<Entity, typename std::allocator_traits<Allocator>::template rebind_alloc<Entity>> {
|
||||
using underlying_type = basic_sparse_set<Entity>;
|
||||
|
||||
public:
|
||||
@@ -516,7 +654,7 @@ public:
|
||||
* @param entt A valid entity identifier.
|
||||
*/
|
||||
void get([[maybe_unused]] const entity_type entt) const {
|
||||
ENTT_ASSERT(this->contains(entt), "Storage does not contain entity");
|
||||
ENTT_ASSERT(underlying_type::contains(entt), "Storage does not contain entity");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -544,7 +682,7 @@ public:
|
||||
*/
|
||||
template<typename... Func>
|
||||
void patch([[maybe_unused]] const entity_type entity, Func &&... func) {
|
||||
ENTT_ASSERT(this->contains(entity), "Storage does not contain entity");
|
||||
ENTT_ASSERT(underlying_type::contains(entity), "Storage does not contain entity");
|
||||
(std::forward<Func>(func)(), ...);
|
||||
}
|
||||
|
||||
@@ -579,6 +717,9 @@ struct storage_adapter_mixin: Type {
|
||||
/*! @brief Underlying entity identifier. */
|
||||
using entity_type = typename Type::entity_type;
|
||||
|
||||
/*! @brief Inherited constructors. */
|
||||
using Type::Type;
|
||||
|
||||
/**
|
||||
* @brief Assigns entities to a storage.
|
||||
* @tparam Args Types of arguments to use to construct the object.
|
||||
@@ -639,6 +780,9 @@ public:
|
||||
/*! @brief Underlying entity identifier. */
|
||||
using entity_type = typename Type::entity_type;
|
||||
|
||||
/*! @brief Inherited constructors. */
|
||||
using Type::Type;
|
||||
|
||||
/**
|
||||
* @brief Returns a sink object.
|
||||
*
|
||||
|
||||
@@ -708,19 +708,6 @@ public:
|
||||
return std::get<0>(pools)->empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Direct access to the raw representation offered by the storage.
|
||||
*
|
||||
* For fully contiguous storage classes, the returned pointer is such that
|
||||
* range `[raw<Component>(), raw<Component>() + size())` is always a valid
|
||||
* range, even if the container is empty.
|
||||
*
|
||||
* @return A pointer to the array of components.
|
||||
*/
|
||||
[[nodiscard]] auto raw() const ENTT_NOEXCEPT {
|
||||
return std::get<0>(pools)->raw();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Direct access to the list of entities.
|
||||
*
|
||||
|
||||
@@ -665,8 +665,8 @@ TEST(OwningGroup, Functionalities) {
|
||||
|
||||
ASSERT_EQ(group.size(), 1u);
|
||||
|
||||
ASSERT_EQ(*(cgroup.raw<const int>() + 0), 42);
|
||||
ASSERT_EQ(*(group.raw<int>() + 0), 42);
|
||||
ASSERT_EQ(*(cgroup.rbegin() + 0), e1);
|
||||
ASSERT_EQ(*(group.rbegin() + 0), e1);
|
||||
|
||||
for(auto entity: group) {
|
||||
ASSERT_EQ(std::get<0>(cgroup.get<const int, const char>(entity)), 42);
|
||||
@@ -675,7 +675,6 @@ TEST(OwningGroup, Functionalities) {
|
||||
}
|
||||
|
||||
ASSERT_EQ(*(group.data() + 0), e1);
|
||||
ASSERT_EQ(*(group.raw<int>() + 0), 42);
|
||||
|
||||
registry.erase<char>(e0);
|
||||
registry.erase<char>(e1);
|
||||
@@ -706,7 +705,6 @@ TEST(OwningGroup, Invalid) {
|
||||
ASSERT_TRUE(group.empty());
|
||||
ASSERT_EQ(group.size(), 0u);
|
||||
|
||||
ASSERT_EQ(group.raw<const int>(), nullptr);
|
||||
ASSERT_EQ(group.data(), nullptr);
|
||||
|
||||
ASSERT_EQ(group.begin(), group.end());
|
||||
@@ -844,18 +842,14 @@ TEST(OwningGroup, SortOrdered) {
|
||||
ASSERT_EQ(*(group.data() + 0u), entities[0]);
|
||||
ASSERT_EQ(*(group.data() + 1u), entities[1]);
|
||||
ASSERT_EQ(*(group.data() + 2u), entities[2]);
|
||||
ASSERT_EQ(*(group.data() + 3u), entities[3]);
|
||||
ASSERT_EQ(*(group.data() + 4u), entities[4]);
|
||||
|
||||
ASSERT_EQ((group.raw<boxed_int>() + 0u)->value, 12);
|
||||
ASSERT_EQ((group.raw<boxed_int>() + 1u)->value, 9);
|
||||
ASSERT_EQ((group.raw<boxed_int>() + 2u)->value, 6);
|
||||
ASSERT_EQ((group.raw<boxed_int>() + 3u)->value, 1);
|
||||
ASSERT_EQ((group.raw<boxed_int>() + 4u)->value, 2);
|
||||
ASSERT_EQ(group.get<boxed_int>(*(group.data() + 0u)).value, 12);
|
||||
ASSERT_EQ(group.get<boxed_int>(*(group.data() + 1u)).value, 9);
|
||||
ASSERT_EQ(group.get<boxed_int>(*(group.data() + 2u)).value, 6);
|
||||
|
||||
ASSERT_EQ(*(group.raw<char>() + 0u), 'a');
|
||||
ASSERT_EQ(*(group.raw<char>() + 1u), 'b');
|
||||
ASSERT_EQ(*(group.raw<char>() + 2u), 'c');
|
||||
ASSERT_EQ(group.get<char>(*(group.data() + 0u)), 'a');
|
||||
ASSERT_EQ(group.get<char>(*(group.data() + 1u)), 'b');
|
||||
ASSERT_EQ(group.get<char>(*(group.data() + 2u)), 'c');
|
||||
|
||||
ASSERT_EQ((group.get<boxed_int, char>(entities[0])), (std::make_tuple(boxed_int{12}, 'a')));
|
||||
ASSERT_EQ((group.get<boxed_int, char>(entities[1])), (std::make_tuple(boxed_int{9}, 'b')));
|
||||
@@ -891,18 +885,14 @@ TEST(OwningGroup, SortReverse) {
|
||||
ASSERT_EQ(*(group.data() + 0u), entities[2]);
|
||||
ASSERT_EQ(*(group.data() + 1u), entities[1]);
|
||||
ASSERT_EQ(*(group.data() + 2u), entities[0]);
|
||||
ASSERT_EQ(*(group.data() + 3u), entities[3]);
|
||||
ASSERT_EQ(*(group.data() + 4u), entities[4]);
|
||||
|
||||
ASSERT_EQ((group.raw<boxed_int>() + 0u)->value, 12);
|
||||
ASSERT_EQ((group.raw<boxed_int>() + 1u)->value, 9);
|
||||
ASSERT_EQ((group.raw<boxed_int>() + 2u)->value, 6);
|
||||
ASSERT_EQ((group.raw<boxed_int>() + 3u)->value, 1);
|
||||
ASSERT_EQ((group.raw<boxed_int>() + 4u)->value, 2);
|
||||
ASSERT_EQ(group.get<boxed_int>(*(group.data() + 0u)).value, 12);
|
||||
ASSERT_EQ(group.get<boxed_int>(*(group.data() + 1u)).value, 9);
|
||||
ASSERT_EQ(group.get<boxed_int>(*(group.data() + 2u)).value, 6);
|
||||
|
||||
ASSERT_EQ(*(group.raw<char>() + 0u), 'c');
|
||||
ASSERT_EQ(*(group.raw<char>() + 1u), 'b');
|
||||
ASSERT_EQ(*(group.raw<char>() + 2u), 'a');
|
||||
ASSERT_EQ(group.get<char>(*(group.data() + 0u)), 'c');
|
||||
ASSERT_EQ(group.get<char>(*(group.data() + 1u)), 'b');
|
||||
ASSERT_EQ(group.get<char>(*(group.data() + 2u)), 'a');
|
||||
|
||||
ASSERT_EQ((group.get<boxed_int, char>(entities[0])), (std::make_tuple(boxed_int{6}, 'a')));
|
||||
ASSERT_EQ((group.get<boxed_int, char>(entities[1])), (std::make_tuple(boxed_int{9}, 'b')));
|
||||
@@ -948,16 +938,18 @@ TEST(OwningGroup, SortUnordered) {
|
||||
ASSERT_EQ(*(group.data() + 2u), entities[0]);
|
||||
ASSERT_EQ(*(group.data() + 3u), entities[1]);
|
||||
ASSERT_EQ(*(group.data() + 4u), entities[2]);
|
||||
ASSERT_EQ(*(group.data() + 5u), entities[5]);
|
||||
ASSERT_EQ(*(group.data() + 6u), entities[6]);
|
||||
|
||||
ASSERT_EQ((group.raw<boxed_int>() + 0u)->value, 12);
|
||||
ASSERT_EQ((group.raw<boxed_int>() + 1u)->value, 9);
|
||||
ASSERT_EQ((group.raw<boxed_int>() + 2u)->value, 6);
|
||||
ASSERT_EQ((group.raw<boxed_int>() + 3u)->value, 3);
|
||||
ASSERT_EQ((group.raw<boxed_int>() + 4u)->value, 1);
|
||||
ASSERT_EQ((group.raw<boxed_int>() + 5u)->value, 4);
|
||||
ASSERT_EQ((group.raw<boxed_int>() + 6u)->value, 5);
|
||||
ASSERT_EQ(group.get<boxed_int>(*(group.data() + 0u)).value, 12);
|
||||
ASSERT_EQ(group.get<boxed_int>(*(group.data() + 1u)).value, 9);
|
||||
ASSERT_EQ(group.get<boxed_int>(*(group.data() + 2u)).value, 6);
|
||||
ASSERT_EQ(group.get<boxed_int>(*(group.data() + 3u)).value, 3);
|
||||
ASSERT_EQ(group.get<boxed_int>(*(group.data() + 4u)).value, 1);
|
||||
|
||||
ASSERT_EQ(group.get<char>(*(group.data() + 0u)), 'e');
|
||||
ASSERT_EQ(group.get<char>(*(group.data() + 1u)), 'd');
|
||||
ASSERT_EQ(group.get<char>(*(group.data() + 2u)), 'c');
|
||||
ASSERT_EQ(group.get<char>(*(group.data() + 3u)), 'b');
|
||||
ASSERT_EQ(group.get<char>(*(group.data() + 4u)), 'a');
|
||||
|
||||
ASSERT_EQ((group.get<boxed_int, char>(entities[0])), (std::make_tuple(boxed_int{6}, 'c')));
|
||||
ASSERT_EQ((group.get<boxed_int, char>(entities[1])), (std::make_tuple(boxed_int{3}, 'b')));
|
||||
@@ -993,15 +985,10 @@ TEST(OwningGroup, SortWithExclusionList) {
|
||||
ASSERT_EQ(*(group.data() + 2u), entities[1]);
|
||||
ASSERT_EQ(*(group.data() + 3u), entities[0]);
|
||||
|
||||
ASSERT_EQ((group.raw<boxed_int>() + 0u)->value, 4);
|
||||
ASSERT_EQ((group.raw<boxed_int>() + 1u)->value, 3);
|
||||
ASSERT_EQ((group.raw<boxed_int>() + 2u)->value, 1);
|
||||
ASSERT_EQ((group.raw<boxed_int>() + 3u)->value, 0);
|
||||
|
||||
ASSERT_EQ(group.get<boxed_int>(entities[0]).value, 0);
|
||||
ASSERT_EQ(group.get<boxed_int>(entities[1]).value, 1);
|
||||
ASSERT_EQ(group.get<boxed_int>(entities[3]).value, 3);
|
||||
ASSERT_EQ(group.get<boxed_int>(entities[4]).value, 4);
|
||||
ASSERT_EQ(group.get<boxed_int>(*(group.data() + 0u)).value, 4);
|
||||
ASSERT_EQ(group.get<boxed_int>(*(group.data() + 1u)).value, 3);
|
||||
ASSERT_EQ(group.get<boxed_int>(*(group.data() + 2u)).value, 1);
|
||||
ASSERT_EQ(group.get<boxed_int>(*(group.data() + 3u)).value, 0);
|
||||
|
||||
ASSERT_FALSE(group.contains(entities[2]));
|
||||
}
|
||||
@@ -1062,8 +1049,6 @@ TEST(OwningGroup, ConstNonConstAndAllInBetween) {
|
||||
static_assert(std::is_same_v<decltype(group.get<int, const char, double, const float>({})), std::tuple<int &, const char &, double &, const float &>>);
|
||||
static_assert(std::is_same_v<decltype(group.get({})), std::tuple<int &, const char &, double &, const float &>>);
|
||||
static_assert(std::is_same_v<decltype(group.data()), const entt::entity *>);
|
||||
static_assert(std::is_same_v<decltype(group.raw<const char>()), const char *>);
|
||||
static_assert(std::is_same_v<decltype(group.raw<int>()), int *>);
|
||||
|
||||
group.each([](auto &&i, auto &&c, auto &&d, auto &&f) {
|
||||
static_assert(std::is_same_v<decltype(i), int &>);
|
||||
|
||||
@@ -41,19 +41,39 @@ TEST(Helper, Invoke) {
|
||||
|
||||
TEST(Helper, ToEntity) {
|
||||
entt::registry registry;
|
||||
const auto entity = registry.create();
|
||||
const auto other = registry.create();
|
||||
const entt::entity null = entt::null;
|
||||
const int value = 42;
|
||||
|
||||
ASSERT_EQ(entt::to_entity(registry, 42), null);
|
||||
ASSERT_EQ(entt::to_entity(registry, value), null);
|
||||
|
||||
const auto entity = registry.create();
|
||||
registry.emplace<int>(entity);
|
||||
|
||||
while(registry.size<int>() < (ENTT_PAGE_SIZE - 1u)) {
|
||||
registry.emplace<int>(registry.create(), value);
|
||||
}
|
||||
|
||||
const auto other = registry.create();
|
||||
const auto next = registry.create();
|
||||
|
||||
registry.emplace<int>(other);
|
||||
registry.emplace<char>(other);
|
||||
registry.emplace<int>(next);
|
||||
|
||||
ASSERT_EQ(entt::to_entity(registry, registry.get<int>(entity)), entity);
|
||||
ASSERT_EQ(entt::to_entity(registry, registry.get<int>(other)), other);
|
||||
ASSERT_EQ(entt::to_entity(registry, registry.get<char>(other)), other);
|
||||
ASSERT_EQ(entt::to_entity(registry, registry.get<int>(next)), next);
|
||||
|
||||
registry.destroy(entity);
|
||||
ASSERT_EQ(®istry.get<int>(entity) + ENTT_PAGE_SIZE - 1u, ®istry.get<int>(other));
|
||||
ASSERT_NE(®istry.get<int>(entity) + ENTT_PAGE_SIZE, ®istry.get<int>(next));
|
||||
|
||||
ASSERT_EQ(entt::to_entity(registry, registry.get<int>(other)), other);
|
||||
ASSERT_EQ(entt::to_entity(registry, registry.get<char>(other)), other);
|
||||
registry.destroy(other);
|
||||
|
||||
ASSERT_EQ(entt::to_entity(registry, registry.get<int>(entity)), entity);
|
||||
ASSERT_EQ(entt::to_entity(registry, registry.get<int>(next)), next);
|
||||
|
||||
ASSERT_EQ(®istry.get<int>(entity) + ENTT_PAGE_SIZE - 1u, ®istry.get<int>(next));
|
||||
|
||||
ASSERT_EQ(entt::to_entity(registry, 42), null);
|
||||
ASSERT_EQ(entt::to_entity(registry, value), null);
|
||||
}
|
||||
|
||||
@@ -154,8 +154,8 @@ TEST(Registry, Functionalities) {
|
||||
ASSERT_TRUE(registry.empty());
|
||||
|
||||
ASSERT_EQ(registry.capacity(), 42u);
|
||||
ASSERT_EQ(registry.capacity<int>(), 8u);
|
||||
ASSERT_EQ(registry.capacity<char>(), 8u);
|
||||
ASSERT_EQ(registry.capacity<int>(), ENTT_PAGE_SIZE);
|
||||
ASSERT_EQ(registry.capacity<char>(), ENTT_PAGE_SIZE);
|
||||
ASSERT_EQ(registry.size<int>(), 0u);
|
||||
ASSERT_EQ(registry.size<char>(), 0u);
|
||||
ASSERT_TRUE((registry.empty<int, char>()));
|
||||
@@ -295,8 +295,8 @@ TEST(Registry, Functionalities) {
|
||||
ASSERT_EQ(registry.size<char>(), 0u);
|
||||
ASSERT_TRUE(registry.empty<int>());
|
||||
|
||||
ASSERT_EQ(registry.capacity<int>(), 8u);
|
||||
ASSERT_EQ(registry.capacity<char>(), 8u);
|
||||
ASSERT_EQ(registry.capacity<int>(), ENTT_PAGE_SIZE);
|
||||
ASSERT_EQ(registry.capacity<char>(), ENTT_PAGE_SIZE);
|
||||
|
||||
registry.shrink_to_fit<int, char>();
|
||||
|
||||
@@ -530,13 +530,25 @@ TEST(Registry, VersionOverflow) {
|
||||
entt::registry registry;
|
||||
const auto entity = registry.create();
|
||||
|
||||
registry.destroy(entity, typename traits_type::version_type(traits_type::version_mask));
|
||||
registry.destroy(entity);
|
||||
|
||||
ASSERT_NE(registry.current(entity), registry.version(entity));
|
||||
ASSERT_NE(registry.current(entity), typename traits_type::version_type{});
|
||||
|
||||
registry.destroy(registry.create(), typename traits_type::version_type{traits_type::version_mask});
|
||||
registry.destroy(registry.create());
|
||||
|
||||
ASSERT_EQ(registry.current(entity), registry.version(entity));
|
||||
ASSERT_EQ(registry.current(entity), typename traits_type::version_type{});
|
||||
}
|
||||
|
||||
TEST(Registry, NullEntity) {
|
||||
entt::registry registry;
|
||||
|
||||
ASSERT_FALSE(registry.valid(entt::null));
|
||||
ASSERT_DEATH(static_cast<void>(registry.create(entt::null)), "");
|
||||
}
|
||||
|
||||
TEST(Registry, Each) {
|
||||
entt::registry registry;
|
||||
entt::registry::size_type tot;
|
||||
|
||||
@@ -14,7 +14,6 @@ TEST(Registry, NoEto) {
|
||||
registry.emplace<empty_type>(entity);
|
||||
registry.emplace<int>(entity, 42);
|
||||
|
||||
ASSERT_NE(registry.view<empty_type>().raw(), nullptr);
|
||||
ASSERT_NE(registry.try_get<empty_type>(entity), nullptr);
|
||||
ASSERT_EQ(registry.view<empty_type>().get(entity), std::as_const(registry).view<const empty_type>().get(entity));
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ TEST(Storage, Functionalities) {
|
||||
|
||||
pool.reserve(42);
|
||||
|
||||
ASSERT_EQ(pool.capacity(), 42u);
|
||||
ASSERT_EQ(pool.capacity(), ENTT_PAGE_SIZE);
|
||||
ASSERT_TRUE(pool.empty());
|
||||
ASSERT_EQ(pool.size(), 0u);
|
||||
ASSERT_EQ(std::as_const(pool).begin(), std::as_const(pool).end());
|
||||
@@ -80,7 +80,7 @@ TEST(Storage, Functionalities) {
|
||||
ASSERT_FALSE(pool.contains(entt::entity{0}));
|
||||
ASSERT_FALSE(pool.contains(entt::entity{41}));
|
||||
|
||||
ASSERT_EQ(pool.capacity(), 42u);
|
||||
ASSERT_EQ(pool.capacity(), ENTT_PAGE_SIZE);
|
||||
|
||||
pool.shrink_to_fit();
|
||||
|
||||
@@ -208,6 +208,35 @@ TEST(Storage, Remove) {
|
||||
ASSERT_EQ(*pool.begin(), 1);
|
||||
}
|
||||
|
||||
TEST(Storage, ShrinkToFit) {
|
||||
entt::storage<int> pool;
|
||||
|
||||
for(std::size_t next{}; next < ENTT_PAGE_SIZE; ++next) {
|
||||
pool.emplace(entt::entity(next));
|
||||
}
|
||||
|
||||
pool.emplace(entt::entity{ENTT_PAGE_SIZE});
|
||||
pool.erase(entt::entity{ENTT_PAGE_SIZE});
|
||||
|
||||
ASSERT_EQ(pool.capacity(), 2 * ENTT_PAGE_SIZE);
|
||||
ASSERT_EQ(pool.size(), ENTT_PAGE_SIZE);
|
||||
|
||||
pool.shrink_to_fit();
|
||||
|
||||
ASSERT_EQ(pool.capacity(), ENTT_PAGE_SIZE);
|
||||
ASSERT_EQ(pool.size(), ENTT_PAGE_SIZE);
|
||||
|
||||
pool.clear();
|
||||
|
||||
ASSERT_EQ(pool.capacity(), ENTT_PAGE_SIZE);
|
||||
ASSERT_EQ(pool.size(), 0u);
|
||||
|
||||
pool.shrink_to_fit();
|
||||
|
||||
ASSERT_EQ(pool.capacity(), 0u);
|
||||
ASSERT_EQ(pool.size(), 0u);
|
||||
}
|
||||
|
||||
TEST(Storage, AggregatesMustWork) {
|
||||
struct aggregate_type { int value; };
|
||||
// the goal of this test is to enforce the requirements for aggregate types
|
||||
@@ -389,22 +418,6 @@ TEST(Storage, ConstReverseIterator) {
|
||||
ASSERT_GE(cend, pool.crend());
|
||||
}
|
||||
|
||||
TEST(Storage, Raw) {
|
||||
entt::storage<int> pool;
|
||||
|
||||
pool.emplace(entt::entity{3}, 3);
|
||||
pool.emplace(entt::entity{12}, 6);
|
||||
pool.emplace(entt::entity{42}, 9);
|
||||
|
||||
ASSERT_EQ(pool.get(entt::entity{3}), 3);
|
||||
ASSERT_EQ(std::as_const(pool).get(entt::entity{12}), 6);
|
||||
ASSERT_EQ(pool.get(entt::entity{42}), 9);
|
||||
|
||||
ASSERT_EQ(pool.raw()[0u], 3);
|
||||
ASSERT_EQ(std::as_const(pool).raw()[1u], 6);
|
||||
ASSERT_EQ(pool.raw()[2u], 9);
|
||||
}
|
||||
|
||||
TEST(Storage, SortOrdered) {
|
||||
entt::storage<boxed_int> pool;
|
||||
entt::entity entities[5u]{entt::entity{12}, entt::entity{42}, entt::entity{7}, entt::entity{3}, entt::entity{9}};
|
||||
@@ -467,9 +480,9 @@ TEST(Storage, SortRange) {
|
||||
|
||||
pool.sort_n(2u, [](auto lhs, auto rhs) { return lhs.value < rhs.value; });
|
||||
|
||||
ASSERT_EQ(pool.raw()[0u], boxed_int{6});
|
||||
ASSERT_EQ(pool.raw()[1u], boxed_int{3});
|
||||
ASSERT_EQ(pool.raw()[2u], boxed_int{1});
|
||||
ASSERT_EQ(pool.rbegin()[0u], boxed_int{6});
|
||||
ASSERT_EQ(pool.rbegin()[1u], boxed_int{3});
|
||||
ASSERT_EQ(pool.rbegin()[2u], boxed_int{1});
|
||||
|
||||
ASSERT_EQ(pool.data()[0u], entt::entity{42});
|
||||
ASSERT_EQ(pool.data()[1u], entt::entity{12});
|
||||
@@ -650,12 +663,12 @@ TEST(Storage, CanModifyDuringIteration) {
|
||||
entt::storage<int> pool;
|
||||
pool.emplace(entt::entity{0}, 42);
|
||||
|
||||
ASSERT_EQ(pool.capacity(), 1u);
|
||||
ASSERT_EQ(pool.capacity(), ENTT_PAGE_SIZE);
|
||||
|
||||
const auto it = pool.cbegin();
|
||||
pool.reserve(2u);
|
||||
pool.reserve(ENTT_PAGE_SIZE + 1u);
|
||||
|
||||
ASSERT_EQ(pool.capacity(), 2u);
|
||||
ASSERT_EQ(pool.capacity(), 2 * ENTT_PAGE_SIZE);
|
||||
|
||||
// this should crash with asan enabled if we break the constraint
|
||||
const auto entity = *it;
|
||||
@@ -696,7 +709,7 @@ TEST(Storage, MoveOnlyComponent) {
|
||||
(void)pool;
|
||||
}
|
||||
|
||||
TEST(Storage, ConstructorExceptionDoesNotAddToStorage) {
|
||||
TEST(Storage, EmplaceStrongExceptionGuarantee) {
|
||||
entt::storage<throwing_component> pool;
|
||||
|
||||
try {
|
||||
|
||||
@@ -47,9 +47,6 @@ TEST(SingleComponentView, Functionalities) {
|
||||
ASSERT_EQ(*(view.data() + 0), e1);
|
||||
ASSERT_EQ(*(view.data() + 1), e0);
|
||||
|
||||
ASSERT_EQ(*(view.raw() + 0), '2');
|
||||
ASSERT_EQ(*(cview.raw() + 1), '1');
|
||||
|
||||
registry.erase<char>(e0);
|
||||
registry.erase<char>(e1);
|
||||
|
||||
@@ -64,7 +61,7 @@ TEST(SingleComponentView, Functionalities) {
|
||||
ASSERT_FALSE(invalid);
|
||||
}
|
||||
|
||||
TEST(SingleComponentView, RawData) {
|
||||
TEST(SingleComponentView, Data) {
|
||||
entt::registry registry;
|
||||
auto view = registry.view<int>();
|
||||
auto cview = std::as_const(registry).view<const int>();
|
||||
@@ -73,8 +70,6 @@ TEST(SingleComponentView, RawData) {
|
||||
|
||||
ASSERT_EQ(view.size(), 0u);
|
||||
ASSERT_EQ(cview.size(), 0u);
|
||||
ASSERT_EQ(view.raw(), nullptr);
|
||||
ASSERT_EQ(cview.raw(), nullptr);
|
||||
ASSERT_EQ(view.data(), nullptr);
|
||||
ASSERT_EQ(cview.data(), nullptr);
|
||||
|
||||
@@ -82,8 +77,6 @@ TEST(SingleComponentView, RawData) {
|
||||
|
||||
ASSERT_NE(view.size(), 0u);
|
||||
ASSERT_NE(cview.size(), 0u);
|
||||
ASSERT_EQ(*view.raw(), 42);
|
||||
ASSERT_EQ(*cview.raw(), 42);
|
||||
ASSERT_EQ(*view.data(), entity);
|
||||
ASSERT_EQ(*cview.data(), entity);
|
||||
|
||||
@@ -105,7 +98,6 @@ TEST(SingleComponentView, LazyTypeFromConstRegistry) {
|
||||
ASSERT_TRUE(cview);
|
||||
ASSERT_TRUE(eview);
|
||||
|
||||
ASSERT_NE(cview.raw(), nullptr);
|
||||
ASSERT_NE(eview.data(), nullptr);
|
||||
|
||||
ASSERT_FALSE(cview.empty());
|
||||
@@ -226,15 +218,10 @@ TEST(SingleComponentView, ConstNonConstAndAllInBetween) {
|
||||
ASSERT_EQ(view.size(), 1u);
|
||||
ASSERT_EQ(cview.size(), 1u);
|
||||
|
||||
static_assert(std::is_same_v<decltype(view.raw()), int *>);
|
||||
static_assert(std::is_same_v<decltype(cview.raw()), const int *>);
|
||||
|
||||
static_assert(std::is_same_v<decltype(view.get<int>({})), int &>);
|
||||
static_assert(std::is_same_v<decltype(view.get({})), std::tuple<int &>>);
|
||||
static_assert(std::is_same_v<decltype(view.raw()), int *>);
|
||||
static_assert(std::is_same_v<decltype(cview.get<const int>({})), const int &>);
|
||||
static_assert(std::is_same_v<decltype(cview.get({})), std::tuple<const int &>>);
|
||||
static_assert(std::is_same_v<decltype(cview.raw()), const int *>);
|
||||
|
||||
view.each([](auto &&i) {
|
||||
static_assert(std::is_same_v<decltype(i), int &>);
|
||||
|
||||
Reference in New Issue
Block a user