#ifndef ENTT_ENTITY_REGISTRY_HPP #define ENTT_ENTITY_REGISTRY_HPP #include #include #include #include #include #include #include #include #include "../config/config.h" #include "../container/dense_map.hpp" #include "../core/algorithm.hpp" #include "../core/any.hpp" #include "../core/fwd.hpp" #include "../core/iterator.hpp" #include "../core/type_info.hpp" #include "../core/type_traits.hpp" #include "../core/utility.hpp" #include "component.hpp" #include "entity.hpp" #include "fwd.hpp" #include "group.hpp" #include "runtime_view.hpp" #include "sparse_set.hpp" #include "storage.hpp" #include "utility.hpp" #include "view.hpp" namespace entt { /** * @cond TURN_OFF_DOXYGEN * Internal details not to be documented. */ namespace internal { template class storage_proxy_iterator final { template friend class storage_proxy_iterator; using mapped_type = std::remove_reference_t()->second)>; public: using value_type = std::pair &>; using pointer = input_iterator_pointer; using reference = value_type; using difference_type = std::ptrdiff_t; using iterator_category = std::input_iterator_tag; storage_proxy_iterator() ENTT_NOEXCEPT : it{} {} storage_proxy_iterator(const It iter) ENTT_NOEXCEPT : it{iter} {} template && std::is_constructible_v>> storage_proxy_iterator(const storage_proxy_iterator &other) ENTT_NOEXCEPT : it{other.it} {} storage_proxy_iterator &operator++() ENTT_NOEXCEPT { return ++it, *this; } storage_proxy_iterator operator++(int) ENTT_NOEXCEPT { storage_proxy_iterator orig = *this; return ++(*this), orig; } storage_proxy_iterator &operator--() ENTT_NOEXCEPT { return --it, *this; } storage_proxy_iterator operator--(int) ENTT_NOEXCEPT { storage_proxy_iterator orig = *this; return operator--(), orig; } storage_proxy_iterator &operator+=(const difference_type value) ENTT_NOEXCEPT { it += value; return *this; } storage_proxy_iterator operator+(const difference_type value) const ENTT_NOEXCEPT { storage_proxy_iterator copy = *this; return (copy += value); } storage_proxy_iterator &operator-=(const difference_type value) ENTT_NOEXCEPT { return (*this += -value); } storage_proxy_iterator operator-(const difference_type value) const ENTT_NOEXCEPT { return (*this + -value); } [[nodiscard]] reference operator[](const difference_type value) const ENTT_NOEXCEPT { return {it[value].first, *it[value].second}; } [[nodiscard]] reference operator*() const ENTT_NOEXCEPT { return {it->first, *it->second}; } [[nodiscard]] pointer operator->() const ENTT_NOEXCEPT { return operator*(); } template friend std::ptrdiff_t operator-(const storage_proxy_iterator &, const storage_proxy_iterator &) ENTT_NOEXCEPT; template friend bool operator==(const storage_proxy_iterator &, const storage_proxy_iterator &) ENTT_NOEXCEPT; template friend bool operator<(const storage_proxy_iterator &, const storage_proxy_iterator &) ENTT_NOEXCEPT; private: It it; }; template [[nodiscard]] std::ptrdiff_t operator-(const storage_proxy_iterator &lhs, const storage_proxy_iterator &rhs) ENTT_NOEXCEPT { return lhs.it - rhs.it; } template [[nodiscard]] bool operator==(const storage_proxy_iterator &lhs, const storage_proxy_iterator &rhs) ENTT_NOEXCEPT { return lhs.it == rhs.it; } template [[nodiscard]] bool operator!=(const storage_proxy_iterator &lhs, const storage_proxy_iterator &rhs) ENTT_NOEXCEPT { return !(lhs == rhs); } template [[nodiscard]] bool operator<(const storage_proxy_iterator &lhs, const storage_proxy_iterator &rhs) ENTT_NOEXCEPT { return lhs.it < rhs.it; } template [[nodiscard]] bool operator>(const storage_proxy_iterator &lhs, const storage_proxy_iterator &rhs) ENTT_NOEXCEPT { return rhs < lhs; } template [[nodiscard]] bool operator<=(const storage_proxy_iterator &lhs, const storage_proxy_iterator &rhs) ENTT_NOEXCEPT { return !(lhs > rhs); } template [[nodiscard]] bool operator>=(const storage_proxy_iterator &lhs, const storage_proxy_iterator &rhs) ENTT_NOEXCEPT { return !(lhs < rhs); } struct registry_context { template Type &emplace_hint(const id_type id, Args &&...args) { return any_cast(data.try_emplace(id, std::in_place_type, std::forward(args)...).first->second); } template Type &emplace(Args &&...args) { return emplace_hint(type_id().hash(), std::forward(args)...); } template bool erase(const id_type id = type_id().hash()) { const auto it = data.find(id); return it != data.end() && it->second.type() == type_id() ? (data.erase(it), true) : false; } template [[nodiscard]] std::add_const_t &at(const id_type id = type_id().hash()) const { return any_cast &>(data.at(id)); } template [[nodiscard]] Type &at(const id_type id = type_id().hash()) { return any_cast(data.at(id)); } template [[nodiscard]] std::add_const_t *find(const id_type id = type_id().hash()) const { const auto it = data.find(id); return it != data.cend() ? any_cast>(&it->second) : nullptr; } template [[nodiscard]] Type *find(const id_type id = type_id().hash()) { const auto it = data.find(id); return it != data.end() ? any_cast(&it->second) : nullptr; } template [[nodiscard]] bool contains(const id_type id = type_id().hash()) const { const auto it = data.find(id); return it != data.end() && it->second.type() == type_id(); } private: dense_map, identity> data; }; } // namespace internal /** * Internal details not to be documented. * @endcond */ /** * @brief Fast and reliable entity-component system. * @tparam Entity A valid entity type (see entt_traits for more details). */ template class basic_registry { using entity_traits = entt_traits; using basic_common_type = basic_sparse_set; template using storage_type = typename storage_traits::storage_type; template struct group_handler; template struct group_handler, get_t, Owned...> { // nasty workaround for an issue with the toolset v141 that doesn't accept a fold expression here static_assert(!std::disjunction_v::in_place_delete>...>, "Groups do not support in-place delete"); std::conditional_t current{}; template void maybe_valid_if(basic_registry &owner, const Entity entt) { [[maybe_unused]] const auto cpools = std::forward_as_tuple(owner.assure()...); const auto is_valid = ((std::is_same_v || std::get &>(cpools).contains(entt)) && ...) && ((std::is_same_v || owner.assure().contains(entt)) && ...) && ((std::is_same_v || !owner.assure().contains(entt)) && ...); if constexpr(sizeof...(Owned) == 0) { if(is_valid && !current.contains(entt)) { current.emplace(entt); } } else { if(is_valid && !(std::get<0>(cpools).index(entt) < current)) { const auto pos = current++; (std::get &>(cpools).swap_elements(std::get &>(cpools).data()[pos], entt), ...); } } } void discard_if([[maybe_unused]] basic_registry &owner, const Entity entt) { if constexpr(sizeof...(Owned) == 0) { current.remove(entt); } else { if(const auto cpools = std::forward_as_tuple(owner.assure()...); std::get<0>(cpools).contains(entt) && (std::get<0>(cpools).index(entt) < current)) { const auto pos = --current; (std::get &>(cpools).swap_elements(std::get &>(cpools).data()[pos], entt), ...); } } } }; struct group_data { std::size_t size; std::unique_ptr group; bool (*owned)(const id_type) ENTT_NOEXCEPT; bool (*get)(const id_type) ENTT_NOEXCEPT; bool (*exclude)(const id_type) ENTT_NOEXCEPT; }; template [[nodiscard]] auto &assure(const id_type id = type_hash::value()) { static_assert(std::is_same_v>, "Non-decayed types not allowed"); auto &&cpool = pools[id]; if(!cpool) { cpool.reset(new storage_type{}); cpool->bind(forward_as_any(*this)); } ENTT_ASSERT(cpool->type() == type_id(), "Unexpected type"); return static_cast &>(*cpool); } template [[nodiscard]] const auto &assure(const id_type id = type_hash::value()) const { static_assert(std::is_same_v>, "Non-decayed types not allowed"); if(const auto it = pools.find(id); it != pools.cend()) { ENTT_ASSERT(it->second->type() == type_id(), "Unexpected type"); return static_cast &>(*it->second); } static storage_type placeholder{}; return placeholder; } auto generate_identifier(const std::size_t pos) ENTT_NOEXCEPT { ENTT_ASSERT(pos < entity_traits::to_entity(null), "No entities available"); return entity_traits::combine(static_cast(pos), {}); } auto recycle_identifier() ENTT_NOEXCEPT { ENTT_ASSERT(free_list != null, "No entities available"); const auto curr = entity_traits::to_entity(free_list); free_list = entity_traits::combine(entity_traits::to_integral(entities[curr]), tombstone); return (entities[curr] = entity_traits::combine(curr, entity_traits::to_integral(entities[curr]))); } auto release_entity(const Entity entity, const typename entity_traits::version_type version) { const typename entity_traits::version_type vers = version + (version == entity_traits::to_version(tombstone)); entities[entity_traits::to_entity(entity)] = entity_traits::construct(entity_traits::to_integral(free_list), vers); free_list = entity_traits::combine(entity_traits::to_integral(entity), tombstone); return vers; } public: /*! @brief Underlying entity identifier. */ using entity_type = Entity; /*! @brief Underlying version type. */ using version_type = typename entity_traits::version_type; /*! @brief Unsigned integer type. */ using size_type = std::size_t; /*! @brief Common type among all storage types. */ using base_type = basic_common_type; /*! @brief Context type. */ using context = internal::registry_context; /*! @brief Default constructor. */ basic_registry() : pools{}, groups{}, entities{}, free_list{tombstone}, vars{} {} /** * @brief Allocates enough memory upon construction to store `count` pools. * @param count The number of pools to allocate memory for. */ basic_registry(const size_type count) : pools{}, groups{}, entities{}, free_list{tombstone}, vars{} { pools.reserve(count); } /** * @brief Move constructor. * @param other The instance to move from. */ basic_registry(basic_registry &&other) : pools{std::move(other.pools)}, groups{std::move(other.groups)}, entities{std::move(other.entities)}, free_list{other.free_list}, vars{std::move(other.vars)} { for(auto &&curr: pools) { curr.second->bind(forward_as_any(*this)); } } /** * @brief Move assignment operator. * @param other The instance to move from. * @return This registry. */ basic_registry &operator=(basic_registry &&other) { pools = std::move(other.pools); groups = std::move(other.groups); entities = std::move(other.entities); free_list = other.free_list; vars = std::move(other.vars); for(auto &&curr: pools) { curr.second->bind(forward_as_any(*this)); } return *this; } /** * @brief Returns an iterable object to use to _visit_ a registry. * * The iterable object returns a pair that contains the name and a reference * to the current storage. * * @return An iterable object to use to _visit_ the registry. */ [[nodiscard]] auto storage() ENTT_NOEXCEPT { return iterable_adaptor{internal::storage_proxy_iterator{pools.begin()}, internal::storage_proxy_iterator{pools.end()}}; } /*! @copydoc storage */ [[nodiscard]] auto storage() const ENTT_NOEXCEPT { return iterable_adaptor{internal::storage_proxy_iterator{pools.cbegin()}, internal::storage_proxy_iterator{pools.cend()}}; } /** * @brief Finds the storage associated with a given name, if any. * @param id Name used to map the storage within the registry. * @return An iterator to the given storage if it's found, past the end * iterator otherwise. */ [[nodiscard]] auto storage(const id_type id) { return internal::storage_proxy_iterator{pools.find(id)}; } /** * @brief Finds the storage associated with a given name, if any. * @param id Name used to map the storage within the registry. * @return An iterator to the given storage if it's found, past the end * iterator otherwise. */ [[nodiscard]] auto storage(const id_type id) const { return internal::storage_proxy_iterator{pools.find(id)}; } /** * @brief Returns the storage for a given component type. * @tparam Component Type of component of which to return the storage. * @param id Optional name used to map the storage within the registry. * @return The storage for the given component type. */ template decltype(auto) storage(const id_type id = type_hash>::value()) { if constexpr(std::is_const_v) { return std::as_const(*this).template storage>(id); } else { return assure(id); } } /** * @brief Returns the storage for a given component type. * * @warning * If a storage for the given component doesn't exist yet, a temporary * placeholder is returned instead. * * @tparam Component Type of component of which to return the storage. * @param id Optional name used to map the storage within the registry. * @return The storage for the given component type. */ template decltype(auto) storage(const id_type id = type_hash>::value()) const { return assure>(id); } /** * @brief Returns the number of entities created so far. * @return Number of entities created so far. */ [[nodiscard]] size_type size() const ENTT_NOEXCEPT { return entities.size(); } /** * @brief Returns the number of entities still in use. * @return Number of entities still in use. */ [[nodiscard]] size_type alive() const { auto sz = entities.size(); for(auto curr = free_list; curr != null; --sz) { curr = entities[entity_traits::to_entity(curr)]; } return sz; } /** * @brief Increases the capacity (number of entities) of the registry. * @param cap Desired capacity. */ void reserve(const size_type cap) { entities.reserve(cap); } /** * @brief Returns the number of entities that a registry has currently * allocated space for. * @return Capacity of the registry. */ [[nodiscard]] size_type capacity() const ENTT_NOEXCEPT { return entities.capacity(); } /** * @brief Checks whether the registry is empty (no entities still in use). * @return True if the registry is empty, false otherwise. */ [[nodiscard]] bool empty() const { return !alive(); } /** * @brief Direct access to the list of entities of a registry. * * The returned pointer is such that range `[data(), data() + size())` is * always a valid range, even if the registry is empty. * * @warning * This list contains both valid and destroyed entities and isn't suitable * for direct use. * * @return A pointer to the array of entities. */ [[nodiscard]] const entity_type *data() const ENTT_NOEXCEPT { return entities.data(); } /** * @brief Returns the head of the list of released entities. * * This function is intended for use in conjunction with `assign`.
* The returned entity has an invalid identifier in all cases. * * @return The head of the list of released entities. */ [[nodiscard]] entity_type released() const ENTT_NOEXCEPT { return free_list; } /** * @brief Checks if an identifier refers to a valid entity. * @param entity An identifier, either valid or not. * @return True if the identifier is valid, false otherwise. */ [[nodiscard]] bool valid(const entity_type entity) const { const auto pos = size_type(entity_traits::to_entity(entity)); return (pos < entities.size() && entities[pos] == entity); } /** * @brief Returns the actual version for an identifier. * @param entity A valid identifier. * @return The version for the given identifier if valid, the tombstone * version otherwise. */ [[nodiscard]] version_type current(const entity_type entity) const { const auto pos = size_type(entity_traits::to_entity(entity)); return entity_traits::to_version(pos < entities.size() ? entities[pos] : tombstone); } /** * @brief Creates a new entity or recycles a destroyed one. * @return A valid identifier. */ [[nodiscard]] entity_type create() { return (free_list == null) ? entities.emplace_back(generate_identifier(entities.size())) : recycle_identifier(); } /** * @copybrief create * * If the requested entity isn't in use, the suggested identifier is used. * Otherwise, a new identifier is generated. * * @param hint Required identifier. * @return A valid identifier. */ [[nodiscard]] entity_type create(const entity_type hint) { const auto length = entities.size(); if(hint == null || hint == tombstone) { return create(); } else if(const auto req = entity_traits::to_entity(hint); !(req < length)) { entities.resize(size_type(req) + 1u, null); for(auto pos = length; pos < req; ++pos) { release_entity(generate_identifier(pos), {}); } return (entities[req] = hint); } else if(const auto curr = entity_traits::to_entity(entities[req]); req == curr) { return create(); } else { auto *it = &free_list; for(; entity_traits::to_entity(*it) != req; it = &entities[entity_traits::to_entity(*it)]) {} *it = entity_traits::combine(curr, entity_traits::to_integral(*it)); return (entities[req] = hint); } } /** * @brief Assigns each element in a range an identifier. * * @sa create * * @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. */ template void create(It first, It last) { for(; free_list != null && first != last; ++first) { *first = recycle_identifier(); } const auto length = entities.size(); entities.resize(length + std::distance(first, last), null); for(auto pos = length; first != last; ++first, ++pos) { *first = entities[pos] = generate_identifier(pos); } } /** * @brief Assigns identifiers to an empty registry. * * This function is intended for use in conjunction with `data`, `size` and * `destroyed`.
* Don't try to inject ranges of randomly generated entities nor the _wrong_ * head for the list of destroyed entities. There is no guarantee that a * registry will continue to work properly in this case. * * @warning * There must be no entities still alive for this to work properly. * * @tparam It Type of input 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 destroyed The head of the list of destroyed entities. */ template void assign(It first, It last, const entity_type destroyed) { ENTT_ASSERT(!alive(), "Entities still alive"); entities.assign(first, last); free_list = destroyed; } /** * @brief Releases an identifier. * * The version is updated and the identifier can be recycled at any time. * * @warning * Attempting to use an invalid entity results in undefined behavior. * * @param entity A valid identifier. * @return The version of the recycled entity. */ version_type release(const entity_type entity) { return release(entity, static_cast(entity_traits::to_version(entity) + 1u)); } /** * @brief Releases an identifier. * * The suggested version or the valid version closest to the suggested one * is used instead of the implicitly generated version. * * @sa release * * @param entity A valid identifier. * @param version A desired version upon destruction. * @return The version actually assigned to the entity. */ version_type release(const entity_type entity, const version_type version) { ENTT_ASSERT(orphan(entity), "Non-orphan entity"); return release_entity(entity, version); } /** * @brief Releases all identifiers in a range. * * @sa release * * @tparam It Type of input 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. */ template void release(It first, It last) { for(; first != last; ++first) { release(*first); } } /** * @brief Destroys an entity and releases its identifier. * * @sa release * * @warning * Adding or removing components to an entity that is being destroyed can * result in undefined behavior. Attempting to use an invalid entity results * in undefined behavior. * * @param entity A valid identifier. * @return The version of the recycled entity. */ version_type destroy(const entity_type entity) { return destroy(entity, static_cast(entity_traits::to_version(entity) + 1u)); } /** * @brief Destroys an entity and releases its identifier. * * The suggested version or the valid version closest to the suggested one * is used instead of the implicitly generated version. * * @sa destroy * * @param entity A valid identifier. * @param version A desired version upon destruction. * @return The version actually assigned to the entity. */ version_type destroy(const entity_type entity, const version_type version) { ENTT_ASSERT(valid(entity), "Invalid entity"); for(size_type pos = pools.size(); pos; --pos) { pools.begin()[pos - 1u].second->remove(entity); } return release_entity(entity, version); } /** * @brief Destroys all entities in a range and releases their identifiers. * * @sa destroy * * @tparam It Type of input 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. */ template void destroy(It first, It last) { for(; first != last; ++first) { destroy(*first); } } /** * @brief Assigns the given component to an entity. * * The component must have a proper constructor or be of aggregate type. * * @warning * Attempting to use an invalid entity or to assign a component to an entity * that already owns it results in undefined behavior. * * @tparam Component Type of component to create. * @tparam Args Types of arguments to use to construct the component. * @param entity A valid identifier. * @param args Parameters to use to initialize the component. * @return A reference to the newly created component. */ template decltype(auto) emplace(const entity_type entity, Args &&...args) { ENTT_ASSERT(valid(entity), "Invalid entity"); return assure().emplace(entity, std::forward(args)...); } /** * @brief Assigns each entity in a range the given component. * * @sa emplace * * @tparam Component Type of component to create. * @tparam It Type of input 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 value An instance of the component to assign. */ template void insert(It first, It last, const Component &value = {}) { ENTT_ASSERT(std::all_of(first, last, [this](const auto entity) { return valid(entity); }), "Invalid entity"); assure().insert(first, last, value); } /** * @brief Assigns each entity in a range the given components. * * @sa emplace * * @tparam Component Type of component to create. * @tparam EIt Type of input iterator. * @tparam CIt Type of input 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 from An iterator to the first element of the range of components. */ template::value_type, Component>>> void insert(EIt first, EIt last, CIt from) { ENTT_ASSERT(std::all_of(first, last, [this](const auto entity) { return valid(entity); }), "Invalid entity"); assure().insert(first, last, from); } /** * @brief Assigns or replaces the given component for an entity. * * @warning * Attempting to use an invalid entity results in undefined behavior. * * @tparam Component Type of component to assign or replace. * @tparam Args Types of arguments to use to construct the component. * @param entity A valid identifier. * @param args Parameters to use to initialize the component. * @return A reference to the newly created component. */ template decltype(auto) emplace_or_replace(const entity_type entity, Args &&...args) { ENTT_ASSERT(valid(entity), "Invalid entity"); auto &cpool = assure(); return cpool.contains(entity) ? cpool.patch(entity, [&args...](auto &...curr) { ((curr = Component{std::forward(args)...}), ...); }) : cpool.emplace(entity, std::forward(args)...); } /** * @brief Patches the given component for an entity. * * The signature of the function should be equivalent to the following: * * @code{.cpp} * void(Component &); * @endcode * * @note * Empty types aren't explicitly instantiated and therefore they are never * returned. However, this function can be used to trigger an update signal * for them. * * @warning * Attempting to use an invalid entity or to patch a component of an entity * that doesn't own it results in undefined behavior. * * @tparam Component Type of component to patch. * @tparam Func Types of the function objects to invoke. * @param entity A valid identifier. * @param func Valid function objects. * @return A reference to the patched component. */ template decltype(auto) patch(const entity_type entity, Func &&...func) { ENTT_ASSERT(valid(entity), "Invalid entity"); return assure().patch(entity, std::forward(func)...); } /** * @brief Replaces the given component for an entity. * * The component must have a proper constructor or be of aggregate type. * * @warning * Attempting to use an invalid entity or to replace a component of an * entity that doesn't own it results in undefined behavior. * * @tparam Component Type of component to replace. * @tparam Args Types of arguments to use to construct the component. * @param entity A valid identifier. * @param args Parameters to use to initialize the component. * @return A reference to the component being replaced. */ template decltype(auto) replace(const entity_type entity, Args &&...args) { ENTT_ASSERT(valid(entity), "Invalid entity"); return assure().patch(entity, [&args...](auto &...curr) { ((curr = Component{std::forward(args)...}), ...); }); } /** * @brief Removes the given components from an entity. * * @warning * Attempting to use an invalid entity results in undefined behavior. * * @tparam Component Type of component to remove. * @tparam Other Other types of components to remove. * @param entity A valid identifier. * @return The number of components actually removed. */ template size_type remove(const entity_type entity) { ENTT_ASSERT(valid(entity), "Invalid entity"); return (assure().remove(entity) + ... + assure().remove(entity)); } /** * @brief Removes the given components from all the entities in a range. * * @sa remove * * @tparam Component Type of component to remove. * @tparam Other Other types of components to remove. * @tparam It Type of input 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. * @return The number of components actually removed. */ template size_type remove(It first, It last) { if constexpr(sizeof...(Other) == 0u) { ENTT_ASSERT(std::all_of(first, last, [this](const auto entity) { return valid(entity); }), "Invalid entity"); return assure().remove(std::move(first), std::move(last)); } else { size_type count{}; for(auto cpools = std::forward_as_tuple(assure(), assure()...); first != last; ++first) { ENTT_ASSERT(valid(*first), "Invalid entity"); count += std::apply([entt = *first](auto &...curr) { return (curr.remove(entt) + ... + 0u); }, cpools); } return count; } } /** * @brief Erases the given components from an entity. * * @warning * Attempting to use an invalid entity or to erase a component from an * entity that doesn't own it results in undefined behavior. * * @tparam Component Types of components to erase. * @tparam Other Other types of components to erase. * @param entity A valid identifier. */ template void erase(const entity_type entity) { ENTT_ASSERT(valid(entity), "Invalid entity"); (assure().erase(entity), (assure().erase(entity), ...)); } /** * @brief Erases the given components from all the entities in a range. * * @sa erase * * @tparam Component Types of components to erase. * @tparam Other Other types of components to erase. * @tparam It Type of input 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. */ template void erase(It first, It last) { if constexpr(sizeof...(Other) == 0u) { ENTT_ASSERT(std::all_of(first, last, [this](const auto entity) { return valid(entity); }), "Invalid entity"); assure().erase(std::move(first), std::move(last)); } else { for(auto cpools = std::forward_as_tuple(assure(), assure()...); first != last; ++first) { ENTT_ASSERT(valid(*first), "Invalid entity"); std::apply([entt = *first](auto &...curr) { (curr.erase(entt), ...); }, cpools); } } } /** * @brief Removes all tombstones from a registry or only the pools for the * given components. * @tparam Component Types of components for which to clear all tombstones. */ template void compact() { if constexpr(sizeof...(Component) == 0) { for(auto &&curr: pools) { curr.second->compact(); } } else { (assure().compact(), ...); } } /** * @brief Checks if an entity has all the given components. * * @warning * Attempting to use an invalid entity results in undefined behavior. * * @tparam Component Components for which to perform the check. * @param entity A valid identifier. * @return True if the entity has all the components, false otherwise. */ template [[nodiscard]] bool all_of(const entity_type entity) const { ENTT_ASSERT(valid(entity), "Invalid entity"); return (assure>().contains(entity) && ...); } /** * @brief Checks if an entity has at least one of the given components. * * @warning * Attempting to use an invalid entity results in undefined behavior. * * @tparam Component Components for which to perform the check. * @param entity A valid identifier. * @return True if the entity has at least one of the given components, * false otherwise. */ template [[nodiscard]] bool any_of(const entity_type entity) const { ENTT_ASSERT(valid(entity), "Invalid entity"); return (assure>().contains(entity) || ...); } /** * @brief Returns references to the given components for an entity. * * @warning * Attempting to use an invalid entity or to get a component from an entity * that doesn't own it results in undefined behavior. * * @tparam Component Types of components to get. * @param entity A valid identifier. * @return References to the components owned by the entity. */ template [[nodiscard]] decltype(auto) get([[maybe_unused]] const entity_type entity) const { ENTT_ASSERT(valid(entity), "Invalid entity"); return view().template get(entity); } /*! @copydoc get */ template [[nodiscard]] decltype(auto) get([[maybe_unused]] const entity_type entity) { ENTT_ASSERT(valid(entity), "Invalid entity"); return view().template get(entity); } /** * @brief Returns a reference to the given component for an entity. * * In case the entity doesn't own the component, the parameters provided are * used to construct it. * * @warning * Attempting to use an invalid entity results in undefined behavior. * * @tparam Component Type of component to get. * @tparam Args Types of arguments to use to construct the component. * @param entity A valid identifier. * @param args Parameters to use to initialize the component. * @return Reference to the component owned by the entity. */ template [[nodiscard]] decltype(auto) get_or_emplace(const entity_type entity, Args &&...args) { ENTT_ASSERT(valid(entity), "Invalid entity"); auto &cpool = assure(); return cpool.contains(entity) ? cpool.get(entity) : cpool.emplace(entity, std::forward(args)...); } /** * @brief Returns pointers to the given components for an entity. * * @warning * Attempting to use an invalid entity results in undefined behavior. * * @note * The registry retains ownership of the pointed-to components. * * @tparam Component Types of components to get. * @param entity A valid identifier. * @return Pointers to the components owned by the entity. */ template [[nodiscard]] auto try_get([[maybe_unused]] const entity_type entity) const { ENTT_ASSERT(valid(entity), "Invalid entity"); if constexpr(sizeof...(Component) == 1) { const auto &cpool = assure...>(); return cpool.contains(entity) ? std::addressof(cpool.get(entity)) : nullptr; } else { return std::make_tuple(try_get(entity)...); } } /*! @copydoc try_get */ template [[nodiscard]] auto try_get([[maybe_unused]] const entity_type entity) { if constexpr(sizeof...(Component) == 1) { return (const_cast(std::as_const(*this).template try_get(entity)), ...); } else { return std::make_tuple(try_get(entity)...); } } /** * @brief Clears a whole registry or the pools for the given components. * @tparam Component Types of components to remove from their entities. */ template void clear() { if constexpr(sizeof...(Component) == 0) { for(auto &&curr: pools) { curr.second->clear(); } each([this](const auto entity) { this->release(entity); }); } else { (assure().clear(), ...); } } /** * @brief Iterates all the entities that are still in use. * * The signature of the function should be equivalent to the following: * * @code{.cpp} * void(const Entity); * @endcode * * It's not defined whether entities created during iteration are returned. * * @tparam Func Type of the function object to invoke. * @param func A valid function object. */ template void each(Func func) const { if(free_list == null) { for(auto pos = entities.size(); pos; --pos) { func(entities[pos - 1]); } } else { for(auto pos = entities.size(); pos; --pos) { if(const auto entity = entities[pos - 1]; entity_traits::to_entity(entity) == (pos - 1)) { func(entity); } } } } /** * @brief Checks if an entity has components assigned. * @param entity A valid identifier. * @return True if the entity has no components assigned, false otherwise. */ [[nodiscard]] bool orphan(const entity_type entity) const { ENTT_ASSERT(valid(entity), "Invalid entity"); return std::none_of(pools.cbegin(), pools.cend(), [entity](auto &&curr) { return curr.second->contains(entity); }); } /** * @brief Returns a sink object for the given component. * * Use this function to receive notifications whenever a new instance of the * given component is created and assigned to an entity.
* The function type for a listener is equivalent to: * * @code{.cpp} * void(basic_registry &, Entity); * @endcode * * Listeners are invoked **after** assigning the component to the entity. * * @sa sink * * @tparam Component Type of component of which to get the sink. * @return A temporary sink object. */ template [[nodiscard]] auto on_construct() { return assure().on_construct(); } /** * @brief Returns a sink object for the given component. * * Use this function to receive notifications whenever an instance of the * given component is explicitly updated.
* The function type for a listener is equivalent to: * * @code{.cpp} * void(basic_registry &, Entity); * @endcode * * Listeners are invoked **after** updating the component. * * @sa sink * * @tparam Component Type of component of which to get the sink. * @return A temporary sink object. */ template [[nodiscard]] auto on_update() { return assure().on_update(); } /** * @brief Returns a sink object for the given component. * * Use this function to receive notifications whenever an instance of the * given component is removed from an entity and thus destroyed.
* The function type for a listener is equivalent to: * * @code{.cpp} * void(basic_registry &, Entity); * @endcode * * Listeners are invoked **before** removing the component from the entity. * * @sa sink * * @tparam Component Type of component of which to get the sink. * @return A temporary sink object. */ template [[nodiscard]] auto on_destroy() { return assure().on_destroy(); } /** * @brief Returns a view for the given components. * * Views are created on the fly and share with the registry its internal * data structures. Feel free to discard them after the use.
* Creating and destroying a view is an incredibly cheap operation. As a * rule of thumb, storing a view should never be an option. * * @tparam Component Type of component used to construct the view. * @tparam Other Other types of components used to construct the view. * @tparam Exclude Types of components used to filter the view. * @return A newly created view. */ template [[nodiscard]] basic_view, std::add_const_t...>, exclude_t> view(exclude_t = {}) const { return {assure>(), assure>()..., assure()...}; } /*! @copydoc view */ template [[nodiscard]] basic_view, exclude_t> view(exclude_t = {}) { return {assure>(), assure>()..., assure()...}; } /** * @brief Returns a group for the given components. * * Groups are created on the fly and share with the registry its internal * data structures. Feel free to discard them after the use.
* Creating and destroying a group is an incredibly cheap operation. As a * rule of thumb, storing a group should never be an option. * * Groups support exclusion lists and can own types of components. The more * types are owned by a group, the faster it is to iterate entities and * components.
* However, groups also affect some features of the registry such as the * creation and destruction of components. * * @note * Pools of components that are owned by a group cannot be sorted anymore. * The group takes the ownership of the pools and arrange components so as * to iterate them as fast as possible. * * @tparam Owned Types of components owned by the group. * @tparam Get Types of components observed by the group. * @tparam Exclude Types of components used to filter the group. * @return A newly created group. */ template [[nodiscard]] basic_group, get_t, exclude_t> group(get_t, exclude_t = {}) { static_assert(sizeof...(Owned) + sizeof...(Get) > 0, "Exclusion-only groups are not supported"); static_assert(sizeof...(Owned) + sizeof...(Get) + sizeof...(Exclude) > 1, "Single component groups are not allowed"); using handler_type = group_handler...>, get_t...>, std::remove_const_t...>; const auto cpools = std::forward_as_tuple(assure>()..., assure>()...); constexpr auto size = sizeof...(Owned) + sizeof...(Get) + sizeof...(Exclude); handler_type *handler = nullptr; auto it = std::find_if(groups.cbegin(), groups.cend(), [size](const auto &gdata) { return gdata.size == size && (gdata.owned(type_hash>::value()) && ...) && (gdata.get(type_hash>::value()) && ...) && (gdata.exclude(type_hash::value()) && ...); }); if(it != groups.cend()) { handler = static_cast(it->group.get()); } else { group_data candidate = { size, {new handler_type{}, [](void *instance) { delete static_cast(instance); }}, []([[maybe_unused]] const id_type ctype) ENTT_NOEXCEPT { return ((ctype == type_hash>::value()) || ...); }, []([[maybe_unused]] const id_type ctype) ENTT_NOEXCEPT { return ((ctype == type_hash>::value()) || ...); }, []([[maybe_unused]] const id_type ctype) ENTT_NOEXCEPT { return ((ctype == type_hash::value()) || ...); }, }; handler = static_cast(candidate.group.get()); const void *maybe_valid_if = nullptr; const void *discard_if = nullptr; if constexpr(sizeof...(Owned) == 0) { groups.push_back(std::move(candidate)); } else { [[maybe_unused]] auto has_conflict = [size](const auto &gdata) { const auto overlapping = (0u + ... + gdata.owned(type_hash>::value())); const auto sz = overlapping + (0u + ... + gdata.get(type_hash>::value())) + (0u + ... + gdata.exclude(type_hash::value())); return !overlapping || ((sz == size) || (sz == gdata.size)); }; ENTT_ASSERT(std::all_of(groups.cbegin(), groups.cend(), std::move(has_conflict)), "Conflicting groups"); const auto next = std::find_if_not(groups.cbegin(), groups.cend(), [size](const auto &gdata) { return !(0u + ... + gdata.owned(type_hash>::value())) || (size > gdata.size); }); const auto prev = std::find_if(std::make_reverse_iterator(next), groups.crend(), [](const auto &gdata) { return (0u + ... + gdata.owned(type_hash>::value())); }); maybe_valid_if = (next == groups.cend() ? maybe_valid_if : next->group.get()); discard_if = (prev == groups.crend() ? discard_if : prev->group.get()); groups.insert(next, std::move(candidate)); } (on_construct>().before(maybe_valid_if).template connect<&handler_type::template maybe_valid_if>>(*handler), ...); (on_construct>().before(maybe_valid_if).template connect<&handler_type::template maybe_valid_if>>(*handler), ...); (on_destroy().before(maybe_valid_if).template connect<&handler_type::template maybe_valid_if>(*handler), ...); (on_destroy>().before(discard_if).template connect<&handler_type::discard_if>(*handler), ...); (on_destroy>().before(discard_if).template connect<&handler_type::discard_if>(*handler), ...); (on_construct().before(discard_if).template connect<&handler_type::discard_if>(*handler), ...); if constexpr(sizeof...(Owned) == 0) { for(const auto entity: view(exclude)) { handler->current.emplace(entity); } } else { // we cannot iterate backwards because we want to leave behind valid entities in case of owned types for(auto *first = std::get<0>(cpools).data(), *last = first + std::get<0>(cpools).size(); first != last; ++first) { handler->template maybe_valid_if...>>>(*this, *first); } } } return {handler->current, std::get> &>(cpools)..., std::get> &>(cpools)...}; } /*! @copydoc group */ template [[nodiscard]] basic_group...>, get_t...>, exclude_t> group_if_exists(get_t, exclude_t = {}) const { auto it = std::find_if(groups.cbegin(), groups.cend(), [](const auto &gdata) { return gdata.size == (sizeof...(Owned) + sizeof...(Get) + sizeof...(Exclude)) && (gdata.owned(type_hash>::value()) && ...) && (gdata.get(type_hash>::value()) && ...) && (gdata.exclude(type_hash::value()) && ...); }); if(it == groups.cend()) { return {}; } else { using handler_type = group_handler...>, get_t...>, std::remove_const_t...>; return {static_cast(it->group.get())->current, assure>()..., assure>()...}; } } /*! @copydoc group */ template [[nodiscard]] basic_group, get_t<>, exclude_t> group(exclude_t = {}) { return group(get_t<>{}, exclude); } /*! @copydoc group */ template [[nodiscard]] basic_group...>, get_t<>, exclude_t> group_if_exists(exclude_t = {}) const { return group_if_exists...>(get_t<>{}, exclude); } /** * @brief Checks whether the given components belong to any group. * @tparam Component Types of components in which one is interested. * @return True if the pools of the given components are _free_, false * otherwise. */ template [[nodiscard]] bool owned() const { return std::any_of(groups.cbegin(), groups.cend(), [](auto &&gdata) { return (gdata.owned(type_hash>::value()) || ...); }); } /** * @brief Checks whether a group can be sorted. * @tparam Owned Types of components owned by the group. * @tparam Get Types of components observed by the group. * @tparam Exclude Types of components used to filter the group. * @return True if the group can be sorted, false otherwise. */ template [[nodiscard]] bool sortable(const basic_group, get_t, exclude_t> &) ENTT_NOEXCEPT { constexpr auto size = sizeof...(Owned) + sizeof...(Get) + sizeof...(Exclude); auto pred = [size](const auto &gdata) { return (0u + ... + gdata.owned(type_hash>::value())) && (size < gdata.size); }; return std::find_if(groups.cbegin(), groups.cend(), std::move(pred)) == groups.cend(); } /** * @brief Sorts the elements of a given component. * * The order remains valid until a component of the given type is assigned * to or removed from an entity.
* The comparison function object returns `true` if the first element is * _less_ than the second one, `false` otherwise. Its signature is also * equivalent to one of the following: * * @code{.cpp} * bool(const Entity, const Entity); * bool(const Component &, const Component &); * @endcode * * Moreover, it shall induce a _strict weak ordering_ on the values.
* The sort function object offers an `operator()` that accepts: * * * An iterator to the first element of the range to sort. * * An iterator past the last element of the range to sort. * * A comparison function object to use to compare the elements. * * The comparison function object hasn't necessarily the type of the one * passed along with the other parameters to this member function. * * @warning * Pools of components owned by a group cannot be sorted. * * @tparam Component Type of components to sort. * @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 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 void sort(Compare compare, Sort algo = Sort{}, Args &&...args) { ENTT_ASSERT(!owned(), "Cannot sort owned storage"); auto &cpool = assure(); if constexpr(std::is_invocable_v) { auto comp = [&cpool, compare = std::move(compare)](const auto lhs, const auto rhs) { return compare(std::as_const(cpool.get(lhs)), std::as_const(cpool.get(rhs))); }; cpool.sort(std::move(comp), std::move(algo), std::forward(args)...); } else { cpool.sort(std::move(compare), std::move(algo), std::forward(args)...); } } /** * @brief Sorts two pools of components in the same way. * * Being `To` and `From` the two sets, after invoking this function an * iterator for `To` returns elements according to the following rules: * * * All entities in `To` that are also in `From` are returned first * according to the order they have in `From`. * * All entities in `To` that are not in `From` are returned in no * particular order after all the other entities. * * Any subsequent change to `From` won't affect the order in `To`. * * @warning * Pools of components owned by a group cannot be sorted. * * @tparam To Type of components to sort. * @tparam From Type of components to use to sort. */ template void sort() { ENTT_ASSERT(!owned(), "Cannot sort owned storage"); assure().respect(assure()); } /** * @brief Returns the context object, that is, a general purpose container. * @return The context object, that is, a general purpose container. */ context &ctx() ENTT_NOEXCEPT { return vars; } /*! @copydoc ctx */ const context &ctx() const ENTT_NOEXCEPT { return vars; } private: dense_map, identity> pools; std::vector groups; std::vector entities; entity_type free_list; context vars; }; } // namespace entt #endif