batch add is now available
This commit is contained in:
1
TODO
1
TODO
@@ -17,7 +17,6 @@
|
||||
* allow some features by component type (eg registry.assign(entity, component);
|
||||
- it could be possible for eg default constructible types by storing aside (pool data) erased functions
|
||||
- does it worth it?
|
||||
* add and bulk add with components (sort of registry.create<A, B>(first, last) and registry.create<A, B>())
|
||||
* events on replace, so that one can track updated components? indagate impact
|
||||
- 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)
|
||||
|
||||
@@ -6,9 +6,10 @@
|
||||
# Table of Contents
|
||||
|
||||
* [Introduction](#introduction)
|
||||
* [Design choices](#design-choices)
|
||||
* [Design decisions](#design-decisions)
|
||||
* [A bitset-free entity-component system](#a-bitset-free-entity-component-system)
|
||||
* [Pay per use](#pay-per-use)
|
||||
* [All or nothing](#all-or-nothing)
|
||||
* [Vademecum](#vademecum)
|
||||
* [The Registry, the Entity and the Component](#the-registry-the-entity-and-the-component)
|
||||
* [Observe changes](#observe-changes)
|
||||
@@ -49,7 +50,7 @@ more) written in modern C++.<br/>
|
||||
The entity-component-system (also known as _ECS_) is an architectural pattern
|
||||
used mostly in game development.
|
||||
|
||||
# Design choices
|
||||
# Design decisions
|
||||
|
||||
## A bitset-free entity-component system
|
||||
|
||||
@@ -90,6 +91,22 @@ performance along critical paths is high.
|
||||
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, because it's of the corner stones of this
|
||||
library.
|
||||
|
||||
Many of the tools described below, from the registry to the views and up to the
|
||||
groups give the possibility to get this information and have been designed
|
||||
around this need, which was and remains one of my main requirements during the
|
||||
development.<br/>
|
||||
The rest is experimentation and the desire to invent something new, hoping to
|
||||
have succeeded.
|
||||
|
||||
# Vademecum
|
||||
|
||||
The registry to store, the views and the groups to iterate. That's all.
|
||||
@@ -129,7 +146,7 @@ Entities are represented by _entity identifiers_. An entity identifier is an
|
||||
opaque type that users should not inspect or modify in any way. It carries
|
||||
information about the entity itself and its version.
|
||||
|
||||
A registry can be used both to construct and destroy entities:
|
||||
A registry can be used both to construct and to destroy entities:
|
||||
|
||||
```cpp
|
||||
// constructs a naked entity with no components and returns its identifier
|
||||
@@ -139,9 +156,9 @@ auto entity = registry.create();
|
||||
registry.destroy(entity);
|
||||
```
|
||||
|
||||
There exist also overloads of the `create` and `destroy` member functions that
|
||||
accept two iterators, that is a range to assign or to destroy. It can be used to
|
||||
create or destroy multiple entities at once:
|
||||
There exists also an overload of the `create` and `destroy` member functions
|
||||
that accepts two iterators, that is a range to assign or to destroy. It can be
|
||||
used to create or destroy multiple entities at once:
|
||||
|
||||
```cpp
|
||||
// destroys all the entities in a range
|
||||
@@ -149,6 +166,11 @@ auto view = registry.view<a_component, another_component>();
|
||||
registry.destroy(view.begin(), view.end());
|
||||
```
|
||||
|
||||
In both cases, the `create` member function accepts also a list of default
|
||||
constructible types of components to assign to the entities before to return.
|
||||
It's a faster alternative to the creation and subsequent assignment of
|
||||
components in separate steps.
|
||||
|
||||
When an entity is destroyed, the registry can freely reuse it internally with a
|
||||
slightly different identifier. In particular, the version of an entity is
|
||||
increased each and every time it's discarded.<br/>
|
||||
|
||||
@@ -63,6 +63,9 @@ class registry {
|
||||
using signal_type = sigh<void(registry &, const Entity)>;
|
||||
using traits_type = entt_traits<Entity>;
|
||||
|
||||
template<typename Component>
|
||||
using pool_type = sparse_set<Entity, std::decay_t<Component>>;
|
||||
|
||||
template<typename, typename>
|
||||
struct non_owning_group;
|
||||
|
||||
@@ -136,6 +139,16 @@ class registry {
|
||||
std::size_t extent;
|
||||
};
|
||||
|
||||
void release(const Entity entity) {
|
||||
// lengthens the implicit list of destroyed entities
|
||||
const auto entt = entity & traits_type::entity_mask;
|
||||
const auto version = ((entity >> traits_type::entity_shift) + 1) << traits_type::entity_shift;
|
||||
const auto node = (available ? next : ((entt + 1) & traits_type::entity_mask)) | version;
|
||||
entities[entt] = node;
|
||||
next = entt;
|
||||
++available;
|
||||
}
|
||||
|
||||
template<typename Component>
|
||||
inline auto pool() const ENTT_NOEXCEPT {
|
||||
const auto ctype = type<Component>();
|
||||
@@ -146,10 +159,10 @@ class registry {
|
||||
});
|
||||
|
||||
assert(it != pools.cend() && it->pool);
|
||||
return std::make_tuple(&*it, static_cast<sparse_set<Entity, std::decay_t<Component>> *>(it->pool.get()));
|
||||
return std::make_tuple(&*it, static_cast<pool_type<Component> *>(it->pool.get()));
|
||||
} else {
|
||||
assert(ctype < pools.size() && pools[ctype].pool && pools[ctype].runtime_type == ctype);
|
||||
return std::make_tuple(&pools[ctype], static_cast<sparse_set<Entity, std::decay_t<Component>> *>(pools[ctype].pool.get()));
|
||||
return std::make_tuple(&pools[ctype], static_cast<pool_type<Component> *>(pools[ctype].pool.get()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,11 +192,11 @@ class registry {
|
||||
}
|
||||
|
||||
if(!pdata->pool) {
|
||||
pdata->pool = std::make_unique<sparse_set<Entity, std::decay_t<Component>>>();
|
||||
pdata->pool = std::make_unique<pool_type<Component>>();
|
||||
pdata->runtime_type = ctype;
|
||||
}
|
||||
|
||||
return std::make_tuple(pdata, static_cast<sparse_set<Entity, std::decay_t<Component>> *>(pdata->pool.get()));
|
||||
return std::make_tuple(pdata, static_cast<pool_type<Component> *>(pdata->pool.get()));
|
||||
}
|
||||
|
||||
public:
|
||||
@@ -328,7 +341,7 @@ public:
|
||||
* There are no guarantees on the order of the components. Use a view if you
|
||||
* want to iterate entities and components in the expected order.
|
||||
*
|
||||
* @warning
|
||||
* @note
|
||||
* Empty components aren't explicitly instantiated. Therefore, this function
|
||||
* always returns `nullptr` for them.
|
||||
*
|
||||
@@ -452,11 +465,18 @@ public:
|
||||
* function can be used to know if they are still valid or the entity has
|
||||
* been destroyed and potentially recycled.
|
||||
*
|
||||
* The returned entity has no components assigned.
|
||||
* The returned entity has assigned the given components, if any. The
|
||||
* components must be at least default constructible. A compilation error
|
||||
* will occur otherwhise.
|
||||
*
|
||||
* @return A valid entity identifier.
|
||||
* @tparam Component Types of components to assign to the entity.
|
||||
* @return A valid entity identifier if the component list is empty, a tuple
|
||||
* containing the entity identifier and the references to the components
|
||||
* just created otherwise.
|
||||
*/
|
||||
entity_type create() {
|
||||
template<typename... Component>
|
||||
std::conditional_t<sizeof...(Component) == 0, entity_type, std::tuple<entity_type, Component &...>>
|
||||
create() {
|
||||
entity_type entity;
|
||||
|
||||
if(available) {
|
||||
@@ -472,7 +492,11 @@ public:
|
||||
assert(entity < traits_type::entity_mask);
|
||||
}
|
||||
|
||||
return entity;
|
||||
if constexpr(sizeof...(Component) == 0) {
|
||||
return entity;
|
||||
} else {
|
||||
return { entity, assign<Component>(entity)... };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -488,30 +512,64 @@ public:
|
||||
* function can be used to know if they are still valid or the entity has
|
||||
* been destroyed and potentially recycled.
|
||||
*
|
||||
* The generated entities have no components assigned.
|
||||
* The entities so generated have assigned the given components, if any. The
|
||||
* components must be at least default constructible. A compilation error
|
||||
* will occur otherwhise.
|
||||
*
|
||||
* @tparam Component Types of components to assign to the entity.
|
||||
* @tparam It Type of forward iterator.
|
||||
* @param first An iterator to the first element of the range to generate.
|
||||
* @param last An iterator past the last element of the range to generate.
|
||||
* @return No return value if the component list is empty, a tuple
|
||||
* containing the pointers to the arrays of components just created and
|
||||
* sorted the same of the entities otherwise.
|
||||
*/
|
||||
template<typename It>
|
||||
void create(It first, It last) {
|
||||
template<typename... Component, typename It>
|
||||
std::conditional_t<sizeof...(Component) == 0, void, std::tuple<Component *...>>
|
||||
create(It first, It last) {
|
||||
static_assert(std::is_convertible_v<entity_type, typename std::iterator_traits<It>::value_type>);
|
||||
const auto length = size_type(std::distance(first, last));
|
||||
const auto sz = std::min(available, length);
|
||||
[[maybe_unused]] entity_type candidate{};
|
||||
|
||||
available -= sz;
|
||||
|
||||
std::generate_n(first, sz, [this]() {
|
||||
const auto tail = std::generate_n(first, sz, [&candidate, this]() mutable {
|
||||
if constexpr(sizeof...(Component) > 0) {
|
||||
candidate = std::max(candidate, next);
|
||||
} else {
|
||||
// suppress warnings
|
||||
(void)candidate;
|
||||
}
|
||||
|
||||
const auto entt = next;
|
||||
const auto version = entities[entt] & (traits_type::version_mask << traits_type::entity_shift);
|
||||
next = entities[entt] & traits_type::entity_mask;
|
||||
return (entities[entt] = entt | version);
|
||||
});
|
||||
|
||||
std::generate_n((first + sz), (length - sz), [this]() {
|
||||
std::generate(tail, last, [this]() {
|
||||
return entities.emplace_back(entity_type(entities.size()));
|
||||
});
|
||||
|
||||
if constexpr(sizeof...(Component) > 0) {
|
||||
const auto hint = size_type(std::max(candidate, *(last-1)))+1;
|
||||
|
||||
auto generator = [first, last, hint, this](auto &&adata) {
|
||||
auto *comp = std::get<1>(adata)->construct(first, last, hint);
|
||||
auto *pdata = std::get<0>(adata);
|
||||
|
||||
if(!pdata->construction.empty()) {
|
||||
std::for_each(first, last, [pdata, this](const auto entity) {
|
||||
pdata->construction.publish(*this, entity);
|
||||
});
|
||||
}
|
||||
|
||||
return comp;
|
||||
};
|
||||
|
||||
return { generator(assure<Component>())... };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -550,14 +608,7 @@ public:
|
||||
|
||||
// just a way to protect users from listeners that attach components
|
||||
assert(orphan(entity));
|
||||
|
||||
// lengthens the implicit list of destroyed entities
|
||||
const auto entt = entity & traits_type::entity_mask;
|
||||
const auto version = ((entity >> traits_type::entity_shift) + 1) << traits_type::entity_shift;
|
||||
const auto node = (available ? next : ((entt + 1) & traits_type::entity_mask)) | version;
|
||||
entities[entt] = node;
|
||||
next = entt;
|
||||
++available;
|
||||
release(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -568,8 +619,26 @@ public:
|
||||
*/
|
||||
template<typename It>
|
||||
void destroy(It first, It last) {
|
||||
assert(std::all_of(first, last, [this](const auto entity) { return valid(entity); }));
|
||||
|
||||
for(auto pos = pools.size(); pos; --pos) {
|
||||
auto &pdata = pools[pos-1];
|
||||
|
||||
if(pdata.pool) {
|
||||
std::for_each(first, last, [&pdata, this](const auto entity) {
|
||||
if(pdata.pool->has(entity)) {
|
||||
pdata.destruction.publish(*this, entity);
|
||||
pdata.pool->destroy(entity);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// just a way to protect users from listeners that attach components
|
||||
assert(std::all_of(first, last, [this](const auto entity) { return orphan(entity); }));
|
||||
|
||||
std::for_each(first, last, [this](const auto entity) {
|
||||
destroy(entity);
|
||||
release(entity);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1433,7 +1502,7 @@ public:
|
||||
* more instances of this class in sync, as an example in a client-server
|
||||
* architecture.
|
||||
*
|
||||
* @warning
|
||||
* @note
|
||||
* The loader returned by this function requires that the registry be empty.
|
||||
* In case it isn't, all the data will be automatically deleted before to
|
||||
* return.
|
||||
|
||||
@@ -370,6 +370,41 @@ public:
|
||||
direct.push_back(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns one or more entities to a sparse set.
|
||||
*
|
||||
* This function requires to use a hint value for performance purposes.<br/>
|
||||
* Its value indicates the size necessary to accommodate the largest entity
|
||||
* if used as an index of a hypothetical array.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to assign an entity that already belongs to the sparse set
|
||||
* results in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* sparse set already contains the given entity.
|
||||
*
|
||||
* @tparam It Type of forward iterator.
|
||||
* @param first An iterator to the first element of the range of entities.
|
||||
* @param last An iterator past the last element of the range of entities.
|
||||
* @param hint Hint value to avoid searching for the largest entity.
|
||||
*/
|
||||
template<typename It>
|
||||
void construct(It first, It last, size_type hint) {
|
||||
if(hint > reverse.size()) {
|
||||
// null is safe in all cases for our purposes
|
||||
reverse.resize(hint, null);
|
||||
}
|
||||
|
||||
std::for_each(first, last, [next = entity_type(direct.size()), this](const auto entity) mutable {
|
||||
assert(!has(entity));
|
||||
const auto pos = size_type(entity & traits_type::entity_mask);
|
||||
assert(pos < reverse.size());
|
||||
reverse[pos] = next++;
|
||||
});
|
||||
|
||||
direct.insert(direct.end(), first, last);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Removes an entity from a sparse set.
|
||||
*
|
||||
@@ -750,7 +785,7 @@ public:
|
||||
* performance boost but less guarantees. Use `begin` and `end` if you want
|
||||
* to iterate the sparse set in the expected order.
|
||||
*
|
||||
* @warning
|
||||
* @note
|
||||
* Empty components aren't explicitly instantiated. Only one instance of the
|
||||
* given type is created. Therefore, this function always returns a pointer
|
||||
* to that instance.
|
||||
@@ -873,7 +908,6 @@ public:
|
||||
/**
|
||||
* @brief Assigns an entity to a sparse set and constructs its object.
|
||||
*
|
||||
* @note
|
||||
* This version accept both types that can be constructed in place directly
|
||||
* and types like aggregates that do not work well with a placement new as
|
||||
* performed usually under the hood during an _emplace back_.
|
||||
@@ -907,6 +941,50 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assigns one or more entities to a sparse set and constructs their
|
||||
* objects.
|
||||
*
|
||||
* This function requires to use a hint value for performance purposes.<br/>
|
||||
* Its value indicates the size necessary to accommodate the largest entity
|
||||
* if used as an index of a hypothetical array.
|
||||
*
|
||||
* @note
|
||||
* The object type must be at least default constructible.
|
||||
*
|
||||
* @note
|
||||
* Empty components aren't explicitly instantiated. Only one instance of the
|
||||
* given type is created. Therefore, this function always returns a pointer
|
||||
* to that instance.
|
||||
*
|
||||
* @warning
|
||||
* Attempting to assign an entity that already belongs to the sparse set
|
||||
* results in undefined behavior.<br/>
|
||||
* An assertion will abort the execution at runtime in debug mode if the
|
||||
* sparse set already contains the given entity.
|
||||
*
|
||||
* @tparam It Type of forward iterator.
|
||||
* @param first An iterator to the first element of the range of entities.
|
||||
* @param last An iterator past the last element of the range of entities.
|
||||
* @param hint Hint value to avoid searching for the largest entity.
|
||||
* @return A pointer to the array of instances just created and sorted the
|
||||
* same of the entities.
|
||||
*/
|
||||
template<typename It>
|
||||
object_type * construct(It first, It last, const size_type hint) {
|
||||
if constexpr(std::is_empty_v<object_type>) {
|
||||
underlying_type::construct(first, last, hint);
|
||||
return &instances;
|
||||
} else {
|
||||
static_assert(std::is_default_constructible_v<object_type>);
|
||||
const auto offset = instances.size();
|
||||
instances.insert(instances.end(), last-first, {});
|
||||
// entity goes after component in case constructor throws
|
||||
underlying_type::construct(first, last, hint);
|
||||
return instances.data() + offset;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Removes an entity from a sparse set and destroies its object.
|
||||
*
|
||||
@@ -960,14 +1038,14 @@ public:
|
||||
* this member function.
|
||||
*
|
||||
* @note
|
||||
* Empty components aren't explicitly instantiated. Therefore, this function
|
||||
* isn't available for them.
|
||||
*
|
||||
* @note
|
||||
* Attempting to iterate elements using a raw pointer returned by a call to
|
||||
* either `data` or `raw` gives no guarantees on the order, even though
|
||||
* `sort` has been invoked.
|
||||
*
|
||||
* @warning
|
||||
* Empty components aren't explicitly instantiated. Therefore, this function
|
||||
* isn't available for them.
|
||||
*
|
||||
* @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.
|
||||
|
||||
@@ -56,6 +56,35 @@ TEST(Benchmark, ConstructMany) {
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(Benchmark, ConstructManyAndAssignComponents) {
|
||||
entt::registry<> registry;
|
||||
std::vector<entt::registry<>::entity_type> entities(1000000);
|
||||
|
||||
std::cout << "Constructing 1000000 entities at once and assign components" << std::endl;
|
||||
|
||||
timer timer;
|
||||
|
||||
registry.create(entities.begin(), entities.end());
|
||||
|
||||
for(const auto entity: entities) {
|
||||
registry.assign<position>(entity);
|
||||
registry.assign<velocity>(entity);
|
||||
}
|
||||
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(Benchmark, ConstructManyWithComponents) {
|
||||
entt::registry<> registry;
|
||||
std::vector<entt::registry<>::entity_type> entities(1000000);
|
||||
|
||||
std::cout << "Constructing 1000000 entities at once with components" << std::endl;
|
||||
|
||||
timer timer;
|
||||
registry.create<position, velocity>(entities.begin(), entities.end());
|
||||
timer.elapsed();
|
||||
}
|
||||
|
||||
TEST(Benchmark, Destroy) {
|
||||
entt::registry<> registry;
|
||||
|
||||
|
||||
@@ -938,6 +938,81 @@ TEST(Registry, CreateManyEntitiesAtOnce) {
|
||||
ASSERT_EQ(registry.version(entities[2]), entt::registry<>::version_type{0});
|
||||
}
|
||||
|
||||
TEST(Registry, CreateAnEntityWithComponents) {
|
||||
entt::registry<> registry;
|
||||
const auto &[entity, ivalue, cvalue] = registry.create<int, char>();
|
||||
|
||||
ASSERT_FALSE(registry.empty<int>());
|
||||
ASSERT_FALSE(registry.empty<char>());
|
||||
|
||||
ASSERT_EQ(registry.size<int>(), entt::registry<>::size_type{1});
|
||||
ASSERT_EQ(registry.size<int>(), entt::registry<>::size_type{1});
|
||||
|
||||
ASSERT_TRUE((registry.has<int, char>(entity)));
|
||||
|
||||
ivalue = 42;
|
||||
cvalue = 'c';
|
||||
|
||||
ASSERT_EQ(registry.get<int>(entity), 42);
|
||||
ASSERT_EQ(registry.get<char>(entity), 'c');
|
||||
}
|
||||
|
||||
TEST(Registry, CreateManyEntitiesWithComponentsAtOnce) {
|
||||
entt::registry<> registry;
|
||||
entt::registry<>::entity_type entities[3];
|
||||
|
||||
const auto entity = registry.create();
|
||||
registry.destroy(registry.create());
|
||||
registry.destroy(entity);
|
||||
registry.destroy(registry.create());
|
||||
|
||||
const auto [iptr, cptr] = registry.create<int, char>(std::begin(entities), std::end(entities));
|
||||
|
||||
ASSERT_FALSE(registry.empty<int>());
|
||||
ASSERT_FALSE(registry.empty<char>());
|
||||
|
||||
ASSERT_EQ(registry.size<int>(), entt::registry<>::size_type{3});
|
||||
ASSERT_EQ(registry.size<int>(), entt::registry<>::size_type{3});
|
||||
|
||||
ASSERT_TRUE(registry.valid(entities[0]));
|
||||
ASSERT_TRUE(registry.valid(entities[1]));
|
||||
ASSERT_TRUE(registry.valid(entities[2]));
|
||||
|
||||
ASSERT_EQ(registry.entity(entities[0]), entt::registry<>::entity_type{0});
|
||||
ASSERT_EQ(registry.version(entities[0]), entt::registry<>::version_type{2});
|
||||
|
||||
ASSERT_EQ(registry.entity(entities[1]), entt::registry<>::entity_type{1});
|
||||
ASSERT_EQ(registry.version(entities[1]), entt::registry<>::version_type{1});
|
||||
|
||||
ASSERT_EQ(registry.entity(entities[2]), entt::registry<>::entity_type{2});
|
||||
ASSERT_EQ(registry.version(entities[2]), entt::registry<>::version_type{0});
|
||||
|
||||
ASSERT_TRUE((registry.has<int, char>(entities[0])));
|
||||
ASSERT_TRUE((registry.has<int, char>(entities[1])));
|
||||
ASSERT_TRUE((registry.has<int, char>(entities[2])));
|
||||
|
||||
for(auto i = 0; i < 3; ++i) {
|
||||
iptr[i] = i;
|
||||
cptr[i] = char('a'+i);
|
||||
}
|
||||
|
||||
for(auto i = 0; i < 3; ++i) {
|
||||
ASSERT_EQ(registry.get<int>(entities[i]), i);
|
||||
ASSERT_EQ(registry.get<char>(entities[i]), char('a'+i));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Registry, CreateManyEntitiesWithComponentsAtOnceWithListener) {
|
||||
entt::registry<> registry;
|
||||
entt::registry<>::entity_type entities[3];
|
||||
listener listener;
|
||||
|
||||
registry.construction<int>().connect<&listener::incr<int>>(&listener);
|
||||
registry.create<int, char>(std::begin(entities), std::end(entities));
|
||||
|
||||
ASSERT_EQ(listener.counter, 3);
|
||||
}
|
||||
|
||||
TEST(Registry, NonOwningGroupInterleaved) {
|
||||
entt::registry<> registry;
|
||||
typename entt::registry<>::entity_type entity = entt::null;
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
#include <memory>
|
||||
#include <iterator>
|
||||
#include <exception>
|
||||
#include <algorithm>
|
||||
#include <unordered_set>
|
||||
#include <gtest/gtest.h>
|
||||
#include <entt/entity/sparse_set.hpp>
|
||||
|
||||
struct empty_type {};
|
||||
|
||||
TEST(SparseSetNoType, Functionalities) {
|
||||
entt::sparse_set<std::uint64_t> set;
|
||||
|
||||
@@ -58,6 +61,32 @@ TEST(SparseSetNoType, Functionalities) {
|
||||
other = std::move(set);
|
||||
}
|
||||
|
||||
TEST(SparseSetNoType, ConstructMany) {
|
||||
entt::sparse_set<std::uint64_t> set;
|
||||
entt::sparse_set<std::uint64_t>::entity_type entities[2];
|
||||
|
||||
entities[0] = 3;
|
||||
entities[1] = 42;
|
||||
|
||||
set.construct(12);
|
||||
set.construct(std::begin(entities), std::end(entities), 43);
|
||||
set.construct(24);
|
||||
|
||||
ASSERT_TRUE(set.has(entities[0]));
|
||||
ASSERT_TRUE(set.has(entities[1]));
|
||||
ASSERT_FALSE(set.has(0));
|
||||
ASSERT_FALSE(set.has(9));
|
||||
ASSERT_TRUE(set.has(12));
|
||||
ASSERT_TRUE(set.has(24));
|
||||
|
||||
ASSERT_FALSE(set.empty());
|
||||
ASSERT_EQ(set.size(), 4u);
|
||||
ASSERT_EQ(set.get(12), 0u);
|
||||
ASSERT_EQ(set.get(entities[0]), 1u);
|
||||
ASSERT_EQ(set.get(entities[1]), 2u);
|
||||
ASSERT_EQ(set.get(24), 3u);
|
||||
}
|
||||
|
||||
TEST(SparseSetNoType, Iterator) {
|
||||
using iterator_type = typename entt::sparse_set<std::uint64_t>::iterator_type;
|
||||
|
||||
@@ -397,8 +426,7 @@ TEST(SparseSetWithType, Functionalities) {
|
||||
other = std::move(set);
|
||||
}
|
||||
|
||||
TEST(SparseSetWithType, FunctionalitiesEmptyType) {
|
||||
struct empty_type {};
|
||||
TEST(SparseSetWithType, EmptyType) {
|
||||
entt::sparse_set<std::uint64_t, empty_type> set;
|
||||
|
||||
ASSERT_EQ(&set.construct(42), &set.construct(99));
|
||||
@@ -407,6 +435,66 @@ TEST(SparseSetWithType, FunctionalitiesEmptyType) {
|
||||
ASSERT_EQ(std::as_const(set).try_get(42), std::as_const(set).try_get(99));
|
||||
}
|
||||
|
||||
TEST(SparseSetWithType, ConstructMany) {
|
||||
entt::sparse_set<std::uint64_t, int> set;
|
||||
entt::sparse_set<std::uint64_t>::entity_type entities[2];
|
||||
|
||||
entities[0] = 3;
|
||||
entities[1] = 42;
|
||||
|
||||
set.reserve(4);
|
||||
set.construct(12, 21);
|
||||
auto *component = set.construct(std::begin(entities), std::end(entities), 43);
|
||||
set.construct(24, 42);
|
||||
|
||||
ASSERT_TRUE(set.has(entities[0]));
|
||||
ASSERT_TRUE(set.has(entities[1]));
|
||||
ASSERT_FALSE(set.has(0));
|
||||
ASSERT_FALSE(set.has(9));
|
||||
ASSERT_TRUE(set.has(12));
|
||||
ASSERT_TRUE(set.has(24));
|
||||
|
||||
ASSERT_FALSE(set.empty());
|
||||
ASSERT_EQ(set.size(), 4u);
|
||||
ASSERT_EQ(set.get(12), 21);
|
||||
ASSERT_EQ(set.get(entities[0]), 0);
|
||||
ASSERT_EQ(set.get(entities[1]), 0);
|
||||
ASSERT_EQ(set.get(24), 42);
|
||||
|
||||
component[0] = 1;
|
||||
component[1] = 2;
|
||||
|
||||
ASSERT_EQ(set.get(entities[0]), 1);
|
||||
ASSERT_EQ(set.get(entities[1]), 2);
|
||||
}
|
||||
|
||||
TEST(SparseSetWithType, ConstructManyEmptyType) {
|
||||
entt::sparse_set<std::uint64_t, empty_type> set;
|
||||
entt::sparse_set<std::uint64_t>::entity_type entities[2];
|
||||
|
||||
entities[0] = 3;
|
||||
entities[1] = 42;
|
||||
|
||||
set.reserve(4);
|
||||
set.construct(12);
|
||||
auto *component = set.construct(std::begin(entities), std::end(entities), 43);
|
||||
set.construct(24);
|
||||
|
||||
ASSERT_TRUE(set.has(entities[0]));
|
||||
ASSERT_TRUE(set.has(entities[1]));
|
||||
ASSERT_FALSE(set.has(0));
|
||||
ASSERT_FALSE(set.has(9));
|
||||
ASSERT_TRUE(set.has(12));
|
||||
ASSERT_TRUE(set.has(24));
|
||||
|
||||
ASSERT_FALSE(set.empty());
|
||||
ASSERT_EQ(set.size(), 4u);
|
||||
ASSERT_EQ(&set.get(entities[0]), &set.get(entities[1]));
|
||||
ASSERT_EQ(&set.get(entities[0]), &set.get(12));
|
||||
ASSERT_EQ(&set.get(entities[0]), &set.get(24));
|
||||
ASSERT_EQ(&set.get(entities[0]), component);
|
||||
}
|
||||
|
||||
TEST(SparseSetWithType, AggregatesMustWork) {
|
||||
struct aggregate_type { int value; };
|
||||
// the goal of this test is to enforce the requirements for aggregate types
|
||||
@@ -509,7 +597,6 @@ TEST(SparseSetWithType, ConstIterator) {
|
||||
}
|
||||
|
||||
TEST(SparseSetWithType, IteratorEmptyType) {
|
||||
struct empty_type {};
|
||||
using iterator_type = typename entt::sparse_set<std::uint64_t, empty_type>::iterator_type;
|
||||
entt::sparse_set<std::uint64_t, empty_type> set;
|
||||
set.construct(3);
|
||||
@@ -557,7 +644,6 @@ TEST(SparseSetWithType, IteratorEmptyType) {
|
||||
}
|
||||
|
||||
TEST(SparseSetWithType, ConstIteratorEmptyType) {
|
||||
struct empty_type {};
|
||||
using iterator_type = typename entt::sparse_set<std::uint64_t, empty_type>::const_iterator_type;
|
||||
entt::sparse_set<std::uint64_t, empty_type> set;
|
||||
set.construct(3);
|
||||
@@ -621,7 +707,6 @@ TEST(SparseSetWithType, Raw) {
|
||||
}
|
||||
|
||||
TEST(SparseSetWithType, RawEmptyType) {
|
||||
struct empty_type {};
|
||||
entt::sparse_set<std::uint64_t, empty_type> set;
|
||||
|
||||
set.construct(3);
|
||||
@@ -933,7 +1018,6 @@ TEST(SparseSetWithType, RespectUnordered) {
|
||||
}
|
||||
|
||||
TEST(SparseSetWithType, RespectOverlapEmptyType) {
|
||||
struct empty_type {};
|
||||
entt::sparse_set<std::uint64_t, empty_type> lhs;
|
||||
entt::sparse_set<std::uint64_t, empty_type> rhs;
|
||||
|
||||
@@ -1050,7 +1134,7 @@ TEST(SparseSetWithType, ConstructorExceptionDoesNotAddToSet) {
|
||||
struct throwing_component {
|
||||
struct constructor_exception: std::exception {};
|
||||
|
||||
throwing_component() { throw constructor_exception{}; }
|
||||
[[noreturn]] throwing_component() { throw constructor_exception{}; }
|
||||
|
||||
// necessary to avoid the short-circuit construct() logic for empty objects
|
||||
int data;
|
||||
|
||||
Reference in New Issue
Block a user