instance-less pools for empty components

This commit is contained in:
Michele Caini
2019-05-09 00:09:04 +02:00
parent 3a3a5dc071
commit 150b83b4f3
11 changed files with 402 additions and 367 deletions

View File

@@ -76,10 +76,17 @@ class basic_registry {
{}
template<typename... Args>
Component & assign(basic_registry &registry, const Entity entt, Args &&... args) {
auto &component = storage<Entity, Component>::construct(entt, std::forward<Args>(args)...);
on_construct.publish(registry, entt, component);
return component;
decltype(auto) assign(basic_registry &registry, const Entity entt, Args &&... args) {
if constexpr(std::is_empty_v<Component>) {
Component component{std::forward<Args>(args)...};
storage<Entity, Component>::construct(entt);
on_construct.publish(registry, entt, component);
return std::move(component);
} else {
auto &component = storage<Entity, Component>::construct(entt, std::forward<Args>(args)...);
on_construct.publish(registry, entt, component);
return component;
}
}
template<typename It>
@@ -87,9 +94,15 @@ class basic_registry {
auto *component = storage<Entity, Component>::batch(first, last);
if(!on_construct.empty()) {
std::for_each(first, last, [&registry, component, this](const auto entt) mutable {
on_construct.publish(registry, entt, *(component++));
});
if constexpr(std::is_empty_v<Component>) {
std::for_each(first, last, [&registry, component = Component{}, this](const auto entt) {
on_construct.publish(registry, entt, component);
});
} else {
std::for_each(first, last, [&registry, component, this](const auto entt) mutable {
on_construct.publish(registry, entt, *(component++));
});
}
}
return component;
@@ -101,10 +114,15 @@ class basic_registry {
}
template<typename... Args>
Component & replace(basic_registry &registry, const Entity entt, Args &&... args) {
decltype(auto) replace(basic_registry &registry, const Entity entt, [[maybe_unused]] Args &&... args) {
Component component{std::forward<Args>(args)...};
on_replace.publish(registry, entt, component);
return (storage<Entity, Component>::get(entt) = std::move(component));
if constexpr(std::is_empty_v<Component>) {
return std::move(component);
} else {
return (storage<Entity, Component>::get(entt) = std::move(component));
}
}
};
@@ -145,25 +163,21 @@ class basic_registry {
void maybe_valid_if(basic_registry &reg, const Entity entt, const Args &...) {
const auto cpools = std::make_tuple(reg.pool<Owned>()...);
auto construct = [&cpools, entt, this]() {
const auto pos = this->owned++;
(std::swap(std::get<pool_type<Owned> *>(cpools)->get(entt), std::get<pool_type<Owned> *>(cpools)->raw()[pos]), ...);
(std::get<pool_type<Owned> *>(cpools)->swap(std::get<pool_type<Owned> *>(cpools)->sparse_set<Entity>::get(entt), pos), ...);
};
if constexpr(std::disjunction_v<std::is_same<Owned, Component>..., std::is_same<Get, Component>...>) {
if(((std::is_same_v<Component, Owned> || std::get<pool_type<Owned> *>(cpools)->has(entt)) && ...)
&& ((std::is_same_v<Component, Get> || reg.pool<Get>()->has(entt)) && ...)
&& !(reg.pool<Exclude>()->has(entt) || ...))
{
construct();
const auto pos = this->owned++;
(reg.swap<Owned>(0, std::get<pool_type<Owned> *>(cpools), entt, pos), ...);
}
} else if constexpr(std::disjunction_v<std::is_same<Exclude, Component>...>) {
if((std::get<pool_type<Owned> *>(cpools)->has(entt) && ...)
&& (reg.pool<Get>()->has(entt) && ...)
&& ((std::is_same_v<Exclude, Component> || !reg.pool<Exclude>()->has(entt)) && ...))
{
construct();
const auto pos = this->owned++;
(reg.swap<Owned>(0, std::get<pool_type<Owned> *>(cpools), entt, pos), ...);
}
}
}
@@ -174,8 +188,7 @@ class basic_registry {
if(std::get<0>(cpools)->has(entt) && std::get<0>(cpools)->sparse_set<Entity>::get(entt) < this->owned) {
const auto pos = --this->owned;
(std::swap(std::get<pool_type<Owned> *>(cpools)->get(entt), std::get<pool_type<Owned> *>(cpools)->raw()[pos]), ...);
(std::get<pool_type<Owned> *>(cpools)->swap(std::get<pool_type<Owned> *>(cpools)->sparse_set<Entity>::get(entt), pos), ...);
(reg.swap<Owned>(0, std::get<pool_type<Owned> *>(cpools), entt, pos), ...);
}
}
};
@@ -217,6 +230,18 @@ class basic_registry {
++available;
}
template<typename Component>
inline auto swap(int, pool_type<Component> *cpool, const Entity entt, const std::size_t pos)
-> decltype(std::swap(cpool->get(entt), cpool->get(cpool->data()[pos])), void()) {
std::swap(cpool->get(entt), cpool->get(cpool->data()[pos]));
cpool->swap(cpool->sparse_set<Entity>::get(entt), pos);
}
template<typename Component>
inline void swap(char, pool_type<Component> *cpool, const Entity entt, const std::size_t pos) {
cpool->swap(cpool->sparse_set<Entity>::get(entt), pos);
}
template<typename Component>
inline const auto * pool() const ENTT_NOEXCEPT {
const auto ctype = type<Component>();
@@ -694,7 +719,7 @@ public:
* @return A reference to the newly created component.
*/
template<typename Component, typename... Args>
Component & assign(const entity_type entity, Args &&... args) {
decltype(auto) assign(const entity_type entity, [[maybe_unused]] Args &&... args) {
ENTT_ASSERT(valid(entity));
return assure<Component>()->assign(*this, entity, std::forward<Args>(args)...);
}
@@ -797,7 +822,7 @@ public:
* @return Reference to the component owned by the entity.
*/
template<typename Component, typename... Args>
Component & get_or_assign(const entity_type entity, Args &&... args) ENTT_NOEXCEPT {
decltype(auto) get_or_assign(const entity_type entity, Args &&... args) ENTT_NOEXCEPT {
ENTT_ASSERT(valid(entity));
auto *cpool = assure<Component>();
auto *comp = cpool->try_get(entity);
@@ -859,7 +884,7 @@ public:
* @return A reference to the newly created component.
*/
template<typename Component, typename... Args>
Component & replace(const entity_type entity, Args &&... args) {
decltype(auto) replace(const entity_type entity, Args &&... args) {
return pool<Component>()->replace(*this, entity, std::forward<Args>(args)...);
}
@@ -886,7 +911,7 @@ public:
* @return A reference to the newly created component.
*/
template<typename Component, typename... Args>
Component & assign_or_replace(const entity_type entity, Args &&... args) {
decltype(auto) assign_or_replace(const entity_type entity, Args &&... args) {
auto *cpool = assure<Component>();
return cpool->has(entity) ? cpool->replace(*this, entity, std::forward<Args>(args)...) : cpool->assign(*this, entity, std::forward<Args>(args)...);
}
@@ -1290,6 +1315,7 @@ public:
inline entt::basic_group<Entity, get_t<Get...>, Owned...> group(get_t<Get...>, exclude_t<Exclude...> = {}) {
static_assert(sizeof...(Owned) + sizeof...(Get) > 0);
static_assert(sizeof...(Owned) + sizeof...(Get) + sizeof...(Exclude) > 1);
using handler_type = group_handler<type_list<Exclude...>, type_list<Get...>, Owned...>;
const std::size_t extent[] = { sizeof...(Owned), sizeof...(Get), sizeof...(Exclude) };
@@ -1336,7 +1362,7 @@ public:
});
// we cannot iterate backwards because we want to leave behind valid entities in case of owned types
std::for_each(cpool->data(), cpool->data() + cpool->size(), [curr, &cpools](const auto entity) {
std::for_each(cpool->data(), cpool->data() + cpool->size(), [curr, &cpools, this](const auto entity) {
if((std::get<pool_type<Owned> *>(cpools)->has(entity) && ...)
&& (std::get<pool_type<Get> *>(cpools)->has(entity) && ...)
&& !(std::get<pool_type<Exclude> *>(cpools)->has(entity) || ...))
@@ -1345,8 +1371,7 @@ public:
curr->construct(entity);
} else {
const auto pos = curr->owned++;
(std::swap(std::get<pool_type<Owned> *>(cpools)->get(entity), std::get<pool_type<Owned> *>(cpools)->raw()[pos]), ...);
(std::get<pool_type<Owned> *>(cpools)->swap(std::get<pool_type<Owned> *>(cpools)->sparse_set<Entity>::get(entity), pos), ...);
(swap<Owned>(0, std::get<pool_type<Owned> *>(cpools), entity, pos), ...);
}
}
});

View File

@@ -226,18 +226,19 @@ class basic_snapshot_loader {
archive(length);
while(length--) {
static constexpr auto destroyed = false;
Entity entt{};
Type instance{};
if constexpr(std::is_empty_v<Type>) {
archive(entt);
force(*reg, entt, destroyed);
reg->template assign<Type>(args..., entt);
} else {
Type instance{};
archive(entt, instance);
force(*reg, entt, destroyed);
reg->template assign<Type>(args..., entt, std::as_const(instance));
}
static constexpr auto destroyed = false;
force(*reg, entt, destroyed);
reg->template assign<Type>(args..., entt, std::as_const(instance));
}
}
@@ -405,24 +406,25 @@ class basic_continuous_loader {
}
}
template<typename Other, typename Archive, typename Func, typename... Type, typename... Member>
void assign(Archive &archive, Func func, Member Type:: *... member) {
template<typename Other, typename Archive, typename... Type, typename... Member>
void assign(Archive &archive, [[maybe_unused]] Member Type:: *... member) {
Entity length{};
archive(length);
while(length--) {
Entity entt{};
Other instance{};
if constexpr(std::is_empty_v<Other>) {
archive(entt);
restore(entt);
reg->template assign_or_replace<Other>(map(entt));
} else {
Other instance{};
archive(entt, instance);
(update(instance, member), ...);
restore(entt);
reg->template assign_or_replace<Other>(map(entt), std::as_const(instance));
}
restore(entt);
(update(instance, member), ...);
func(map(entt), instance);
}
}
@@ -497,12 +499,8 @@ public:
*/
template<typename... Component, typename Archive, typename... Type, typename... Member>
basic_continuous_loader & component(Archive &archive, Member Type:: *... member) {
auto apply = [this](const auto entt, const auto &component) {
reg->template assign_or_replace<std::decay_t<decltype(component)>>(entt, component);
};
(reset<Component>(), ...);
(assign<Component>(archive, apply, member...), ...);
(assign<Component>(archive, member...), ...);
return *this;
}

View File

@@ -61,9 +61,9 @@ class sparse_set {
public:
using difference_type = index_type;
using value_type = const Entity;
using pointer = value_type *;
using reference = value_type &;
using value_type = Entity;
using pointer = const value_type *;
using reference = const value_type &;
using iterator_category = std::random_access_iterator_tag;
iterator() ENTT_NOEXCEPT = default;

View File

@@ -22,7 +22,7 @@ namespace entt {
* @brief Basic storage implementation.
*
* This class is a refinement of a sparse set that associates an object to an
* entity. The main purpose of this class is to use sparse sets to store
* entity. The main purpose of this class is to extend sparse sets to store
* components in a registry. It guarantees fast access both to the elements and
* to the entities.
*
@@ -36,19 +36,24 @@ namespace entt {
* iterate directly the internal packed array (see `raw` and `size` member
* functions for that). Use `begin` and `end` instead.
*
* @warning
* Empty types aren't explicitly instantiated. Temporary objects are returned in
* place of the instances of the components and raw access isn't available for
* them.
*
* @sa sparse_set<Entity>
*
* @tparam Entity A valid entity type (see entt_traits for more details).
* @tparam Type Type of objects assigned to the entities.
*/
template<typename Entity, typename Type>
class storage: public sparse_set<Entity> {
template<typename Entity, typename Type, typename = std::void_t<>>
class basic_storage: public sparse_set<Entity> {
using underlying_type = sparse_set<Entity>;
using traits_type = entt_traits<Entity>;
template<bool Const, bool = std::is_empty_v<Type>>
template<bool Const>
class iterator {
friend class storage<Entity, Type>;
friend class basic_storage<Entity, Type>;
using instance_type = std::conditional_t<Const, const std::vector<Type>, std::vector<Type>>;
using index_type = typename traits_type::difference_type;
@@ -59,9 +64,9 @@ class storage: public sparse_set<Entity> {
public:
using difference_type = index_type;
using value_type = std::conditional_t<Const, const Type, Type>;
using pointer = value_type *;
using reference = value_type &;
using value_type = Type;
using pointer = std::conditional_t<Const, const value_type *, value_type *>;
using reference = std::conditional_t<Const, const value_type &, value_type &>;
using iterator_category = std::random_access_iterator_tag;
iterator() ENTT_NOEXCEPT = default;
@@ -148,106 +153,6 @@ class storage: public sparse_set<Entity> {
index_type index;
};
template<bool Const>
class iterator<Const, true> {
friend class storage<Entity, Type>;
using instance_type = std::conditional_t<Const, const Type, Type>;
using index_type = typename traits_type::difference_type;
iterator(instance_type *ref, const index_type idx) ENTT_NOEXCEPT
: instance{ref}, index{idx}
{}
public:
using difference_type = index_type;
using value_type = std::conditional_t<Const, const Type, Type>;
using pointer = value_type *;
using reference = value_type &;
using iterator_category = std::random_access_iterator_tag;
iterator() ENTT_NOEXCEPT = default;
iterator & operator++() ENTT_NOEXCEPT {
return --index, *this;
}
iterator operator++(int) ENTT_NOEXCEPT {
iterator orig = *this;
return ++(*this), orig;
}
iterator & operator--() ENTT_NOEXCEPT {
return ++index, *this;
}
iterator operator--(int) ENTT_NOEXCEPT {
iterator orig = *this;
return --(*this), orig;
}
iterator & operator+=(const difference_type value) ENTT_NOEXCEPT {
index -= value;
return *this;
}
iterator operator+(const difference_type value) const ENTT_NOEXCEPT {
return iterator{instance, index-value};
}
inline iterator & operator-=(const difference_type value) ENTT_NOEXCEPT {
return (*this += -value);
}
inline iterator operator-(const difference_type value) const ENTT_NOEXCEPT {
return (*this + -value);
}
difference_type operator-(const iterator &other) const ENTT_NOEXCEPT {
return other.index - index;
}
reference operator[](const difference_type) const ENTT_NOEXCEPT {
return *instance;
}
bool operator==(const iterator &other) const ENTT_NOEXCEPT {
return other.index == index;
}
inline bool operator!=(const iterator &other) const ENTT_NOEXCEPT {
return !(*this == other);
}
bool operator<(const iterator &other) const ENTT_NOEXCEPT {
return index > other.index;
}
bool operator>(const iterator &other) const ENTT_NOEXCEPT {
return index < other.index;
}
inline bool operator<=(const iterator &other) const ENTT_NOEXCEPT {
return !(*this > other);
}
inline bool operator>=(const iterator &other) const ENTT_NOEXCEPT {
return !(*this < other);
}
pointer operator->() const ENTT_NOEXCEPT {
return instance;
}
inline reference operator*() const ENTT_NOEXCEPT {
return *operator->();
}
private:
instance_type *instance;
index_type index;
};
public:
/*! @brief Type of the objects associated with the entities. */
using object_type = Type;
@@ -270,25 +175,13 @@ public:
*/
void reserve(const size_type cap) override {
underlying_type::reserve(cap);
if constexpr(!std::is_empty_v<object_type>) {
instances.reserve(cap);
}
instances.reserve(cap);
}
/**
* @brief Requests the removal of unused capacity.
*
* @note
* Empty components aren't explicitly instantiated. Only one instance of the
* given type is created. Therefore, this function does nothing.
*/
/*! @brief Requests the removal of unused capacity. */
void shrink_to_fit() override {
underlying_type::shrink_to_fit();
if constexpr(!std::is_empty_v<object_type>) {
instances.shrink_to_fit();
}
instances.shrink_to_fit();
}
/**
@@ -304,19 +197,10 @@ public:
* performance boost but less guarantees. Use `begin` and `end` if you want
* to iterate the storage in the expected order.
*
* @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.
*
* @return A pointer to the array of objects.
*/
const object_type * raw() const ENTT_NOEXCEPT {
if constexpr(std::is_empty_v<object_type>) {
return &instances;
} else {
return instances.data();
}
return instances.data();
}
/*! @copydoc raw */
@@ -392,13 +276,8 @@ public:
* @param entt A valid entity identifier.
* @return The object associated with the entity.
*/
const object_type & get([[maybe_unused]] const entity_type entt) const ENTT_NOEXCEPT {
if constexpr(std::is_empty_v<object_type>) {
ENTT_ASSERT(underlying_type::has(entt));
return instances;
} else {
return instances[underlying_type::get(entt)];
}
const object_type & get(const entity_type entt) const ENTT_NOEXCEPT {
return instances[underlying_type::get(entt)];
}
/*! @copydoc get */
@@ -412,11 +291,7 @@ public:
* @return The object associated with the entity, if any.
*/
const object_type * try_get(const entity_type entt) const ENTT_NOEXCEPT {
if constexpr(std::is_empty_v<object_type>) {
return underlying_type::has(entt) ? &instances : nullptr;
} else {
return underlying_type::has(entt) ? (instances.data() + underlying_type::get(entt)) : nullptr;
}
return underlying_type::has(entt) ? (instances.data() + underlying_type::get(entt)) : nullptr;
}
/*! @copydoc try_get */
@@ -443,21 +318,16 @@ public:
* @return The object associated with the entity.
*/
template<typename... Args>
object_type & construct(const entity_type entt, [[maybe_unused]] Args &&... args) {
if constexpr(std::is_empty_v<object_type>) {
underlying_type::construct(entt);
return instances;
object_type & construct(const entity_type entt, Args &&... args) {
if constexpr(std::is_aggregate_v<object_type>) {
instances.emplace_back(Type{std::forward<Args>(args)...});
} else {
if constexpr(std::is_aggregate_v<object_type>) {
instances.emplace_back(Type{std::forward<Args>(args)...});
} else {
instances.emplace_back(std::forward<Args>(args)...);
}
// entity goes after component in case constructor throws
underlying_type::construct(entt);
return instances.back();
instances.emplace_back(std::forward<Args>(args)...);
}
// entity goes after component in case constructor throws
underlying_type::construct(entt);
return instances.back();
}
/**
@@ -466,11 +336,6 @@ public:
*
* 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 storage
* results in undefined behavior.<br/>
@@ -485,17 +350,12 @@ public:
*/
template<typename It>
object_type * batch(It first, It last) {
if constexpr(std::is_empty_v<object_type>) {
underlying_type::batch(first, last);
return &instances;
} else {
static_assert(std::is_default_constructible_v<object_type>);
const auto skip = instances.size();
instances.insert(instances.end(), last-first, {});
// entity goes after component in case constructor throws
underlying_type::batch(first, last);
return instances.data() + skip;
}
static_assert(std::is_default_constructible_v<object_type>);
const auto skip = instances.size();
instances.insert(instances.end(), last-first, {});
// entity goes after component in case constructor throws
underlying_type::batch(first, last);
return instances.data() + skip;
}
/**
@@ -510,16 +370,13 @@ public:
* @param entt A valid entity identifier.
*/
void destroy(const entity_type entt) override {
if constexpr(!std::is_empty_v<object_type>) {
std::swap(instances[underlying_type::get(entt)], instances.back());
instances.pop_back();
}
std::swap(instances[underlying_type::get(entt)], instances.back());
instances.pop_back();
underlying_type::destroy(entt);
}
/**
* @brief Sort components according to the given comparison function.
* @brief Sort instances according to the given comparison function.
*
* Sort the elements so that iterating the storage with a couple of
* iterators returns them in the expected order. See `begin` and `end` for
@@ -549,10 +406,6 @@ public:
* parameters to this member function.
*
* @note
* Empty components aren't explicitly instantiated. Therefore, the
* comparison function must necessarily accept entity identifiers.
*
* @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.
@@ -588,11 +441,7 @@ public:
while(curr != next) {
const auto lhs = copy[curr];
const auto rhs = copy[next];
if constexpr(!std::is_empty_v<object_type>) {
std::swap(instances[lhs], instances[rhs]);
}
std::swap(instances[lhs], instances[rhs]);
underlying_type::swap(lhs, rhs);
copy[curr] = curr;
curr = next;
@@ -602,14 +451,13 @@ public:
}
/**
* @brief Sort components according to the order of the entities in another
* @brief Sort instances according to the order of the entities in another
* sparse set.
*
* Entities that are part of both the storage are ordered internally
* according to the order they have in `other`. All the other entities goes
* to the end of the list and there are no guarantess on their order.
* Components are sorted according to the entities to which they
* belong.<br/>
* Instances are sorted according to the entities to which they belong.<br/>
* In other terms, this function can be used to impose the same order on two
* sets by using one of them as a master and the other one as a slave.
*
@@ -625,47 +473,253 @@ public:
* @param other The sparse sets that imposes the order of the entities.
*/
void respect(const sparse_set<Entity> &other) ENTT_NOEXCEPT override {
if constexpr(std::is_empty_v<object_type>) {
underlying_type::respect(other);
} else {
const auto to = other.end();
auto from = other.begin();
const auto to = other.end();
auto from = other.begin();
size_type pos = underlying_type::size() - 1;
const auto *local = underlying_type::data();
size_type pos = underlying_type::size() - 1;
const auto *local = underlying_type::data();
while(pos && from != to) {
const auto curr = *from;
while(pos && from != to) {
const auto curr = *from;
if(underlying_type::has(curr)) {
if(curr != *(local + pos)) {
auto candidate = underlying_type::get(curr);
std::swap(instances[pos], instances[candidate]);
underlying_type::swap(pos, candidate);
}
--pos;
if(underlying_type::has(curr)) {
if(curr != *(local + pos)) {
auto candidate = underlying_type::get(curr);
std::swap(instances[pos], instances[candidate]);
underlying_type::swap(pos, candidate);
}
++from;
--pos;
}
++from;
}
}
/*! @brief Resets a storage. */
void reset() override {
underlying_type::reset();
if constexpr(!std::is_empty_v<object_type>) {
instances.clear();
}
instances.clear();
}
private:
std::conditional_t<std::is_empty_v<object_type>, object_type, std::vector<object_type>> instances;
std::vector<object_type> instances;
};
/*! @copydoc basic_storage */
template<typename Entity, typename Type>
class basic_storage<Entity, Type, std::enable_if_t<std::is_empty_v<Type>>>: public sparse_set<Entity> {
using underlying_type = sparse_set<Entity>;
using traits_type = entt_traits<Entity>;
class iterator {
friend class basic_storage<Entity, Type>;
using index_type = typename traits_type::difference_type;
iterator(const index_type idx) ENTT_NOEXCEPT
: index{idx}
{}
public:
using difference_type = index_type;
using value_type = Type;
using pointer = const value_type *;
using reference = value_type;
using iterator_category = std::input_iterator_tag;
iterator() ENTT_NOEXCEPT = default;
iterator & operator++() ENTT_NOEXCEPT {
return --index, *this;
}
iterator operator++(int) ENTT_NOEXCEPT {
iterator orig = *this;
return ++(*this), orig;
}
iterator & operator--() ENTT_NOEXCEPT {
return ++index, *this;
}
iterator operator--(int) ENTT_NOEXCEPT {
iterator orig = *this;
return --(*this), orig;
}
iterator & operator+=(const difference_type value) ENTT_NOEXCEPT {
index -= value;
return *this;
}
iterator operator+(const difference_type value) const ENTT_NOEXCEPT {
return iterator{index-value};
}
inline iterator & operator-=(const difference_type value) ENTT_NOEXCEPT {
return (*this += -value);
}
inline iterator operator-(const difference_type value) const ENTT_NOEXCEPT {
return (*this + -value);
}
difference_type operator-(const iterator &other) const ENTT_NOEXCEPT {
return other.index - index;
}
reference operator[](const difference_type) const ENTT_NOEXCEPT {
return {};
}
bool operator==(const iterator &other) const ENTT_NOEXCEPT {
return other.index == index;
}
inline bool operator!=(const iterator &other) const ENTT_NOEXCEPT {
return !(*this == other);
}
bool operator<(const iterator &other) const ENTT_NOEXCEPT {
return index > other.index;
}
bool operator>(const iterator &other) const ENTT_NOEXCEPT {
return index < other.index;
}
inline bool operator<=(const iterator &other) const ENTT_NOEXCEPT {
return !(*this > other);
}
inline bool operator>=(const iterator &other) const ENTT_NOEXCEPT {
return !(*this < other);
}
pointer operator->() const ENTT_NOEXCEPT {
return nullptr;
}
inline reference operator*() const ENTT_NOEXCEPT {
return {};
}
private:
index_type index;
};
public:
/*! @brief Type of the objects associated with the entities. */
using object_type = Type;
/*! @brief Underlying entity identifier. */
using entity_type = typename underlying_type::entity_type;
/*! @brief Unsigned integer type. */
using size_type = typename underlying_type::size_type;
/*! @brief Random access iterator type. */
using iterator_type = iterator;
/**
* @brief Direct access to the array of objects.
*
* @note
* Empty types aren't explicitly instantiated. Therefore, this function
* always returns a null pointer.
*
* @return A pointer to the array of objects.
*/
const object_type * raw() const ENTT_NOEXCEPT {
return nullptr;
}
/**
* @brief Returns an iterator to the beginning.
*
* The returned iterator points to the first instance of the given type. If
* the storage is empty, the returned iterator will be equal to `end()`.
*
* @note
* Input iterators stay true to the order imposed by a call to either `sort`
* or `respect`.
*
* @return An iterator to the first instance of the given type.
*/
iterator_type cbegin() const ENTT_NOEXCEPT {
const typename traits_type::difference_type pos = underlying_type::size();
return iterator_type{pos};
}
/*! @copydoc cbegin */
inline iterator_type begin() const ENTT_NOEXCEPT {
return cbegin();
}
/**
* @brief Returns an iterator to the end.
*
* The returned iterator points to the element following the last instance
* of the given type. Attempting to dereference the returned iterator
* results in undefined behavior.
*
* @note
* Input iterators stay true to the order imposed by a call to either `sort`
* or `respect`.
*
* @return An iterator to the element following the last instance of the
* given type.
*/
iterator_type cend() const ENTT_NOEXCEPT {
return iterator_type{};
}
/*! @copydoc cend */
inline iterator_type end() const ENTT_NOEXCEPT {
return cend();
}
/**
* @brief Returns the object associated with an entity.
*
* @note
* Empty types aren't explicitly instantiated. Therefore, this function
* always returns a temporary object.
*
* @warning
* Attempting to use an entity that doesn't belong to the storage results in
* undefined behavior.<br/>
* An assertion will abort the execution at runtime in debug mode if the
* storage doesn't contain the given entity.
*
* @param entt A valid entity identifier.
* @return The object associated with the entity.
*/
object_type get([[maybe_unused]] const entity_type entt) const ENTT_NOEXCEPT {
ENTT_ASSERT(underlying_type::has(entt));
return {};
}
/**
* @brief Returns a pointer to the object associated with an entity, if any.
*
* @note
* Empty types aren't explicitly instantiated. Therefore, this function
* always returns a null pointer.
*
* @param entt A valid entity identifier.
* @return The object associated with the entity, if any.
*/
const object_type * try_get([[maybe_unused]] const entity_type entt) const ENTT_NOEXCEPT {
ENTT_ASSERT(underlying_type::has(entt));
return nullptr;
}
};
/*! @copydoc basic_storage */
template<typename Entity, typename Type>
struct storage: basic_storage<Entity, Type> {};
}

View File

@@ -161,20 +161,28 @@ class basic_view {
return other;
}
template<typename Comp, typename Other>
inline decltype(auto) get([[maybe_unused]] component_iterator_type<Comp> it, [[maybe_unused]] pool_type<Other> *cpool, [[maybe_unused]] const Entity entt) const ENTT_NOEXCEPT {
if constexpr(std::is_same_v<Comp, Other>) {
return *it;
} else {
return cpool->get(entt);
}
}
template<typename Comp, typename... Other, typename Func>
void each(type_list<Other...>, Func func) const {
const auto end = std::get<pool_type<Comp> *>(pools)->sparse_set<Entity>::end();
auto begin = std::get<pool_type<Comp> *>(pools)->sparse_set<Entity>::begin();
std::for_each(begin, end, [&func, raw = std::get<pool_type<Comp> *>(pools)->begin(), this](const auto entity) mutable {
std::tuple<component_iterator_type<Comp>, Other *...> pack;
std::get<component_iterator_type<Comp>>(pack) = raw++;
std::for_each(begin, end, [raw = std::get<pool_type<Comp> *>(pools)->begin(), &func, this](const auto entity) mutable {
auto curr = raw++;
if(((std::get<Other *>(pack) = std::get<pool_type<Other> *>(pools)->try_get(entity)) && ...)) {
if((std::get<pool_type<Other> *>(pools)->has(entity) && ...)) {
if constexpr(std::is_invocable_v<Func, std::add_lvalue_reference_t<Component>...>) {
func(*std::get<std::conditional_t<std::is_same_v<Comp, Component>, component_iterator_type<Component>, Component *>>(pack)...);
func(get<Comp, Component>(curr, std::get<pool_type<Component> *>(pools), entity)...);
} else {
func(entity, *std::get<std::conditional_t<std::is_same_v<Comp, Component>, component_iterator_type<Component>, Component *>>(pack)...);
func(entity, get<Comp, Component>(curr, std::get<pool_type<Component> *>(pools), entity)...);
}
}
});

View File

@@ -131,8 +131,7 @@ class meta_factory {
template<auto Member>
auto unregister_all(int)
-> decltype((internal::meta_info<Type>::type->*Member)->prop, void())
{
-> decltype((internal::meta_info<Type>::type->*Member)->prop, void()) {
while(internal::meta_info<Type>::type->*Member) {
auto node = internal::meta_info<Type>::type->*Member;
internal::meta_info<Type>::type->*Member = node->next;

View File

@@ -222,8 +222,7 @@ auto find_if(Op op, const Node *curr) ENTT_NOEXCEPT {
template<auto Member, typename Op>
auto find_if(Op op, const meta_type_node *node) ENTT_NOEXCEPT
-> decltype(find_if(op, node->*Member))
{
-> decltype(find_if(op, node->*Member)) {
decltype(find_if(op, node->*Member)) ret = nullptr;
if(node) {
@@ -311,8 +310,7 @@ class meta_any {
template<typename Type>
inline static auto compare(int, const Type &lhs, const Type &rhs)
-> decltype(lhs == rhs, bool{})
{
-> decltype(lhs == rhs, bool{}) {
return lhs == rhs;
}

View File

@@ -366,12 +366,11 @@ TEST(NonOwningGroup, EmptyAndNonEmptyTypes) {
ASSERT_TRUE(entity == e0 || entity == e1);
}
group.each([e0, e1](const auto entity, const int &, const empty_type &) {
group.each([e0, e1](const auto entity, const int &, empty_type) {
ASSERT_TRUE(entity == e0 || entity == e1);
});
ASSERT_EQ(group.size(), typename decltype(group)::size_type{2});
ASSERT_EQ(&group.get<empty_type>(e0), &group.get<empty_type>(e1));
}
TEST(NonOwningGroup, TrackEntitiesOnComponentDestruction) {
@@ -872,12 +871,11 @@ TEST(OwningGroup, EmptyAndNonEmptyTypes) {
ASSERT_TRUE(entity == e0 || entity == e1);
}
group.each([e0, e1](const auto entity, const empty_type &, const int &) {
group.each([e0, e1](const auto entity, empty_type, const int &) {
ASSERT_TRUE(entity == e0 || entity == e1);
});
ASSERT_EQ(group.size(), typename decltype(group)::size_type{2});
ASSERT_EQ(&group.get<empty_type>(e0), &group.get<empty_type>(e1));
}
TEST(OwningGroup, TrackEntitiesOnComponentDestruction) {

View File

@@ -248,7 +248,6 @@ TEST(Snapshot, Continuous) {
using storage_type = std::tuple<
std::queue<entt::entity>,
std::queue<a_component>,
std::queue<another_component>,
std::queue<what_a_component>,
std::queue<double>
@@ -432,10 +431,7 @@ TEST(Snapshot, MoreOnShrink) {
entt::continuous_loader loader{dst};
using storage_type = std::tuple<
std::queue<entt::entity>,
std::queue<a_component>
>;
using storage_type = std::tuple<std::queue<entt::entity>>;
storage_type storage;
output_archive<storage_type> output{storage};

View File

@@ -19,7 +19,7 @@ struct throwing_component {
int data;
};
TEST(SparseSetWithType, Functionalities) {
TEST(Storage, Functionalities) {
entt::storage<std::uint64_t, int> set;
set.reserve(42);
@@ -79,16 +79,23 @@ TEST(SparseSetWithType, Functionalities) {
other = std::move(set);
}
TEST(SparseSetWithType, EmptyType) {
TEST(Storage, EmptyType) {
entt::storage<std::uint64_t, empty_type> set;
ASSERT_EQ(&set.construct(42), &set.construct(99));
ASSERT_EQ(&set.get(42), set.try_get(42));
ASSERT_EQ(&set.get(42), &set.get(99));
set.construct(42);
set.construct(99);
ASSERT_TRUE(set.has(42));
ASSERT_TRUE(set.has(99));
ASSERT_EQ(set.try_get(42), nullptr);
ASSERT_EQ(std::as_const(set).try_get(42), std::as_const(set).try_get(99));
auto &&component = set.get(42);
ASSERT_TRUE((std::is_same_v<decltype(component), empty_type &&>));
}
TEST(SparseSetWithType, BatchAdd) {
TEST(Storage, BatchAdd) {
entt::storage<std::uint64_t, int> set;
entt::storage<std::uint64_t, int>::entity_type entities[2];
@@ -121,7 +128,7 @@ TEST(SparseSetWithType, BatchAdd) {
ASSERT_EQ(set.get(entities[1]), 2);
}
TEST(SparseSetWithType, BatchAddEmptyType) {
TEST(Storage, BatchAddEmptyType) {
entt::storage<std::uint64_t, empty_type> set;
entt::storage<std::uint64_t, empty_type>::entity_type entities[2];
@@ -130,7 +137,7 @@ TEST(SparseSetWithType, BatchAddEmptyType) {
set.reserve(4);
set.construct(12);
auto *component = set.batch(std::begin(entities), std::end(entities));
set.batch(std::begin(entities), std::end(entities));
set.construct(24);
ASSERT_TRUE(set.has(entities[0]));
@@ -142,26 +149,26 @@ TEST(SparseSetWithType, BatchAddEmptyType) {
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);
auto &&component = set.get(entities[0]);
ASSERT_TRUE((std::is_same_v<decltype(component), empty_type &&>));
}
TEST(SparseSetWithType, AggregatesMustWork) {
TEST(Storage, AggregatesMustWork) {
struct aggregate_type { int value; };
// the goal of this test is to enforce the requirements for aggregate types
entt::storage<std::uint64_t, aggregate_type>{}.construct(0, 42);
}
TEST(SparseSetWithType, TypesFromStandardTemplateLibraryMustWork) {
TEST(Storage, TypesFromStandardTemplateLibraryMustWork) {
// see #37 - this test shouldn't crash, that's all
entt::storage<std::uint64_t, std::unordered_set<int>> set;
set.construct(0).insert(42);
set.destroy(0);
}
TEST(SparseSetWithType, Iterator) {
TEST(Storage, Iterator) {
using iterator_type = typename entt::storage<std::uint64_t, boxed_int>::iterator_type;
entt::storage<std::uint64_t, boxed_int> set;
@@ -203,7 +210,7 @@ TEST(SparseSetWithType, Iterator) {
ASSERT_GE(end, set.end());
}
TEST(SparseSetWithType, ConstIterator) {
TEST(Storage, ConstIterator) {
using iterator_type = typename entt::storage<std::uint64_t, boxed_int>::const_iterator_type;
entt::storage<std::uint64_t, boxed_int> set;
@@ -245,7 +252,7 @@ TEST(SparseSetWithType, ConstIterator) {
ASSERT_GE(cend, set.cend());
}
TEST(SparseSetWithType, IteratorEmptyType) {
TEST(Storage, IteratorEmptyType) {
using iterator_type = typename entt::storage<std::uint64_t, empty_type>::iterator_type;
entt::storage<std::uint64_t, empty_type> set;
set.construct(3);
@@ -277,7 +284,7 @@ TEST(SparseSetWithType, IteratorEmptyType) {
ASSERT_EQ(end - (end - begin), set.begin());
ASSERT_EQ(end + (begin - end), set.begin());
ASSERT_EQ(&begin[0], set.begin().operator->());
ASSERT_EQ(set.begin().operator->(), nullptr);
ASSERT_LT(begin, end);
ASSERT_LE(begin, set.begin());
@@ -286,60 +293,12 @@ TEST(SparseSetWithType, IteratorEmptyType) {
ASSERT_GE(end, set.end());
set.construct(33);
auto &&component = *begin;
ASSERT_EQ(&*begin, &*(begin+1));
ASSERT_EQ(&begin[0], &begin[1]);
ASSERT_EQ(begin.operator->(), (end-1).operator->());
ASSERT_TRUE((std::is_same_v<decltype(component), empty_type &&>));
}
TEST(SparseSetWithType, ConstIteratorEmptyType) {
using iterator_type = typename entt::storage<std::uint64_t, empty_type>::const_iterator_type;
entt::storage<std::uint64_t, empty_type> set;
set.construct(3);
iterator_type cend{set.cbegin()};
iterator_type cbegin{};
cbegin = set.cend();
std::swap(cbegin, cend);
ASSERT_EQ(cbegin, set.cbegin());
ASSERT_EQ(cend, set.cend());
ASSERT_NE(cbegin, cend);
ASSERT_EQ(cbegin++, set.cbegin());
ASSERT_EQ(cbegin--, set.cend());
ASSERT_EQ(cbegin+1, set.cend());
ASSERT_EQ(cend-1, set.cbegin());
ASSERT_EQ(++cbegin, set.cend());
ASSERT_EQ(--cbegin, set.cbegin());
ASSERT_EQ(cbegin += 1, set.cend());
ASSERT_EQ(cbegin -= 1, set.cbegin());
ASSERT_EQ(cbegin + (cend - cbegin), set.cend());
ASSERT_EQ(cbegin - (cbegin - cend), set.cend());
ASSERT_EQ(cend - (cend - cbegin), set.cbegin());
ASSERT_EQ(cend + (cbegin - cend), set.cbegin());
ASSERT_EQ(&cbegin[0], set.cbegin().operator->());
ASSERT_LT(cbegin, cend);
ASSERT_LE(cbegin, set.cbegin());
ASSERT_GT(cend, cbegin);
ASSERT_GE(cend, set.cend());
set.construct(33);
ASSERT_EQ(&*cbegin, &*(cbegin+1));
ASSERT_EQ(&cbegin[0], &cbegin[1]);
ASSERT_EQ(cbegin.operator->(), (cend-1).operator->());
}
TEST(SparseSetWithType, Raw) {
TEST(Storage, Raw) {
entt::storage<std::uint64_t, int> set;
set.construct(3, 3);
@@ -355,7 +314,7 @@ TEST(SparseSetWithType, Raw) {
ASSERT_EQ(*(set.raw() + 2u), 9);
}
TEST(SparseSetWithType, RawEmptyType) {
TEST(Storage, RawEmptyType) {
entt::storage<std::uint64_t, empty_type> set;
set.construct(3);
@@ -364,7 +323,7 @@ TEST(SparseSetWithType, RawEmptyType) {
ASSERT_EQ(set.try_get(3), set.raw());
}
TEST(SparseSetWithType, SortOrdered) {
TEST(Storage, SortOrdered) {
entt::storage<std::uint64_t, boxed_int> set;
set.construct(12, boxed_int{12});
@@ -400,7 +359,7 @@ TEST(SparseSetWithType, SortOrdered) {
ASSERT_EQ(begin, end);
}
TEST(SparseSetWithType, SortReverse) {
TEST(Storage, SortReverse) {
entt::storage<std::uint64_t, boxed_int> set;
set.construct(12, boxed_int{1});
@@ -436,7 +395,7 @@ TEST(SparseSetWithType, SortReverse) {
ASSERT_EQ(begin, end);
}
TEST(SparseSetWithType, SortUnordered) {
TEST(Storage, SortUnordered) {
entt::storage<std::uint64_t, boxed_int> set;
set.construct(12, boxed_int{6});
@@ -472,7 +431,7 @@ TEST(SparseSetWithType, SortUnordered) {
ASSERT_EQ(begin, end);
}
TEST(SparseSetWithType, RespectDisjoint) {
TEST(Storage, RespectDisjoint) {
entt::storage<std::uint64_t, int> lhs;
entt::storage<std::uint64_t, int> rhs;
@@ -499,7 +458,7 @@ TEST(SparseSetWithType, RespectDisjoint) {
ASSERT_EQ(begin, end);
}
TEST(SparseSetWithType, RespectOverlap) {
TEST(Storage, RespectOverlap) {
entt::storage<std::uint64_t, int> lhs;
entt::storage<std::uint64_t, int> rhs;
@@ -528,7 +487,7 @@ TEST(SparseSetWithType, RespectOverlap) {
ASSERT_EQ(begin, end);
}
TEST(SparseSetWithType, RespectOrdered) {
TEST(Storage, RespectOrdered) {
entt::storage<std::uint64_t, int> lhs;
entt::storage<std::uint64_t, int> rhs;
@@ -574,7 +533,7 @@ TEST(SparseSetWithType, RespectOrdered) {
ASSERT_EQ(*(rhs.data() + 5u), 5u);
}
TEST(SparseSetWithType, RespectReverse) {
TEST(Storage, RespectReverse) {
entt::storage<std::uint64_t, int> lhs;
entt::storage<std::uint64_t, int> rhs;
@@ -620,7 +579,7 @@ TEST(SparseSetWithType, RespectReverse) {
ASSERT_EQ(*(rhs.data() + 5u), 5u);
}
TEST(SparseSetWithType, RespectUnordered) {
TEST(Storage, RespectUnordered) {
entt::storage<std::uint64_t, int> lhs;
entt::storage<std::uint64_t, int> rhs;
@@ -666,7 +625,7 @@ TEST(SparseSetWithType, RespectUnordered) {
ASSERT_EQ(*(rhs.data() + 5u), 5u);
}
TEST(SparseSetWithType, RespectOverlapEmptyType) {
TEST(Storage, RespectOverlapEmptyType) {
entt::storage<std::uint64_t, empty_type> lhs;
entt::storage<std::uint64_t, empty_type> rhs;
@@ -687,7 +646,7 @@ TEST(SparseSetWithType, RespectOverlapEmptyType) {
ASSERT_EQ(std::as_const(lhs).sparse_set<std::uint64_t>::get(42), 1u);
}
TEST(SparseSetWithType, CanModifyDuringIteration) {
TEST(Storage, CanModifyDuringIteration) {
entt::storage<std::uint64_t, int> set;
set.construct(0, 42);
@@ -703,7 +662,7 @@ TEST(SparseSetWithType, CanModifyDuringIteration) {
(void)entity;
}
TEST(SparseSetWithType, ReferencesGuaranteed) {
TEST(Storage, ReferencesGuaranteed) {
entt::storage<std::uint64_t, boxed_int> set;
set.construct(0, 0);
@@ -731,13 +690,13 @@ TEST(SparseSetWithType, ReferencesGuaranteed) {
ASSERT_EQ(set.get(1).value, 3);
}
TEST(SparseSetWithType, MoveOnlyComponent) {
TEST(Storage, MoveOnlyComponent) {
// the purpose is to ensure that move only components are always accepted
entt::storage<std::uint64_t, std::unique_ptr<int>> set;
(void)set;
}
TEST(SparseSetWithType, ConstructorExceptionDoesNotAddToSet) {
TEST(Storage, ConstructorExceptionDoesNotAddToSet) {
entt::storage<std::uint64_t, throwing_component> set;
try {

View File

@@ -368,7 +368,7 @@ TEST(Mod, Duktape) {
ASSERT_EQ(registry.view<position>().size(), 3u);
ASSERT_EQ(registry.view<renderable>().size(), 2u);
registry.view<duktape_runtime>().each([](auto, const duktape_runtime &runtime) {
registry.view<duktape_runtime>().each([](const duktape_runtime &runtime) {
ASSERT_EQ(runtime.components.size(), 2u);
});
@@ -387,7 +387,7 @@ TEST(Mod, Duktape) {
ASSERT_EQ(registry.view<position>().size(), 3u);
ASSERT_EQ(registry.view<renderable>().size(), 2u);
registry.view<position, renderable, duktape_runtime>().each([](auto, const position &position, const auto &...) {
registry.view<position, renderable, duktape_runtime>().each([](const position &position, auto &&...) {
ASSERT_EQ(position.x, -100.);
ASSERT_EQ(position.y, -100.);
});