mirror of
https://github.com/zhenyan121/Cubed.git
synced 2026-08-08 17:57:02 +08:00
1143 lines
43 KiB
C++
1143 lines
43 KiB
C++
#ifndef ENTT_ENTITY_VIEW_HPP
|
|
#define ENTT_ENTITY_VIEW_HPP
|
|
|
|
#include "../config/config.h"
|
|
#include "../core/concepts.hpp"
|
|
#include "../core/iterator.hpp"
|
|
#include "../core/type_traits.hpp"
|
|
#include "../stl/array.hpp"
|
|
#include "../stl/concepts.hpp"
|
|
#include "../stl/cstddef.hpp"
|
|
#include "../stl/iterator.hpp"
|
|
#include "../stl/tuple.hpp"
|
|
#include "../stl/type_traits.hpp"
|
|
#include "../stl/utility.hpp"
|
|
#include "entity.hpp"
|
|
#include "fwd.hpp"
|
|
|
|
namespace entt {
|
|
|
|
/*! @cond ENTT_INTERNAL */
|
|
namespace internal {
|
|
|
|
template<typename... Type>
|
|
// NOLINTNEXTLINE(misc-redundant-expression)
|
|
static constexpr bool tombstone_check_v = ((sizeof...(Type) == 1u) && ... && (Type::storage_policy == deletion_policy::in_place));
|
|
|
|
template<cvref_unqualified Type>
|
|
const Type *view_placeholder() {
|
|
static const Type placeholder{};
|
|
return &placeholder;
|
|
}
|
|
|
|
[[nodiscard]] bool all_of(auto first, const auto last, const auto entt) noexcept {
|
|
for(; (first != last) && (*first)->contains(entt); ++first) {}
|
|
return first == last;
|
|
}
|
|
|
|
[[nodiscard]] bool none_of(auto first, const auto last, const auto entt) noexcept {
|
|
for(; (first != last) && !(*first)->contains(entt); ++first) {}
|
|
return first == last;
|
|
}
|
|
|
|
template<typename It>
|
|
[[nodiscard]] bool fully_initialized(It first, const It last, const stl::remove_pointer_t<typename stl::iterator_traits<It>::value_type> *placeholder) noexcept {
|
|
for(; (first != last) && *first != placeholder; ++first) {}
|
|
return first == last;
|
|
}
|
|
|
|
template<typename Result, typename View, typename Other, stl::size_t... GLhs, stl::size_t... ELhs, stl::size_t... GRhs, stl::size_t... ERhs>
|
|
[[nodiscard]] Result view_pack(const View &view, const Other &other, stl::index_sequence<GLhs...>, stl::index_sequence<ELhs...>, stl::index_sequence<GRhs...>, stl::index_sequence<ERhs...>) {
|
|
Result elem{};
|
|
// friend-initialization, avoid multiple calls to refresh
|
|
elem.pools = {view.template storage<GLhs>()..., other.template storage<GRhs>()...};
|
|
[[maybe_unused]] const auto filter_or_placeholder = [placeholder = elem.placeholder](auto *value) { return (value == nullptr) ? placeholder : value; };
|
|
elem.filter = {filter_or_placeholder(view.template storage<sizeof...(GLhs) + ELhs>())..., filter_or_placeholder(other.template storage<sizeof...(GRhs) + ERhs>())...};
|
|
elem.refresh();
|
|
return elem;
|
|
}
|
|
|
|
template<typename Type, bool Checked, stl::size_t Get, stl::size_t Exclude>
|
|
class view_iterator final {
|
|
template<typename, typename...>
|
|
friend struct extended_view_iterator;
|
|
|
|
using iterator_type = Type::const_iterator;
|
|
using iterator_traits = stl::iterator_traits<iterator_type>;
|
|
|
|
[[nodiscard]] bool valid(const iterator_traits::value_type entt) const noexcept {
|
|
return (!Checked || (entt != tombstone))
|
|
&& ((Get == 1u) || (internal::all_of(pools.begin(), pools.begin() + index, entt) && internal::all_of(pools.begin() + index + 1, pools.end(), entt)))
|
|
&& ((Exclude == 0u) || internal::none_of(filter.begin(), filter.end(), entt));
|
|
}
|
|
|
|
void seek_next() {
|
|
for(constexpr iterator_type sentinel{}; it != sentinel && !valid(*it); ++it) {}
|
|
}
|
|
|
|
public:
|
|
using value_type = iterator_traits::value_type;
|
|
using pointer = iterator_traits::pointer;
|
|
using reference = iterator_traits::reference;
|
|
using difference_type = iterator_traits::difference_type;
|
|
using iterator_category = stl::forward_iterator_tag;
|
|
|
|
constexpr view_iterator() noexcept
|
|
: it{},
|
|
pools{},
|
|
filter{},
|
|
index{} {}
|
|
|
|
view_iterator(iterator_type first, stl::array<const Type *, Get> value, stl::array<const Type *, Exclude> excl, const stl::size_t idx) noexcept
|
|
: it{first},
|
|
pools{value},
|
|
filter{excl},
|
|
index{static_cast<difference_type>(idx)} {
|
|
ENTT_ASSERT((Get != 1u) || (Exclude != 0u) || pools[0u]->policy() == deletion_policy::in_place, "Non in-place storage view iterator");
|
|
seek_next();
|
|
}
|
|
|
|
view_iterator &operator++() noexcept {
|
|
++it;
|
|
seek_next();
|
|
return *this;
|
|
}
|
|
|
|
view_iterator operator++(int) noexcept {
|
|
const view_iterator orig = *this;
|
|
return ++(*this), orig;
|
|
}
|
|
|
|
[[nodiscard]] pointer operator->() const noexcept {
|
|
return &*it;
|
|
}
|
|
|
|
[[nodiscard]] reference operator*() const noexcept {
|
|
return *operator->();
|
|
}
|
|
|
|
template<typename Other, auto... Args>
|
|
[[nodiscard]] constexpr bool operator==(const view_iterator<Other, Args...> &other) const noexcept {
|
|
return it == other.it;
|
|
}
|
|
|
|
private:
|
|
iterator_type it;
|
|
stl::array<const Type *, Get> pools;
|
|
stl::array<const Type *, Exclude> filter;
|
|
difference_type index;
|
|
};
|
|
|
|
template<typename It, typename... Get>
|
|
struct extended_view_iterator final {
|
|
using iterator_type = It;
|
|
using value_type = decltype(stl::tuple_cat(stl::make_tuple(*stl::declval<It>()), stl::declval<Get>().get_as_tuple({})...));
|
|
using pointer = input_iterator_pointer<value_type>;
|
|
using reference = value_type;
|
|
using difference_type = stl::ptrdiff_t;
|
|
using iterator_category = stl::input_iterator_tag;
|
|
using iterator_concept = stl::forward_iterator_tag;
|
|
|
|
constexpr extended_view_iterator()
|
|
: it{} {}
|
|
|
|
extended_view_iterator(iterator_type from)
|
|
: it{from} {}
|
|
|
|
extended_view_iterator &operator++() noexcept {
|
|
return ++it, *this;
|
|
}
|
|
|
|
extended_view_iterator operator++(int) noexcept {
|
|
const extended_view_iterator orig = *this;
|
|
return ++(*this), orig;
|
|
}
|
|
|
|
[[nodiscard]] reference operator*() const noexcept {
|
|
return [this]<auto... Index>(stl::index_sequence<Index...>) {
|
|
return stl::tuple_cat(stl::make_tuple(*it), static_cast<Get *>(const_cast<constness_as_t<typename Get::base_type, Get> *>(stl::get<Index>(it.pools)))->get_as_tuple(*it)...);
|
|
}(stl::index_sequence_for<Get...>{});
|
|
}
|
|
|
|
[[nodiscard]] pointer operator->() const noexcept {
|
|
return operator*();
|
|
}
|
|
|
|
[[nodiscard]] constexpr iterator_type base() const noexcept {
|
|
return it;
|
|
}
|
|
|
|
template<typename... Other>
|
|
[[nodiscard]] constexpr bool operator==(const extended_view_iterator<Other...> &other) const noexcept {
|
|
return it == other.it;
|
|
}
|
|
|
|
private:
|
|
It it;
|
|
};
|
|
|
|
} // namespace internal
|
|
/*! @endcond */
|
|
|
|
/**
|
|
* @brief View implementation.
|
|
*
|
|
* Primary template isn't defined on purpose. All the specializations give a
|
|
* compile-time error, but for a few reasonable cases.
|
|
*
|
|
* @b Important
|
|
*
|
|
* View iterators aren't invalidated if:
|
|
*
|
|
* * New elements are added to the storage iterated by the view.
|
|
* * The entity currently returned is modified (for example, elements are added
|
|
* or removed from it).
|
|
* * The entity currently returned is destroyed.
|
|
*
|
|
* In all other cases, modifying the storage iterated by a view in any way can
|
|
* invalidate all iterators.
|
|
*/
|
|
template<typename, typename>
|
|
class basic_view;
|
|
|
|
/**
|
|
* @brief Basic storage view implementation.
|
|
* @warning For internal use only, backward compatibility not guaranteed.
|
|
* @tparam Type Common type among all storage types.
|
|
* @tparam Checked True to enable the tombstone check, false otherwise.
|
|
* @tparam Get Number of storage iterated by the view.
|
|
* @tparam Exclude Number of storage used to filter the view.
|
|
*/
|
|
template<cvref_unqualified Type, bool Checked, stl::size_t Get, stl::size_t Exclude>
|
|
class basic_common_view {
|
|
template<typename Return, typename View, typename Other, stl::size_t... GLhs, stl::size_t... ELhs, stl::size_t... GRhs, stl::size_t... ERhs>
|
|
friend Return internal::view_pack(const View &, const Other &, stl::index_sequence<GLhs...>, stl::index_sequence<ELhs...>, stl::index_sequence<GRhs...>, stl::index_sequence<ERhs...>);
|
|
|
|
[[nodiscard]] auto offset() const noexcept {
|
|
ENTT_ASSERT(index != Get, "Invalid view");
|
|
return (pools[index]->policy() == deletion_policy::swap_only) ? pools[index]->free_list() : pools[index]->size();
|
|
}
|
|
|
|
void unchecked_refresh() noexcept {
|
|
index = 0u;
|
|
|
|
if constexpr(Get > 1u) {
|
|
for(size_type pos{1u}; pos < Get; ++pos) {
|
|
if(pools[pos]->size() < pools[index]->size()) {
|
|
index = pos;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
protected:
|
|
/*! @cond ENTT_INTERNAL */
|
|
basic_common_view() noexcept {
|
|
for(size_type pos{}, last = filter.size(); pos < last; ++pos) {
|
|
filter[pos] = placeholder;
|
|
}
|
|
}
|
|
|
|
basic_common_view(stl::array<const Type *, Get> value, stl::array<const Type *, Exclude> excl) noexcept
|
|
: pools{value},
|
|
filter{excl},
|
|
index{Get} {
|
|
unchecked_refresh();
|
|
}
|
|
|
|
[[nodiscard]] const Type *pool_at(const stl::size_t pos) const noexcept {
|
|
return pools[pos];
|
|
}
|
|
|
|
void pool_at(const stl::size_t pos, const Type *elem) noexcept {
|
|
ENTT_ASSERT(elem != nullptr, "Unexpected element");
|
|
pools[pos] = elem;
|
|
refresh();
|
|
}
|
|
|
|
[[nodiscard]] const Type *filter_at(const stl::size_t pos) const noexcept {
|
|
return (filter[pos] == placeholder) ? nullptr : filter[pos];
|
|
}
|
|
|
|
void filter_at(const stl::size_t pos, const Type *elem) noexcept {
|
|
ENTT_ASSERT(elem != nullptr, "Unexpected element");
|
|
filter[pos] = elem;
|
|
}
|
|
|
|
[[nodiscard]] bool none_of(const Type::entity_type entt) const noexcept {
|
|
return internal::none_of(filter.begin(), filter.end(), entt);
|
|
}
|
|
|
|
void use(const stl::size_t pos) noexcept {
|
|
index = (index != Get) ? pos : Get;
|
|
}
|
|
/*! @endcond */
|
|
|
|
public:
|
|
/*! @brief Common type among all storage types. */
|
|
using common_type = Type;
|
|
/*! @brief Underlying entity identifier. */
|
|
using entity_type = Type::entity_type;
|
|
/*! @brief Unsigned integer type. */
|
|
using size_type = stl::size_t;
|
|
/*! @brief Signed integer type. */
|
|
using difference_type = stl::ptrdiff_t;
|
|
/*! @brief Forward iterator type. */
|
|
using iterator = internal::view_iterator<common_type, Checked, Get, Exclude>;
|
|
|
|
/*! @brief Updates the internal leading view if required. */
|
|
void refresh() noexcept {
|
|
size_type pos = static_cast<size_type>(index != Get) * Get;
|
|
for(; pos < Get && pools[pos] != nullptr; ++pos) {}
|
|
|
|
if(pos == Get) {
|
|
unchecked_refresh();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Returns the leading storage of a view, if any.
|
|
* @return The leading storage of the view.
|
|
*/
|
|
[[nodiscard]] const common_type *handle() const noexcept {
|
|
return (index != Get) ? pools[index] : nullptr;
|
|
}
|
|
|
|
/**
|
|
* @brief Estimates the number of entities iterated by the view.
|
|
* @return Estimated number of entities iterated by the view.
|
|
*/
|
|
[[nodiscard]] size_type size_hint() const noexcept {
|
|
return (index != Get) ? offset() : size_type{};
|
|
}
|
|
|
|
/**
|
|
* @brief Returns an iterator to the first entity of the view.
|
|
*
|
|
* If the view is empty, the returned iterator will be equal to `end()`.
|
|
*
|
|
* @return An iterator to the first entity of the view.
|
|
*/
|
|
[[nodiscard]] iterator begin() const noexcept {
|
|
return (index != Get) ? iterator{pools[index]->end() - static_cast<difference_type>(offset()), pools, filter, index} : iterator{};
|
|
}
|
|
|
|
/**
|
|
* @brief Returns an iterator that is past the last entity of the view.
|
|
* @return An iterator to the entity following the last entity of the view.
|
|
*/
|
|
[[nodiscard]] iterator end() const noexcept {
|
|
return (index != Get) ? iterator{pools[index]->end(), pools, filter, index} : iterator{};
|
|
}
|
|
|
|
/**
|
|
* @brief Returns the first entity of the view, if any.
|
|
* @return The first entity of the view if one exists, the null entity
|
|
* otherwise.
|
|
*/
|
|
[[nodiscard]] entity_type front() const noexcept {
|
|
const auto it = begin();
|
|
return it != end() ? *it : null;
|
|
}
|
|
|
|
/**
|
|
* @brief Returns the last entity of the view, if any.
|
|
* @return The last entity of the view if one exists, the null entity
|
|
* otherwise.
|
|
*/
|
|
[[nodiscard]] entity_type back() const noexcept {
|
|
if(index != Get) {
|
|
auto it = pools[index]->rbegin();
|
|
const auto last = it + static_cast<difference_type>(offset());
|
|
for(const auto idx = static_cast<difference_type>(index); it != last && !(internal::all_of(pools.begin(), pools.begin() + idx, *it) && internal::all_of(pools.begin() + idx + 1, pools.end(), *it) && internal::none_of(filter.begin(), filter.end(), *it)); ++it) {}
|
|
return it == last ? null : *it;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* @brief Finds an entity.
|
|
* @param entt A valid identifier.
|
|
* @return An iterator to the given entity if it's found, past the end
|
|
* iterator otherwise.
|
|
*/
|
|
[[nodiscard]] iterator find(const entity_type entt) const noexcept {
|
|
return contains(entt) ? iterator{pools[index]->find(entt), pools, filter, index} : end();
|
|
}
|
|
|
|
/**
|
|
* @brief Checks if a view is fully initialized.
|
|
* @return True if the view is fully initialized, false otherwise.
|
|
*/
|
|
[[nodiscard]] explicit operator bool() const noexcept {
|
|
return (index != Get) && internal::fully_initialized(filter.begin(), filter.end(), placeholder);
|
|
}
|
|
|
|
/**
|
|
* @brief Checks if a view contains an entity.
|
|
* @param entt A valid identifier.
|
|
* @return True if the view contains the given entity, false otherwise.
|
|
*/
|
|
[[nodiscard]] bool contains(const entity_type entt) const noexcept {
|
|
return (index != Get)
|
|
&& internal::all_of(pools.begin(), pools.end(), entt)
|
|
&& internal::none_of(filter.begin(), filter.end(), entt)
|
|
&& pools[index]->index(entt) < offset();
|
|
}
|
|
|
|
private:
|
|
stl::array<const common_type *, Get> pools{};
|
|
stl::array<const common_type *, Exclude> filter{};
|
|
const common_type *placeholder{internal::view_placeholder<common_type>()};
|
|
size_type index{Get};
|
|
};
|
|
|
|
/**
|
|
* @brief General purpose view.
|
|
*
|
|
* This view visits all entities that are at least in the given storage. During
|
|
* initialization, it also looks at the number of elements available for each
|
|
* storage and uses the smallest set in order to get a performance boost.
|
|
*
|
|
* @sa basic_view
|
|
*
|
|
* @tparam Get Types of storage iterated by the view.
|
|
* @tparam Exclude Types of storage used to filter the view.
|
|
*/
|
|
template<typename... Get, typename... Exclude>
|
|
requires (sizeof...(Get) != 0u)
|
|
class basic_view<get_t<Get...>, exclude_t<Exclude...>>
|
|
: public basic_common_view<stl::common_type_t<typename Get::base_type...>, internal::tombstone_check_v<Get...>, sizeof...(Get), sizeof...(Exclude)> {
|
|
using base_type = basic_common_view<stl::common_type_t<typename Get::base_type...>, internal::tombstone_check_v<Get...>, sizeof...(Get), sizeof...(Exclude)>;
|
|
|
|
template<stl::size_t Index>
|
|
using element_at = type_list_element_t<Index, type_list<Get..., Exclude...>>;
|
|
|
|
template<typename Type>
|
|
static constexpr stl::size_t index_of = type_list_index_v<stl::remove_const_t<Type>, type_list<typename Get::element_type..., typename Exclude::element_type...>>;
|
|
|
|
template<stl::size_t Curr, stl::size_t Other, typename... Args>
|
|
[[nodiscard]] auto dispatch_get(const stl::tuple<typename base_type::entity_type, Args...> &curr) const {
|
|
if constexpr(Curr == Other) {
|
|
return stl::forward_as_tuple(stl::get<Args>(curr)...);
|
|
} else {
|
|
return storage<Other>()->get_as_tuple(stl::get<0>(curr));
|
|
}
|
|
}
|
|
|
|
template<stl::size_t Curr, typename Func, stl::size_t... Index>
|
|
void each(Func func, stl::index_sequence<Index...>) const {
|
|
for(const auto curr: storage<Curr>()->each()) {
|
|
if(const auto entt = stl::get<0>(curr); (!internal::tombstone_check_v<Get...> || (entt != tombstone)) && ((Curr == Index || base_type::pool_at(Index)->contains(entt)) && ...) && base_type::none_of(entt)) {
|
|
if constexpr(is_applicable_v<Func, decltype(stl::tuple_cat(stl::tuple<entity_type>{}, stl::declval<basic_view>().get({})))>) {
|
|
stl::apply(func, stl::tuple_cat(stl::make_tuple(entt), dispatch_get<Curr, Index>(curr)...));
|
|
} else {
|
|
stl::apply(func, stl::tuple_cat(dispatch_get<Curr, Index>(curr)...));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
template<typename Type>
|
|
void storage_if(Type *elem) noexcept {
|
|
if(elem != nullptr) {
|
|
storage<index_of<typename Type::element_type>>(*elem);
|
|
}
|
|
}
|
|
|
|
public:
|
|
/*! @brief Common type among all storage types. */
|
|
using common_type = base_type::common_type;
|
|
/*! @brief Underlying entity identifier. */
|
|
using entity_type = base_type::entity_type;
|
|
/*! @brief Unsigned integer type. */
|
|
using size_type = base_type::size_type;
|
|
/*! @brief Signed integer type. */
|
|
using difference_type = stl::ptrdiff_t;
|
|
/*! @brief Forward iterator type. */
|
|
using iterator = base_type::iterator;
|
|
/*! @brief Iterable view type. */
|
|
using iterable = iterable_adaptor<internal::extended_view_iterator<iterator, Get...>>;
|
|
|
|
/*! @brief Default constructor to use to create empty, invalid views. */
|
|
basic_view() noexcept
|
|
: base_type{} {}
|
|
|
|
/**
|
|
* @brief Constructs a view from a set of storage classes.
|
|
* @param value The storage for the types to iterate.
|
|
* @param excl The storage for the types used to filter the view.
|
|
*/
|
|
basic_view(Get &...value, Exclude &...excl) noexcept
|
|
: base_type{{&value...}, {&excl...}} {
|
|
}
|
|
|
|
/**
|
|
* @brief Constructs a view from a set of storage classes.
|
|
* @param value The storage for the types to iterate.
|
|
* @param excl The storage for the types used to filter the view.
|
|
*/
|
|
basic_view(stl::tuple<Get &...> value, stl::tuple<Exclude &...> excl = {}) noexcept
|
|
: basic_view{stl::make_from_tuple<basic_view>(stl::tuple_cat(value, excl))} {}
|
|
|
|
/**
|
|
* @brief Constructs a view from a convertible counterpart.
|
|
* @tparam Args Storage types managed by the other view.
|
|
* @param other A view to convert from.
|
|
*/
|
|
template<typename... Args>
|
|
requires (!stl::same_as<basic_view, basic_view<Args...>>)
|
|
basic_view(const basic_view<Args...> &other) noexcept
|
|
: basic_view{} {
|
|
(storage_if(other.template storage<typename Get::element_type>()), ...);
|
|
(storage_if(other.template storage<typename Exclude::element_type>()), ...);
|
|
}
|
|
|
|
/**
|
|
* @brief Forces a view to use a given element to drive iterations
|
|
* @tparam Type Type of element to use to drive iterations.
|
|
*/
|
|
template<typename Type>
|
|
void use() noexcept {
|
|
use<index_of<Type>>();
|
|
}
|
|
|
|
/**
|
|
* @brief Forces a view to use a given element to drive iterations
|
|
* @tparam Index Index of the element to use to drive iterations.
|
|
*/
|
|
template<stl::size_t Index>
|
|
void use() noexcept {
|
|
base_type::use(Index);
|
|
}
|
|
|
|
/**
|
|
* @brief Returns the storage for a given element type, if any.
|
|
* @tparam Type Type of element of which to return the storage.
|
|
* @return The storage for the given element type.
|
|
*/
|
|
template<typename Type>
|
|
[[nodiscard]] auto *storage() const noexcept {
|
|
return storage<index_of<Type>>();
|
|
}
|
|
|
|
/**
|
|
* @brief Returns the storage for a given index, if any.
|
|
* @tparam Index Index of the storage to return.
|
|
* @return The storage for the given index.
|
|
*/
|
|
template<stl::size_t Index>
|
|
[[nodiscard]] auto *storage() const noexcept {
|
|
if constexpr(Index < sizeof...(Get)) {
|
|
return static_cast<element_at<Index> *>(const_cast<constness_as_t<common_type, element_at<Index>> *>(base_type::pool_at(Index)));
|
|
} else {
|
|
return static_cast<element_at<Index> *>(const_cast<constness_as_t<common_type, element_at<Index>> *>(base_type::filter_at(Index - sizeof...(Get))));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Assigns a storage to a view.
|
|
* @tparam Type Type of storage to assign to the view.
|
|
* @param elem A storage to assign to the view.
|
|
*/
|
|
template<typename Type>
|
|
void storage(Type &elem) noexcept {
|
|
storage<index_of<typename Type::element_type>>(elem);
|
|
}
|
|
|
|
/**
|
|
* @brief Assigns a storage to a view.
|
|
* @tparam Index Index of the storage to assign to the view.
|
|
* @tparam Type Type of storage to assign to the view.
|
|
* @param elem A storage to assign to the view.
|
|
*/
|
|
template<stl::size_t Index, typename Type>
|
|
void storage(Type &elem) noexcept {
|
|
static_assert(stl::is_convertible_v<Type &, element_at<Index> &>, "Unexpected type");
|
|
|
|
if constexpr(Index < sizeof...(Get)) {
|
|
base_type::pool_at(Index, &elem);
|
|
} else {
|
|
base_type::filter_at(Index - sizeof...(Get), &elem);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Returns the elements assigned to the given entity.
|
|
* @param entt A valid identifier.
|
|
* @return The elements assigned to the given entity.
|
|
*/
|
|
[[nodiscard]] decltype(auto) operator[](const entity_type entt) const {
|
|
return get(entt);
|
|
}
|
|
|
|
/**
|
|
* @brief Returns the elements assigned to the given entity.
|
|
* @tparam Type Type of the element to get.
|
|
* @tparam Other Other types of elements to get.
|
|
* @param entt A valid identifier.
|
|
* @return The elements assigned to the entity.
|
|
*/
|
|
template<typename Type, typename... Other>
|
|
[[nodiscard]] decltype(auto) get(const entity_type entt) const {
|
|
return get<index_of<Type>, index_of<Other>...>(entt);
|
|
}
|
|
|
|
/**
|
|
* @brief Returns the elements assigned to the given entity.
|
|
* @tparam Index Indexes of the elements to get.
|
|
* @param entt A valid identifier.
|
|
* @return The elements assigned to the entity.
|
|
*/
|
|
template<stl::size_t... Index>
|
|
[[nodiscard]] decltype(auto) get(const entity_type entt) const {
|
|
if constexpr(sizeof...(Index) == 0) {
|
|
return [this, entt]<auto... Idx>(stl::index_sequence<Idx...>) {
|
|
return stl::tuple_cat(this->storage<Idx>()->get_as_tuple(entt)...);
|
|
}(stl::index_sequence_for<Get...>{});
|
|
} else if constexpr(sizeof...(Index) == 1) {
|
|
return (storage<Index>()->get(entt), ...);
|
|
} else {
|
|
return stl::tuple_cat(storage<Index>()->get_as_tuple(entt)...);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Iterates entities and elements and applies the given function
|
|
* object to them.
|
|
*
|
|
* The signature of the function must be equivalent to one of the following
|
|
* (non-empty types only, constness as requested):
|
|
*
|
|
* @code{.cpp}
|
|
* void(const entity_type, Type &...);
|
|
* void(Type &...);
|
|
* @endcode
|
|
*
|
|
* @tparam Func Type of the function object to invoke.
|
|
* @param func A valid function object.
|
|
*/
|
|
template<typename Func>
|
|
void each(Func func) const {
|
|
[this, &func]<auto... Index>(stl::index_sequence<Index...> seq) {
|
|
if(const auto *view = base_type::handle(); view != nullptr) {
|
|
((view == base_type::pool_at(Index) ? each<Index>(stl::move(func), seq) : void()), ...);
|
|
}
|
|
}(stl::index_sequence_for<Get...>{});
|
|
}
|
|
|
|
/**
|
|
* @brief Returns an iterable object to use to _visit_ a view.
|
|
*
|
|
* The iterable object returns a tuple that contains the current entity and
|
|
* a set of references to its non-empty elements. The _constness_ of the
|
|
* elements is as requested.
|
|
*
|
|
* @return An iterable object to use to _visit_ the view.
|
|
*/
|
|
[[nodiscard]] iterable each() const noexcept {
|
|
return iterable{base_type::begin(), base_type::end()};
|
|
}
|
|
|
|
/**
|
|
* @brief Combines a view and a storage in _more specific_ view.
|
|
* @tparam OGet Type of storage to combine the view with.
|
|
* @param other The storage for the type to combine the view with.
|
|
* @return A more specific view.
|
|
*/
|
|
template<stl::derived_from<common_type> OGet>
|
|
[[nodiscard]] basic_view<get_t<Get..., OGet>, exclude_t<Exclude...>> operator|(OGet &other) const noexcept {
|
|
return *this | basic_view<get_t<OGet>, exclude_t<>>{other};
|
|
}
|
|
|
|
/**
|
|
* @brief Combines two views in a _more specific_ one.
|
|
* @tparam OGet Element list of the view to combine with.
|
|
* @tparam OExclude Filter list of the view to combine with.
|
|
* @param other The view to combine with.
|
|
* @return A more specific view.
|
|
*/
|
|
template<stl::derived_from<common_type>... OGet, stl::derived_from<common_type>... OExclude>
|
|
[[nodiscard]] auto operator|(const basic_view<get_t<OGet...>, exclude_t<OExclude...>> &other) const noexcept {
|
|
return internal::view_pack<basic_view<get_t<Get..., OGet...>, exclude_t<Exclude..., OExclude...>>>(
|
|
*this, other, stl::index_sequence_for<Get...>{}, stl::index_sequence_for<Exclude...>{}, stl::index_sequence_for<OGet...>{}, stl::index_sequence_for<OExclude...>{});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @brief Basic storage view implementation.
|
|
* @warning For internal use only, backward compatibility not guaranteed.
|
|
* @tparam Type Common type among all storage types.
|
|
* @tparam Policy Storage policy.
|
|
*/
|
|
template<cvref_unqualified Type, deletion_policy Policy>
|
|
class basic_storage_view {
|
|
protected:
|
|
/*! @cond ENTT_INTERNAL */
|
|
basic_storage_view() noexcept = default;
|
|
|
|
basic_storage_view(const Type *value) noexcept
|
|
: leading{value} {
|
|
ENTT_ASSERT(leading->policy() == Policy, "Unexpected storage policy");
|
|
}
|
|
/*! @endcond */
|
|
|
|
public:
|
|
/*! @brief Common type among all storage types. */
|
|
using common_type = Type;
|
|
/*! @brief Underlying entity identifier. */
|
|
using entity_type = common_type::entity_type;
|
|
/*! @brief Unsigned integer type. */
|
|
using size_type = stl::size_t;
|
|
/*! @brief Signed integer type. */
|
|
using difference_type = stl::ptrdiff_t;
|
|
/*! @brief Random access iterator type. */
|
|
using iterator = stl::conditional_t<Policy == deletion_policy::in_place, internal::view_iterator<common_type, true, 1u, 0u>, typename common_type::iterator>;
|
|
/*! @brief Reverse iterator type. */
|
|
using reverse_iterator = stl::conditional_t<Policy == deletion_policy::in_place, void, typename common_type::reverse_iterator>;
|
|
|
|
/**
|
|
* @brief Returns the leading storage of a view, if any.
|
|
* @return The leading storage of the view.
|
|
*/
|
|
[[nodiscard]] const common_type *handle() const noexcept {
|
|
return leading;
|
|
}
|
|
|
|
/**
|
|
* @brief Returns the number of entities that have the given element.
|
|
* @return Number of entities that have the given element.
|
|
*/
|
|
[[nodiscard]] size_type size() const noexcept
|
|
requires (Policy != deletion_policy::in_place) {
|
|
if constexpr(Policy == deletion_policy::swap_and_pop) {
|
|
return leading ? leading->size() : size_type{};
|
|
} else {
|
|
static_assert(Policy == deletion_policy::swap_only, "Unexpected storage policy");
|
|
return leading ? leading->free_list() : size_type{};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Estimates the number of entities iterated by the view.
|
|
* @return Estimated number of entities iterated by the view.
|
|
*/
|
|
[[nodiscard]] size_type size_hint() const noexcept
|
|
requires (Policy == deletion_policy::in_place) {
|
|
return leading ? leading->size() : size_type{};
|
|
}
|
|
|
|
/**
|
|
* @brief Checks whether a view is empty.
|
|
* @return True if the view is empty, false otherwise.
|
|
*/
|
|
[[nodiscard]] bool empty() const noexcept
|
|
requires (Policy != deletion_policy::in_place) {
|
|
if constexpr(Policy == deletion_policy::swap_and_pop) {
|
|
return !leading || leading->empty();
|
|
} else {
|
|
static_assert(Policy == deletion_policy::swap_only, "Unexpected storage policy");
|
|
return !leading || (leading->free_list() == 0u);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Returns an iterator to the first entity of the view.
|
|
*
|
|
* If the view is empty, the returned iterator will be equal to `end()`.
|
|
*
|
|
* @return An iterator to the first entity of the view.
|
|
*/
|
|
[[nodiscard]] iterator begin() const noexcept {
|
|
if constexpr(Policy == deletion_policy::swap_and_pop) {
|
|
return leading ? leading->begin() : iterator{};
|
|
} else if constexpr(Policy == deletion_policy::swap_only) {
|
|
return leading ? (leading->end() - static_cast<difference_type>(leading->free_list())) : iterator{};
|
|
} else {
|
|
static_assert(Policy == deletion_policy::in_place, "Unexpected storage policy");
|
|
return leading ? iterator{leading->begin(), {leading}, {}, 0u} : iterator{};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Returns an iterator that is past the last entity of the view.
|
|
* @return An iterator to the entity following the last entity of the view.
|
|
*/
|
|
[[nodiscard]] iterator end() const noexcept {
|
|
if constexpr(Policy == deletion_policy::swap_and_pop || Policy == deletion_policy::swap_only) {
|
|
return leading ? leading->end() : iterator{};
|
|
} else {
|
|
static_assert(Policy == deletion_policy::in_place, "Unexpected storage policy");
|
|
return leading ? iterator{leading->end(), {leading}, {}, 0u} : iterator{};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Returns an iterator to the first entity of the reversed view.
|
|
*
|
|
* If the view is empty, the returned iterator will be equal to `rend()`.
|
|
*
|
|
* @return An iterator to the first entity of the reversed view.
|
|
*/
|
|
[[nodiscard]] reverse_iterator rbegin() const noexcept
|
|
requires (Policy != deletion_policy::in_place) {
|
|
return leading ? leading->rbegin() : reverse_iterator{};
|
|
}
|
|
|
|
/**
|
|
* @brief Returns an iterator that is past the last entity of the reversed
|
|
* view.
|
|
* @return An iterator to the entity following the last entity of the
|
|
* reversed view.
|
|
*/
|
|
[[nodiscard]] reverse_iterator rend() const noexcept
|
|
requires (Policy != deletion_policy::in_place) {
|
|
if constexpr(Policy == deletion_policy::swap_and_pop) {
|
|
return leading ? leading->rend() : reverse_iterator{};
|
|
} else {
|
|
static_assert(Policy == deletion_policy::swap_only, "Unexpected storage policy");
|
|
return leading ? (leading->rbegin() + static_cast<difference_type>(leading->free_list())) : reverse_iterator{};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Returns the first entity of the view, if any.
|
|
* @return The first entity of the view if one exists, the null entity
|
|
* otherwise.
|
|
*/
|
|
[[nodiscard]] entity_type front() const noexcept {
|
|
if constexpr(Policy == deletion_policy::swap_and_pop) {
|
|
return empty() ? null : *leading->begin();
|
|
} else if constexpr(Policy == deletion_policy::swap_only) {
|
|
return empty() ? null : *(leading->end() - static_cast<difference_type>(leading->free_list()));
|
|
} else {
|
|
static_assert(Policy == deletion_policy::in_place, "Unexpected storage policy");
|
|
const auto it = begin();
|
|
return (it == end()) ? null : *it;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Returns the last entity of the view, if any.
|
|
* @return The last entity of the view if one exists, the null entity
|
|
* otherwise.
|
|
*/
|
|
[[nodiscard]] entity_type back() const noexcept {
|
|
if constexpr(Policy == deletion_policy::swap_and_pop || Policy == deletion_policy::swap_only) {
|
|
return empty() ? null : *leading->rbegin();
|
|
} else {
|
|
static_assert(Policy == deletion_policy::in_place, "Unexpected storage policy");
|
|
|
|
if(leading) {
|
|
auto it = leading->rbegin();
|
|
const auto last = leading->rend();
|
|
for(; (it != last) && (*it == tombstone); ++it) {}
|
|
return it == last ? null : *it;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Finds an entity.
|
|
* @param entt A valid identifier.
|
|
* @return An iterator to the given entity if it's found, past the end
|
|
* iterator otherwise.
|
|
*/
|
|
[[nodiscard]] iterator find(const entity_type entt) const noexcept {
|
|
if constexpr(Policy == deletion_policy::swap_and_pop) {
|
|
return leading ? leading->find(entt) : iterator{};
|
|
} else if constexpr(Policy == deletion_policy::swap_only) {
|
|
const auto it = leading ? leading->find(entt) : iterator{};
|
|
return leading && (static_cast<size_type>(it.index()) < leading->free_list()) ? it : iterator{};
|
|
} else {
|
|
return leading ? iterator{leading->find(entt), {leading}, {}, 0u} : iterator{};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Checks if a view is fully initialized.
|
|
* @return True if the view is fully initialized, false otherwise.
|
|
*/
|
|
[[nodiscard]] explicit operator bool() const noexcept {
|
|
return (leading != nullptr);
|
|
}
|
|
|
|
/**
|
|
* @brief Checks if a view contains an entity.
|
|
* @param entt A valid identifier.
|
|
* @return True if the view contains the given entity, false otherwise.
|
|
*/
|
|
[[nodiscard]] bool contains(const entity_type entt) const noexcept {
|
|
if constexpr(Policy == deletion_policy::swap_and_pop || Policy == deletion_policy::in_place) {
|
|
return leading && leading->contains(entt);
|
|
} else {
|
|
static_assert(Policy == deletion_policy::swap_only, "Unexpected storage policy");
|
|
return leading && leading->contains(entt) && (leading->index(entt) < leading->free_list());
|
|
}
|
|
}
|
|
|
|
private:
|
|
const common_type *leading{};
|
|
};
|
|
|
|
/**
|
|
* @brief Storage view specialization.
|
|
*
|
|
* This specialization offers a boost in terms of performance. It can access the
|
|
* underlying data structure directly and avoid superfluous checks.
|
|
*
|
|
* @sa basic_view
|
|
*
|
|
* @tparam Get Type of storage iterated by the view.
|
|
*/
|
|
template<typename Get>
|
|
class basic_view<get_t<Get>, exclude_t<>>
|
|
: public basic_storage_view<typename Get::base_type, Get::storage_policy> {
|
|
using base_type = basic_storage_view<typename Get::base_type, Get::storage_policy>;
|
|
|
|
void storage_if(Get *value) noexcept {
|
|
if(value != nullptr) {
|
|
storage(*value);
|
|
}
|
|
}
|
|
|
|
public:
|
|
/*! @brief Common type among all storage types. */
|
|
using common_type = base_type::common_type;
|
|
/*! @brief Underlying entity identifier. */
|
|
using entity_type = base_type::entity_type;
|
|
/*! @brief Unsigned integer type. */
|
|
using size_type = base_type::size_type;
|
|
/*! @brief Signed integer type. */
|
|
using difference_type = stl::ptrdiff_t;
|
|
/*! @brief Random access iterator type. */
|
|
using iterator = base_type::iterator;
|
|
/*! @brief Reverse iterator type. */
|
|
using reverse_iterator = base_type::reverse_iterator;
|
|
/*! @brief Iterable view type. */
|
|
using iterable = stl::conditional_t<Get::storage_policy == deletion_policy::in_place, iterable_adaptor<internal::extended_view_iterator<iterator, Get>>, decltype(stl::declval<Get>().each())>;
|
|
|
|
/*! @brief Default constructor to use to create empty, invalid views. */
|
|
basic_view() noexcept
|
|
: base_type{} {}
|
|
|
|
/**
|
|
* @brief Constructs a view from a storage class.
|
|
* @param value The storage for the type to iterate.
|
|
*/
|
|
basic_view(Get &value) noexcept
|
|
: base_type{&value} {
|
|
}
|
|
|
|
/**
|
|
* @brief Constructs a view from a storage class.
|
|
* @param value The storage for the type to iterate.
|
|
*/
|
|
basic_view(stl::tuple<Get &> value, stl::tuple<> = {}) noexcept
|
|
: basic_view{stl::get<0>(value)} {}
|
|
|
|
/**
|
|
* @brief Constructs a view from a convertible counterpart.
|
|
* @tparam Args Storage types managed by the other view.
|
|
* @param other A view to convert from.
|
|
*/
|
|
template<typename... Args>
|
|
requires (!stl::same_as<basic_view, basic_view<Args...>>)
|
|
basic_view(const basic_view<Args...> &other) noexcept
|
|
: base_type{} {
|
|
storage_if(other.template storage<typename Get::element_type>());
|
|
}
|
|
|
|
/**
|
|
* @brief Returns the storage for a given element type, if any.
|
|
* @tparam Type Type of element of which to return the storage.
|
|
* @return The storage for the given element type.
|
|
*/
|
|
template<typename Type = Get::element_type>
|
|
[[nodiscard]] auto *storage() const noexcept {
|
|
static_assert(stl::is_same_v<stl::remove_const_t<Type>, typename Get::element_type>, "Invalid element type");
|
|
return storage<0>();
|
|
}
|
|
|
|
/**
|
|
* @brief Returns the storage for a given index, if any.
|
|
* @tparam Index Index of the storage to return.
|
|
* @return The storage for the given index.
|
|
*/
|
|
template<stl::size_t Index>
|
|
[[nodiscard]] auto *storage() const noexcept {
|
|
static_assert(Index == 0u, "Index out of bounds");
|
|
return static_cast<Get *>(const_cast<constness_as_t<common_type, Get> *>(base_type::handle()));
|
|
}
|
|
|
|
/**
|
|
* @brief Assigns a storage to a view.
|
|
* @param elem A storage to assign to the view.
|
|
*/
|
|
void storage(Get &elem) noexcept {
|
|
storage<0>(elem);
|
|
}
|
|
|
|
/**
|
|
* @brief Assigns a storage to a view.
|
|
* @tparam Index Index of the storage to assign to the view.
|
|
* @param elem A storage to assign to the view.
|
|
*/
|
|
template<stl::size_t Index>
|
|
void storage(Get &elem) noexcept {
|
|
static_assert(Index == 0u, "Index out of bounds");
|
|
*this = basic_view{elem};
|
|
}
|
|
|
|
/**
|
|
* @brief Returns a pointer to the underlying storage.
|
|
* @return A pointer to the underlying storage.
|
|
*/
|
|
[[nodiscard]] Get *operator->() const noexcept {
|
|
return storage();
|
|
}
|
|
|
|
/**
|
|
* @brief Returns the element assigned to the given entity.
|
|
* @param entt A valid identifier.
|
|
* @return The element assigned to the given entity.
|
|
*/
|
|
[[nodiscard]] decltype(auto) operator[](const entity_type entt) const {
|
|
return storage()->get(entt);
|
|
}
|
|
|
|
/**
|
|
* @brief Returns the element assigned to the given entity.
|
|
* @tparam Elem Type of the element to get.
|
|
* @param entt A valid identifier.
|
|
* @return The element assigned to the entity.
|
|
*/
|
|
template<typename Elem>
|
|
[[nodiscard]] decltype(auto) get(const entity_type entt) const {
|
|
static_assert(stl::is_same_v<stl::remove_const_t<Elem>, typename Get::element_type>, "Invalid element type");
|
|
return get<0>(entt);
|
|
}
|
|
|
|
/**
|
|
* @brief Returns the element assigned to the given entity.
|
|
* @tparam Index Index of the element to get.
|
|
* @param entt A valid identifier.
|
|
* @return The element assigned to the entity.
|
|
*/
|
|
template<stl::size_t... Index>
|
|
[[nodiscard]] decltype(auto) get(const entity_type entt) const {
|
|
if constexpr(sizeof...(Index) == 0) {
|
|
return storage()->get_as_tuple(entt);
|
|
} else {
|
|
return storage<Index...>()->get(entt);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Iterates entities and elements and applies the given function
|
|
* object to them.
|
|
*
|
|
* The signature of the function must be equivalent to one of the following
|
|
* (non-empty types only, constness as requested):
|
|
*
|
|
* @code{.cpp}
|
|
* void(const entity_type, Type &);
|
|
* void(Type &);
|
|
* @endcode
|
|
*
|
|
* @tparam Func Type of the function object to invoke.
|
|
* @param func A valid function object.
|
|
*/
|
|
template<typename Func>
|
|
void each(Func func) const {
|
|
if constexpr(is_applicable_v<Func, decltype(stl::tuple_cat(stl::tuple<entity_type>{}, stl::declval<basic_view>().get({})))>) {
|
|
for(const auto pack: each()) {
|
|
stl::apply(func, pack);
|
|
}
|
|
} else if constexpr(Get::storage_policy == deletion_policy::swap_and_pop || Get::storage_policy == deletion_policy::swap_only) {
|
|
if constexpr(stl::is_void_v<typename Get::value_type>) {
|
|
for(size_type pos = base_type::size(); pos; --pos) {
|
|
func();
|
|
}
|
|
} else {
|
|
if(const auto len = static_cast<difference_type>(base_type::size()); len != 0) {
|
|
for(auto last = storage()->end(), first = last - len; first != last; ++first) {
|
|
func(*first);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
static_assert(Get::storage_policy == deletion_policy::in_place, "Unexpected storage policy");
|
|
|
|
for(const auto pack: each()) {
|
|
stl::apply([&func](const auto, auto &&...elem) { func(stl::forward<decltype(elem)>(elem)...); }, pack);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Returns an iterable object to use to _visit_ a view.
|
|
*
|
|
* The iterable object returns a tuple that contains the current entity and
|
|
* a reference to its element if it's a non-empty one. The _constness_ of
|
|
* the element is as requested.
|
|
*
|
|
* @return An iterable object to use to _visit_ the view.
|
|
*/
|
|
[[nodiscard]] iterable each() const noexcept {
|
|
if constexpr(Get::storage_policy == deletion_policy::swap_and_pop || Get::storage_policy == deletion_policy::swap_only) {
|
|
return base_type::handle() ? storage()->each() : iterable{};
|
|
} else {
|
|
static_assert(Get::storage_policy == deletion_policy::in_place, "Unexpected storage policy");
|
|
return iterable{base_type::begin(), base_type::end()};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Combines a view and a storage in _more specific_ view.
|
|
* @tparam OGet Type of storage to combine the view with.
|
|
* @param other The storage for the type to combine the view with.
|
|
* @return A more specific view.
|
|
*/
|
|
template<stl::derived_from<common_type> OGet>
|
|
[[nodiscard]] basic_view<get_t<Get, OGet>, exclude_t<>> operator|(OGet &other) const noexcept {
|
|
return *this | basic_view<get_t<OGet>, exclude_t<>>{other};
|
|
}
|
|
|
|
/**
|
|
* @brief Combines two views in a _more specific_ one.
|
|
* @tparam OGet Element list of the view to combine with.
|
|
* @tparam OExclude Filter list of the view to combine with.
|
|
* @param other The view to combine with.
|
|
* @return A more specific view.
|
|
*/
|
|
template<stl::derived_from<common_type>... OGet, stl::derived_from<common_type>... OExclude>
|
|
[[nodiscard]] auto operator|(const basic_view<get_t<OGet...>, exclude_t<OExclude...>> &other) const noexcept {
|
|
return internal::view_pack<basic_view<get_t<Get, OGet...>, exclude_t<OExclude...>>>(
|
|
*this, other, stl::index_sequence_for<Get>{}, stl::index_sequence_for<>{}, stl::index_sequence_for<OGet...>{}, stl::index_sequence_for<OExclude...>{});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @brief Deduction guide.
|
|
* @tparam Type Type of storage classes used to create the view.
|
|
* @param storage The storage for the types to iterate.
|
|
*/
|
|
template<typename... Type>
|
|
basic_view(Type &...storage) -> basic_view<get_t<Type...>, exclude_t<>>;
|
|
|
|
/**
|
|
* @brief Deduction guide.
|
|
* @tparam Get Types of elements iterated by the view.
|
|
* @tparam Exclude Types of elements used to filter the view.
|
|
*/
|
|
template<typename... Get, typename... Exclude>
|
|
basic_view(stl::tuple<Get &...>, stl::tuple<Exclude &...> = {}) -> basic_view<get_t<Get...>, exclude_t<Exclude...>>;
|
|
|
|
} // namespace entt
|
|
|
|
#endif
|