Files
Cubed/include/entt/entity/storage.hpp
zhenyan121 236e7c0433 feature: creature (#38)
* build(deps): add assimp library as dependency

* feat(render): add model loading and rendering pipeline

* refactor(render): rename depth player shaders to depth model and clean up

* build: add EnTT library

* feat(entity): add ECS-based entity rendering

* feat(render): add shadow pass for entity models

* refactor(render): unify model and player rendering with single shader pipeline

* refactor(collision): convert AABB to center-half representation and migrate player data to ECS

* refactor(gameplay): add base classes Chunk and World for shared logic

* feat(gameplay): implement entity movement system with collision detection

- Add Velocity and HitBoxes components to entity
- Introduce HitboxManager for loading per-entity collision AABBs from JSON
- Create MoveSystem with per-axis collision handling
- Move get_block_aabb to base World class and add virtual get_per_tick_time
- Remove static get_block_aabb from ClientWorld; use member m_per_tick_time for tick duration

* fix(gameplay): correct hitbox insertion in HitboxManager::load

* build: remove entt library

* refactor(gameplay): replace entt components with SparseVector for player data

Refactor client world to use a custom SparseVector for player data instead of entt registry. Consolidate Transform, ViewAngles, and related structs into Position, Orientation, WalkPose. Introduce PlayerData and PlayerRenderData. Remove the unused move_system.cpp. Unify player render and shadow render into a single function.

* feat(render): add model ID system with concurrent lookup and namespace-based loading

- Extract `ModelID` and `Model` struct to new `model.hpp`
- Replace `std::unordered_map` with `tbb::concurrent_hash_map` for thread safety
- Add `get_model(ModelID)`, `get_model_id`, `get_model_name` methods
- Parse model names in `namespace:name` format to construct asset paths
- Update `load_model` to accept `string_view` and use ID-based management

* refactor(gameplay): extract movement, gravity, orientation, and walk pose into structs

* refactor(gameplay): extract Entity base class from ClientPlayer

Move position, walk pose, velocity, orientation, movement, and gravity
fields and their accessors into a new Entity base class. ClientPlayer now
inherits from Entity, removing duplicated members. Also update velocity
handling to use 3D vector per axis and adjust related logic.

* feat(player): split max speed into horizontal/vertical, set spawn pos

* refactor: move movement logic into SpeedSystem and Entity components

* refactor(gameplay): extract physics and collision from ClientPlayer into PhysicalSystem

Move per-axis collision detection and move distance calculation to new
PhysicalSystem. Move SpeedSystem implementation from inline header to
separate .cpp file. Add const accessors to Entity. Replace inline AABB
helper with HitboxManager registration of player hitbox using new
PLAYER_SIZE constant. Remove obsolete members and functions from
ClientPlayer.

* refactor(gameplay): integrate model and hitbox ID system into Entity

- Introduce HitboxID, EntityID, and ModelID types for safer ID-based lookups
- Replace AABB struct with Hitbox (includes HitboxID)
- Convert ModelManager and HitboxManager to singletons with Handle structs
- Update Entity to store IDs for model and hitbox, removing direct references
- Reorganize creature model assets into subdirectories per entity type
- Add player model (player.glb) and collision data for pig
- Remove ModelManager dependency from App and Renderer
- Add namespace parsing utility for asset paths
- Mark sparse_vector::insert() with [[nodiscard]]

* build: add entt library

* refactor(gameplay): rename ClientPlayer to LocalPlayer

* refactor(gameplay): separate player logic into manager and ECS components

* feat(gameplay): add entity managers and refactor to ECS components

* refactor(gameplay): replace client thread with timer-based system and add entity manager

* feat(gameplay): implement entity system with concurrent task handling and fix model loading

* feat(gameplay): add entity destruction support

* feat(entity): add client-to-server entity creation and destruction requests

Extend the network protocol with `C2SEntityCreateRequest` and `C2SEntityDestoryRequest` packets. Refactor entity managers to split public client-facing create/destroy methods that send requests over the network, from internal handlers that process received packets. Add utility functions for converting protobuf Vec3 to glm::vec3.

* feat(gameplay): implement entity update packets

Add S2CEntityUpdate to sync entity positions from server to client, including update handling in the client entity manager and server-side AI updates.

* feat(ai): add wander AI system with move boost

Add AIBase and WanderAITag components, a WanderAISystem, and a MoveBoost component to control wandering behavior. Replace the old MoveState with MoveBoost and include a horizontal random direction helper.

* refactor(ecs): move entity physics to tick-based systems

Update PhysicalSystem and SpeedSystem to operate on entt::registry
instead of individual components. Add TickVelocity for server creatures
so movement is calculated per tick without frame delta time. LocalPlayer
now implements its own client-side physics with collision detection.

* feat(gameplay): add server-side entity movement and AI

Refactor LocalPlayer::update_physical to operate on a passed position, and run speed, physical, and wander AI systems in ServerEntityManager.

* feat(gameplay): add pig wander AI and refine speed physics

* feat(server): send entity position updates to all sessions

* fix: correct velocity clamping and hitbox loading

Preserve sign when clamping velocity; negative velocities now clamp to zero instead of flipping direction. Support loading hitbox definitions from JSON arrays and objects. Tune pig entity movement, spawn height, and wander probabilities.

* feat(creatures): refine pig movement physics

Add movement constants for pigs and switch deceleration to per-axis friction so creatures stop naturally. Extend wander boost duration for smoother behavior.

* feat(physics): implement step-up for horizontal collisions

* feat: sync entity direction and rotate models accordingly

Move direction into the transform component and include it in server-to-client entity updates. Client now sets transform direction from the network message and uses it to compute yaw for model rotation, so entities visually face their movement direction. Refactor net_utils to support arbitrary Vec3 fields.

* feat(render): interpolate entity transforms for rendering

Add RenderTransform component to smooth position/direction updates
and use it in entity and shadow passes.

* fix(server_world): send time before entity updates

* build: replace nlohmann json with rapidjson

* refactor(json): migrate from nlohmann to rapidjson

Replace nlohmann::json with rapidjson across localization, hitbox manager, server world, and sensitive filter. Add json_utils helper for converting rapidjson documents to maps, and improve parse error handling.

* feat(item): add item manager with JSON asset loading

Implement ItemManager to load and query item definitions from assets/item JSON files. Add ItemData struct, item asset metadata, CMake source registration, and initialize the manager during app startup. Also add AGENTS.md repository guidelines.

* refactor(item): decouple items from block types

The item system now supports an ItemKind and property, allowing item JSON to declare a type instead of assuming every item is a block. BlockManager was moved to its own header, redesigned around concurrent hash maps, and exposes id_from_name(). Texture and UI code now key items by ItemID, while block placement resolves the block type from the item registry.

* fix(gameplay): use inline const for static EMPTY members

Use inline const for static EMPTY members to avoid ODR and linker issues.
Add missing block_manager.hpp includes in gameplay sources.

* fix: stabilize block item registration and display

Store item names as owned strings in ItemManager, set block type
property for block items, validate item kind before placement, and
handle items without textures gracefully in inventory UI.

* feat: add pig spawn egg item

Add pig spawn egg asset and texture, parse spawn egg item kind and creature property, and spawn the configured entity when used on an empty block. Also fix item texture loading to use actual image dimensions and update selected item UI sizing.

* refactor(gameplay): update systems per entity with chunk check

Refactor server entity update flow to process entities individually,
skipping those whose chunk is not loaded. System update methods now
accept a single entity instead of iterating the full registry view,
and `ServerWorld::get_chunk_ref_count` is added to determine if an
entity's chunk is active.

* perf(render): skip entities outside loaded chunks

* feat: add --direct-enter option to skip to world scene

Support a new CLI flag that launches the app directly into the world scene. If no IP is provided, it starts a local server on the specified port and connects to 127.0.0.1; otherwise it connects to the given IP. With this flag enabled, a port must be specified via -p. Also expose SceneManager::push as public so scenes can be pushed immediately when bypassing the normal menu flow.

* refactor(localization): switch block translations to item naming

Remove the `name_key` field from block definitions and use item-based
localization keys (`item.*.name`) for inventory display. Item data now
stores the localized name at load time.

* feat(gameplay): spawn creatures during chunk generation

Add creature spawning to chunk generation with a configurable
SpawnConfig, including a default pig spawn. Spawning occurs in
the final generation phase and registers entities through the
server world's entity manager.

Also destroy entities when their chunk is unloaded to prevent
orphaned entities and expose the entity manager from ServerWorld.

* feat(gameplay): add run mode based thread pool config

Introduce RunMode enum and thread pool sizing helpers for client,
server, and hybrid modes. Add compute pool to server world and pass
mode through server/client initialization.

* perf(server): parallelize entity update loop

Use parallel_do to process entities concurrently via the compute
pool. Switch get_all_session to a thread-safe
tbb::concurrent_vector and make ChunkEntity's ref_count atomic to
avoid data races. Also remove the debug pig spawn from world init.

* feat(creatures): animate creature walk cycles

Add a Gait component synchronized over the network and procedural animation for model nodes. Load animation parameters from assets/model/creature/pig/animation.json and apply leg swing, body bob, and head motion based on gait. Refactor Gait into its own header for reuse.

* fix(gameplay): make entity interpolation frame-rate independent

* perf(render): batch model rendering with instancing

Replaces per-entity draw calls with instanced rendering for both main and shadow passes. Each model's node hierarchy is flattened once and instance matrices are updated per frame, reducing draw calls and CPU overhead.

* refactor(render): build entity instance buffers once per frame

Move instance matrix upload into build_vertices and use a precomputed
instance data map for both shadow and color passes, avoiding duplicate
GPU buffer updates.

* perf(gameplay): batch entity updates into single packet

Aggregate per-entity update messages into S2CEntityUpdateBatch and
broadcast once per tick. Reuse serialized packets in several broadcast
loops to avoid repeated make_packet calls.

* fix: correct thread count and shadow projection uniform

- Add reserved client thread count to client threads in server generation calculation.
- Set projection matrix uniform only for non-shadow rendering.

* feat(ecs): add entity type separation and creature limits

Replace generic entity creation with typed creatures and items. Rename `add_entity` to `add_creature`, enforce per-player creature cap, and track entity/creature totals for metrics. Expose run mode to dev panel and show new counts.

* perf(render): frustum cull entities before instance building

Add frustum culling to entity instance data collection using AABB vs camera frustum planes. Pass renderer into get_instances_data_map to compute MVP and extract planes. Also add "Rendered Entities" debug counter for visibility.

* feat(audio): add ambient pig sounds

Add pig call audio with randomized timing, triggered by proximity to the player as 3D positional sound.

* feat(client-entity): use snapshot history for entity interpolation

Replace exponential smoothing with a buffered snapshot system. Each entity keeps a deque of position/direction snapshots with timestamps; the render transform is interpolated at a fixed 100 ms render delay to hide network jitter. Snapshots are capped to 16 entries.

* refactor(hitbox): move player hitbox definition to JSON asset

* feat(client_player): use snapshot interpolation for remote players

Replace exponential position smoothing with snapshot interpolation. Remote player transforms are rendered from a delayed snapshot history, using linear interpolation for position and shortest-path interpolation for yaw/pitch. A 100 ms render delay compensates for network tick rate.
2026-08-07 10:19:49 +08:00

1223 lines
44 KiB
C++

#ifndef ENTT_ENTITY_STORAGE_HPP
#define ENTT_ENTITY_STORAGE_HPP
#include <compare>
#include "../config/config.h"
#include "../core/bit.hpp"
#include "../core/iterator.hpp"
#include "../core/memory.hpp"
#include "../core/type_info.hpp"
#include "../stl/concepts.hpp"
#include "../stl/cstddef.hpp"
#include "../stl/iterator.hpp"
#include "../stl/memory.hpp"
#include "../stl/tuple.hpp"
#include "../stl/type_traits.hpp"
#include "../stl/utility.hpp"
#include "../stl/vector.hpp"
#include "component.hpp"
#include "entity.hpp"
#include "fwd.hpp"
#include "sparse_set.hpp"
namespace entt {
/*! @cond ENTT_INTERNAL */
namespace internal {
template<typename Container, auto Page>
class storage_iterator final {
template<typename, auto>
friend class storage_iterator;
using container_type = stl::remove_const_t<Container>;
using alloc_traits = stl::allocator_traits<typename container_type::allocator_type>;
using iterator_traits = stl::iterator_traits<stl::conditional_t<
stl::is_const_v<Container>,
typename alloc_traits::template rebind_traits<typename stl::pointer_traits<typename container_type::value_type>::element_type>::const_pointer,
typename alloc_traits::template rebind_traits<typename stl::pointer_traits<typename container_type::value_type>::element_type>::pointer>>;
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::random_access_iterator_tag;
constexpr storage_iterator() noexcept = default;
constexpr storage_iterator(Container *ref, const difference_type idx) noexcept
: payload{ref},
offset{idx} {}
template<stl::same_as<stl::remove_const_t<Container>> Other>
requires stl::is_const_v<Container>
constexpr storage_iterator(const storage_iterator<Other, Page> &other) noexcept
: storage_iterator{other.payload, other.offset} {}
constexpr storage_iterator &operator++() noexcept {
return --offset, *this;
}
constexpr storage_iterator operator++(int) noexcept {
const storage_iterator orig = *this;
return ++(*this), orig;
}
constexpr storage_iterator &operator--() noexcept {
return ++offset, *this;
}
constexpr storage_iterator operator--(int) noexcept {
const storage_iterator orig = *this;
return operator--(), orig;
}
constexpr storage_iterator &operator+=(const difference_type value) noexcept {
offset -= value;
return *this;
}
constexpr storage_iterator operator+(const difference_type value) const noexcept {
storage_iterator copy = *this;
return (copy += value);
}
constexpr storage_iterator &operator-=(const difference_type value) noexcept {
return (*this += -value);
}
constexpr storage_iterator operator-(const difference_type value) const noexcept {
return (*this + -value);
}
[[nodiscard]] constexpr reference operator[](const difference_type value) const noexcept {
const auto pos = static_cast<Container::size_type>(index() - value);
return (*payload)[pos / Page][fast_mod(static_cast<stl::size_t>(pos), Page)];
}
[[nodiscard]] constexpr pointer operator->() const noexcept {
return stl::addressof(operator[](0));
}
[[nodiscard]] constexpr reference operator*() const noexcept {
return operator[](0);
}
template<typename Other, auto Arg>
[[nodiscard]] constexpr stl::ptrdiff_t operator-(const storage_iterator<Other, Arg> &other) const noexcept {
// intentionally reversed due to backward iteration
return other.offset - offset;
}
template<typename Other, auto Arg>
[[nodiscard]] constexpr bool operator==(const storage_iterator<Other, Arg> &other) const noexcept {
return offset == other.offset;
}
template<typename Other, auto Arg>
[[nodiscard]] constexpr auto operator<=>(const storage_iterator<Other, Arg> &other) const noexcept {
// intentionally reversed due to backward iteration
return other.offset <=> offset;
}
[[nodiscard]] constexpr difference_type index() const noexcept {
return offset - 1;
}
private:
Container *payload;
difference_type offset;
};
template<typename It, typename... Other>
class extended_storage_iterator final {
template<typename, typename...>
friend class extended_storage_iterator;
public:
using iterator_type = It;
using value_type = decltype(stl::tuple_cat(stl::make_tuple(*stl::declval<It>()), stl::forward_as_tuple(*stl::declval<Other>()...)));
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_storage_iterator()
: it{} {}
constexpr extended_storage_iterator(iterator_type base, Other... other)
: it{base, other...} {}
template<typename... Args>
requires (!stl::same_as<Other, Args> && ...) && (stl::constructible_from<Other, Args> && ...)
constexpr extended_storage_iterator(const extended_storage_iterator<It, Args...> &other)
: it{other.it} {}
constexpr extended_storage_iterator &operator++() noexcept {
return ++stl::get<It>(it), (++stl::get<Other>(it), ...), *this;
}
constexpr extended_storage_iterator operator++(int) noexcept {
const extended_storage_iterator orig = *this;
return ++(*this), orig;
}
[[nodiscard]] constexpr pointer operator->() const noexcept {
return operator*();
}
[[nodiscard]] constexpr reference operator*() const noexcept {
return {*stl::get<It>(it), *stl::get<Other>(it)...};
}
[[nodiscard]] constexpr iterator_type base() const noexcept {
return stl::get<It>(it);
}
template<typename... Args>
[[nodiscard]] constexpr bool operator==(const extended_storage_iterator<Args...> &other) const noexcept {
return stl::get<0>(it) == stl::get<0>(other.it);
}
private:
stl::tuple<It, Other...> it;
};
} // namespace internal
/*! @endcond */
/**
* @brief Storage implementation.
*
* Internal data structures arrange elements to maximize performance. There are
* no guarantees that objects are returned in the insertion order when iterate
* a storage. Do not make assumption on the order in any case.
*
* @warning
* Empty types aren't explicitly instantiated. Therefore, many of the functions
* normally available for non-empty types will not be available for empty ones.
*
* @tparam Type Element type.
* @tparam Entity A valid entity type.
* @tparam Allocator Type of allocator used to manage memory and elements.
*/
template<typename Type, typename Entity, typename Allocator>
class basic_storage: public basic_sparse_set<Entity, typename stl::allocator_traits<Allocator>::template rebind_alloc<Entity>> {
using alloc_traits = stl::allocator_traits<Allocator>;
static_assert(stl::is_same_v<typename alloc_traits::value_type, Type>, "Invalid value type");
using container_type = stl::vector<typename alloc_traits::pointer, typename alloc_traits::template rebind_alloc<typename alloc_traits::pointer>>;
using underlying_type = basic_sparse_set<Entity, typename alloc_traits::template rebind_alloc<Entity>>;
using underlying_iterator = underlying_type::basic_iterator;
using traits_type = component_traits<Type, Entity>;
[[nodiscard]] auto &element_at(const stl::size_t pos) const {
return payload[pos / traits_type::page_size][fast_mod(pos, traits_type::page_size)];
}
auto assure_at_least(const stl::size_t pos) {
const auto idx = pos / traits_type::page_size;
if(!(idx < payload.size())) {
auto curr = payload.size();
allocator_type allocator{get_allocator()};
payload.resize(idx + 1u, nullptr);
ENTT_TRY {
for(const auto last = payload.size(); curr < last; ++curr) {
payload[curr] = alloc_traits::allocate(allocator, traits_type::page_size);
}
}
ENTT_CATCH {
payload.resize(curr);
ENTT_THROW;
}
}
return payload[idx] + fast_mod(pos, traits_type::page_size);
}
template<typename... Args>
auto emplace_element(const Entity entt, const bool force_back, Args &&...args) {
const auto it = base_type::try_emplace(entt, force_back);
ENTT_TRY {
auto *elem = stl::to_address(assure_at_least(static_cast<size_type>(it.index())));
entt::uninitialized_construct_using_allocator(elem, get_allocator(), stl::forward<Args>(args)...);
}
ENTT_CATCH {
base_type::pop(it, it + 1u);
ENTT_THROW;
}
return it;
}
void shrink_to_size(const stl::size_t sz) {
const auto from = (sz + traits_type::page_size - 1u) / traits_type::page_size;
allocator_type allocator{get_allocator()};
if constexpr(!stl::is_trivially_destructible_v<element_type>) {
for(auto pos = sz, length = base_type::size(); pos < length; ++pos) {
if constexpr(traits_type::in_place_delete) {
if(base_type::data()[pos] != tombstone) {
alloc_traits::destroy(allocator, stl::addressof(element_at(pos)));
}
} else {
alloc_traits::destroy(allocator, stl::addressof(element_at(pos)));
}
}
}
for(auto pos = from, last = payload.size(); pos < last; ++pos) {
alloc_traits::deallocate(allocator, payload[pos], traits_type::page_size);
}
payload.resize(from);
payload.shrink_to_fit();
}
void swap_at(const stl::size_t lhs, const stl::size_t rhs) {
using stl::swap;
swap(element_at(lhs), element_at(rhs));
}
void move_to(const stl::size_t lhs, const stl::size_t rhs) {
auto &elem = element_at(lhs);
allocator_type allocator{get_allocator()};
entt::uninitialized_construct_using_allocator(stl::to_address(assure_at_least(rhs)), allocator, stl::move(elem));
alloc_traits::destroy(allocator, stl::addressof(elem));
}
private:
[[nodiscard]] const void *get_at(const stl::size_t pos) const final {
return stl::addressof(element_at(pos));
}
void swap_or_move([[maybe_unused]] const stl::size_t from, [[maybe_unused]] const stl::size_t to) override {
static constexpr bool is_pinned_type = !(stl::is_move_constructible_v<Type> && stl::is_move_assignable_v<Type>);
// use a runtime value to avoid compile-time suppression that drives the code coverage tool crazy
ENTT_ASSERT((from + 1u) && !is_pinned_type, "Pinned type");
if constexpr(!is_pinned_type) {
if constexpr(traits_type::in_place_delete) {
(base_type::operator[](to) == tombstone) ? move_to(from, to) : swap_at(from, to);
} else {
swap_at(from, to);
}
}
}
protected:
/**
* @brief Erases entities from a storage.
* @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.
*/
void pop(underlying_iterator first, underlying_iterator last) override {
for(allocator_type allocator{get_allocator()}; first != last; ++first) {
// cannot use first.index() because it would break with cross iterators
auto &elem = element_at(base_type::index(*first));
if constexpr(traits_type::in_place_delete) {
base_type::in_place_pop(*first);
alloc_traits::destroy(allocator, stl::addressof(elem));
} else if constexpr(stl::is_trivially_destructible_v<element_type>) {
elem = stl::move(element_at(base_type::size() - 1u));
base_type::swap_and_pop(*first);
} else {
auto &other = element_at(base_type::size() - 1u);
// destroying on exit allows reentrant destructors
[[maybe_unused]] auto unused = stl::exchange(elem, stl::move(other));
alloc_traits::destroy(allocator, stl::addressof(other));
base_type::swap_and_pop(*first);
}
}
}
/*! @brief Erases all entities of a storage. */
void pop_all() override {
if constexpr(stl::is_trivially_destructible_v<element_type>) {
base_type::pop_all();
} else {
allocator_type allocator{get_allocator()};
for(auto first = base_type::begin(); !(first.index() < 0); ++first) {
if constexpr(traits_type::in_place_delete) {
if(*first != tombstone) {
base_type::in_place_pop(*first);
alloc_traits::destroy(allocator, stl::addressof(element_at(static_cast<size_type>(first.index()))));
}
} else {
base_type::swap_and_pop(*first);
alloc_traits::destroy(allocator, stl::addressof(element_at(static_cast<size_type>(first.index()))));
}
}
}
}
/**
* @brief Assigns an entity to a storage.
* @param entt A valid identifier.
* @param value Optional opaque value.
* @param force_back Force back insertion.
* @return Iterator pointing to the emplaced element.
*/
underlying_iterator try_emplace([[maybe_unused]] const Entity entt, [[maybe_unused]] const bool force_back, const void *value) override {
if(value != nullptr) {
if constexpr(stl::is_copy_constructible_v<element_type>) {
return emplace_element(entt, force_back, *static_cast<const element_type *>(value));
} else {
return base_type::end();
}
} else {
if constexpr(stl::is_default_constructible_v<element_type>) {
return emplace_element(entt, force_back);
} else {
return base_type::end();
}
}
}
public:
/*! @brief Allocator type. */
using allocator_type = Allocator;
/*! @brief Base type. */
using base_type = underlying_type;
/*! @brief Element type. */
using element_type = Type;
/*! @brief Type of the objects assigned to entities. */
using value_type = element_type;
/*! @brief Underlying entity identifier. */
using entity_type = Entity;
/*! @brief Unsigned integer type. */
using size_type = stl::size_t;
/*! @brief Signed integer type. */
using difference_type = stl::ptrdiff_t;
/*! @brief Pointer type to contained elements. */
using pointer = container_type::pointer;
/*! @brief Constant pointer type to contained elements. */
using const_pointer = alloc_traits::template rebind_traits<typename alloc_traits::const_pointer>::const_pointer;
/*! @brief Random access iterator type. */
using iterator = internal::storage_iterator<container_type, traits_type::page_size>;
/*! @brief Constant random access iterator type. */
using const_iterator = internal::storage_iterator<const container_type, traits_type::page_size>;
/*! @brief Reverse iterator type. */
using reverse_iterator = stl::reverse_iterator<iterator>;
/*! @brief Constant reverse iterator type. */
using const_reverse_iterator = stl::reverse_iterator<const_iterator>;
/*! @brief Extended iterable storage proxy. */
using iterable = iterable_adaptor<internal::extended_storage_iterator<typename base_type::iterator, iterator>>;
/*! @brief Constant extended iterable storage proxy. */
using const_iterable = iterable_adaptor<internal::extended_storage_iterator<typename base_type::const_iterator, const_iterator>>;
/*! @brief Extended reverse iterable storage proxy. */
using reverse_iterable = iterable_adaptor<internal::extended_storage_iterator<typename base_type::reverse_iterator, reverse_iterator>>;
/*! @brief Constant extended reverse iterable storage proxy. */
using const_reverse_iterable = iterable_adaptor<internal::extended_storage_iterator<typename base_type::const_reverse_iterator, const_reverse_iterator>>;
/*! @brief Storage deletion policy. */
static constexpr deletion_policy storage_policy{traits_type::in_place_delete};
/*! @brief Default constructor. */
basic_storage()
: basic_storage{allocator_type{}} {}
/**
* @brief Constructs an empty storage with a given allocator.
* @param allocator The allocator to use.
*/
explicit basic_storage(const allocator_type &allocator)
: base_type{type_id<element_type>(), storage_policy, allocator},
payload{allocator} {}
/*! @brief Default copy constructor, deleted on purpose. */
basic_storage(const basic_storage &) = delete;
/**
* @brief Move constructor.
* @param other The instance to move from.
*/
basic_storage(basic_storage &&other) noexcept
: base_type{static_cast<base_type &&>(other)},
payload{stl::move(other.payload)} {}
/**
* @brief Allocator-extended move constructor.
* @param other The instance to move from.
* @param allocator The allocator to use.
*/
basic_storage(basic_storage &&other, const allocator_type &allocator)
: base_type{static_cast<base_type &&>(other), allocator},
payload{stl::move(other.payload), allocator} {
ENTT_ASSERT(alloc_traits::is_always_equal::value || get_allocator() == other.get_allocator(), "Copying a storage is not allowed");
}
/*! @brief Default destructor. */
// NOLINTNEXTLINE(bugprone-exception-escape)
~basic_storage() override {
shrink_to_size(0u);
}
/**
* @brief Default copy assignment operator, deleted on purpose.
* @return This storage.
*/
basic_storage &operator=(const basic_storage &) = delete;
/**
* @brief Move assignment operator.
* @param other The instance to move from.
* @return This storage.
*/
basic_storage &operator=(basic_storage &&other) noexcept {
ENTT_ASSERT(alloc_traits::is_always_equal::value || get_allocator() == other.get_allocator(), "Copying a storage is not allowed");
swap(other);
return *this;
}
/**
* @brief Exchanges the contents with those of a given storage.
* @param other Storage to exchange the content with.
*/
void swap(basic_storage &other) noexcept {
using stl::swap;
swap(payload, other.payload);
base_type::swap(other);
}
/**
* @brief Returns the associated allocator.
* @return The associated allocator.
*/
[[nodiscard]] constexpr allocator_type get_allocator() const noexcept {
return payload.get_allocator();
}
/**
* @brief Increases the capacity of a storage.
*
* If the new capacity is greater than the current capacity, new storage is
* allocated, otherwise the method does nothing.
*
* @param cap Desired capacity.
*/
void reserve(const size_type cap) override {
if(cap != 0u) {
base_type::reserve(cap);
assure_at_least(cap - 1u);
}
}
/**
* @brief Returns the number of elements that a storage has currently
* allocated space for.
* @return Capacity of the storage.
*/
[[nodiscard]] size_type capacity() const noexcept override {
return payload.size() * traits_type::page_size;
}
/*! @brief Requests the removal of unused capacity. */
void shrink_to_fit() override {
base_type::shrink_to_fit();
shrink_to_size(base_type::size());
}
/**
* @brief Direct access to the array of objects.
* @return A pointer to the array of objects.
*/
[[nodiscard]] const_pointer raw() const noexcept {
return payload.data();
}
/*! @copydoc raw */
[[nodiscard]] pointer raw() noexcept {
return payload.data();
}
/**
* @brief Returns an iterator to the beginning.
*
* If the storage is empty, the returned iterator will be equal to `end()`.
*
* @return An iterator to the first instance of the internal array.
*/
[[nodiscard]] const_iterator cbegin() const noexcept {
const auto pos = static_cast<difference_type>(base_type::size());
return const_iterator{&payload, pos};
}
/*! @copydoc cbegin */
[[nodiscard]] const_iterator begin() const noexcept {
return cbegin();
}
/*! @copydoc begin */
[[nodiscard]] iterator begin() noexcept {
const auto pos = static_cast<difference_type>(base_type::size());
return iterator{&payload, pos};
}
/**
* @brief Returns an iterator to the end.
* @return An iterator to the element following the last instance of the
* internal array.
*/
[[nodiscard]] const_iterator cend() const noexcept {
return const_iterator{&payload, {}};
}
/*! @copydoc cend */
[[nodiscard]] const_iterator end() const noexcept {
return cend();
}
/*! @copydoc end */
[[nodiscard]] iterator end() noexcept {
return iterator{&payload, {}};
}
/**
* @brief Returns a reverse iterator to the beginning.
*
* If the storage is empty, the returned iterator will be equal to `rend()`.
*
* @return An iterator to the first instance of the reversed internal array.
*/
[[nodiscard]] const_reverse_iterator crbegin() const noexcept {
return stl::make_reverse_iterator(cend());
}
/*! @copydoc crbegin */
[[nodiscard]] const_reverse_iterator rbegin() const noexcept {
return crbegin();
}
/*! @copydoc rbegin */
[[nodiscard]] reverse_iterator rbegin() noexcept {
return stl::make_reverse_iterator(end());
}
/**
* @brief Returns a reverse iterator to the end.
* @return An iterator to the element following the last instance of the
* reversed internal array.
*/
[[nodiscard]] const_reverse_iterator crend() const noexcept {
return stl::make_reverse_iterator(cbegin());
}
/*! @copydoc crend */
[[nodiscard]] const_reverse_iterator rend() const noexcept {
return crend();
}
/*! @copydoc rend */
[[nodiscard]] reverse_iterator rend() noexcept {
return stl::make_reverse_iterator(begin());
}
/**
* @brief Returns the object assigned to an entity.
*
* @warning
* Attempting to use an entity that doesn't belong to the storage results in
* undefined behavior.
*
* @param entt A valid identifier.
* @return The object assigned to the entity.
*/
[[nodiscard]] const value_type &get(const entity_type entt) const noexcept {
return element_at(base_type::index(entt));
}
/*! @copydoc get */
[[nodiscard]] value_type &get(const entity_type entt) noexcept {
return const_cast<value_type &>(stl::as_const(*this).get(entt));
}
/**
* @brief Returns the object assigned to an entity as a tuple.
* @param entt A valid identifier.
* @return The object assigned to the entity as a tuple.
*/
[[nodiscard]] stl::tuple<const value_type &> get_as_tuple(const entity_type entt) const noexcept {
return stl::forward_as_tuple(get(entt));
}
/*! @copydoc get_as_tuple */
[[nodiscard]] stl::tuple<value_type &> get_as_tuple(const entity_type entt) noexcept {
return stl::forward_as_tuple(get(entt));
}
/**
* @brief Assigns an entity to a storage and constructs its object.
*
* @warning
* Attempting to use an entity that already belongs to the storage results
* in undefined behavior.
*
* @tparam Args Types of arguments to use to construct the object.
* @param entt A valid identifier.
* @param args Parameters to use to construct an object for the entity.
* @return A reference to the newly created object.
*/
template<typename... Args>
value_type &emplace(const entity_type entt, Args &&...args) {
if constexpr(stl::is_aggregate_v<value_type> && (sizeof...(Args) != 0u || !stl::is_default_constructible_v<value_type>)) {
const auto it = emplace_element(entt, false, Type{stl::forward<Args>(args)...});
return element_at(static_cast<size_type>(it.index()));
} else {
const auto it = emplace_element(entt, false, stl::forward<Args>(args)...);
return element_at(static_cast<size_type>(it.index()));
}
}
/**
* @brief Updates the instance assigned to a given entity in-place.
* @tparam Func Types of the function objects to invoke.
* @param entt A valid identifier.
* @param func Valid function objects.
* @return A reference to the updated instance.
*/
template<typename... Func>
value_type &patch(const entity_type entt, Func &&...func) {
const auto idx = base_type::index(entt);
auto &elem = element_at(idx);
(stl::forward<Func>(func)(elem), ...);
return elem;
}
/**
* @brief Assigns one or more entities to a storage and constructs their
* objects from a given instance.
*
* @warning
* Attempting to assign an entity that already belongs to the storage
* results in undefined behavior.
*
* @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 object to construct.
* @return Iterator pointing to the first element inserted, if any.
*/
iterator insert(stl::input_iterator auto first, stl::input_iterator auto last, const value_type &value = {}) {
for(; first != last; ++first) {
emplace_element(*first, true, value);
}
return begin();
}
/**
* @brief Assigns one or more entities to a storage and constructs their
* objects from a given range.
*
* @sa construct
*
* @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 from An iterator to the first element of the range of objects.
* @return Iterator pointing to the first element inserted, if any.
*/
template<stl::input_iterator It>
requires stl::same_as<typename stl::iterator_traits<It>::value_type, value_type>
iterator insert(stl::input_iterator auto first, stl::input_iterator auto last, It from) {
for(; first != last; ++first, ++from) {
emplace_element(*first, true, *from);
}
return begin();
}
/**
* @brief Returns an iterable object to use to _visit_ a storage.
*
* The iterable object returns a tuple that contains the current entity and
* a reference to its element.
*
* @return An iterable object to use to _visit_ the storage.
*/
[[nodiscard]] iterable each() noexcept {
return iterable{{base_type::begin(), begin()}, {base_type::end(), end()}};
}
/*! @copydoc each */
[[nodiscard]] const_iterable each() const noexcept {
return const_iterable{{base_type::cbegin(), cbegin()}, {base_type::cend(), cend()}};
}
/**
* @brief Returns a reverse iterable object to use to _visit_ a storage.
*
* @sa each
*
* @return A reverse iterable object to use to _visit_ the storage.
*/
[[nodiscard]] reverse_iterable reach() noexcept {
return reverse_iterable{{base_type::rbegin(), rbegin()}, {base_type::rend(), rend()}};
}
/*! @copydoc reach */
[[nodiscard]] const_reverse_iterable reach() const noexcept {
return const_reverse_iterable{{base_type::crbegin(), crbegin()}, {base_type::crend(), crend()}};
}
private:
container_type payload;
};
/*! @copydoc basic_storage */
template<typename Type, typename Entity, typename Allocator>
requires (component_traits<Type, Entity>::page_size == 0u)
class basic_storage<Type, Entity, Allocator>
: public basic_sparse_set<Entity, typename stl::allocator_traits<Allocator>::template rebind_alloc<Entity>> {
using alloc_traits = stl::allocator_traits<Allocator>;
static_assert(stl::is_same_v<typename alloc_traits::value_type, Type>, "Invalid value type");
using traits_type = component_traits<Type, Entity>;
public:
/*! @brief Allocator type. */
using allocator_type = Allocator;
/*! @brief Base type. */
using base_type = basic_sparse_set<Entity, typename alloc_traits::template rebind_alloc<Entity>>;
/*! @brief Element type. */
using element_type = Type;
/*! @brief Type of the objects assigned to entities. */
using value_type = void;
/*! @brief Underlying entity identifier. */
using entity_type = Entity;
/*! @brief Unsigned integer type. */
using size_type = stl::size_t;
/*! @brief Signed integer type. */
using difference_type = stl::ptrdiff_t;
/*! @brief Extended iterable storage proxy. */
using iterable = iterable_adaptor<internal::extended_storage_iterator<typename base_type::iterator>>;
/*! @brief Constant extended iterable storage proxy. */
using const_iterable = iterable_adaptor<internal::extended_storage_iterator<typename base_type::const_iterator>>;
/*! @brief Extended reverse iterable storage proxy. */
using reverse_iterable = iterable_adaptor<internal::extended_storage_iterator<typename base_type::reverse_iterator>>;
/*! @brief Constant extended reverse iterable storage proxy. */
using const_reverse_iterable = iterable_adaptor<internal::extended_storage_iterator<typename base_type::const_reverse_iterator>>;
/*! @brief Storage deletion policy. */
static constexpr deletion_policy storage_policy{traits_type::in_place_delete};
/*! @brief Default constructor. */
basic_storage()
: basic_storage{allocator_type{}} {}
/**
* @brief Constructs an empty container with a given allocator.
* @param allocator The allocator to use.
*/
explicit basic_storage(const allocator_type &allocator)
: base_type{type_id<element_type>(), storage_policy, allocator} {}
/*! @brief Default copy constructor, deleted on purpose. */
basic_storage(const basic_storage &) = delete;
/**
* @brief Move constructor.
* @param other The instance to move from.
*/
basic_storage(basic_storage &&other) noexcept = default;
/**
* @brief Allocator-extended move constructor.
* @param other The instance to move from.
* @param allocator The allocator to use.
*/
basic_storage(basic_storage &&other, const allocator_type &allocator)
: base_type{stl::move(other), allocator} {}
/*! @brief Default destructor. */
~basic_storage() override = default;
/**
* @brief Default copy assignment operator, deleted on purpose.
* @return This storage.
*/
basic_storage &operator=(const basic_storage &) = delete;
/**
* @brief Move assignment operator.
* @param other The instance to move from.
* @return This storage.
*/
basic_storage &operator=(basic_storage &&other) noexcept = default;
/**
* @brief Returns the associated allocator.
* @return The associated allocator.
*/
[[nodiscard]] constexpr allocator_type get_allocator() const noexcept {
return allocator_type{base_type::get_allocator()};
}
/**
* @brief Returns the object assigned to an entity, that is `void`.
*
* @warning
* Attempting to use an entity that doesn't belong to the storage results in
* undefined behavior.
*
* @param entt A valid identifier.
*/
void get([[maybe_unused]] const entity_type entt) const noexcept {
ENTT_ASSERT(base_type::contains(entt), "Invalid entity");
}
/**
* @brief Returns an empty tuple.
* @param entt A valid identifier.
* @return Returns an empty tuple.
*/
[[nodiscard]] stl::tuple<> get_as_tuple([[maybe_unused]] const entity_type entt) const noexcept {
ENTT_ASSERT(base_type::contains(entt), "Invalid entity");
return stl::tuple{};
}
/**
* @brief Assigns an entity to a storage and constructs its object.
*
* @warning
* Attempting to use an entity that already belongs to the storage results
* in undefined behavior.
*
* @param entt A valid identifier.
*/
void emplace(const entity_type entt, const auto &...) {
base_type::try_emplace(entt, false);
}
/**
* @brief Updates the instance assigned to a given entity in-place.
* @tparam Func Types of the function objects to invoke.
* @param entt A valid identifier.
* @param func Valid function objects.
*/
template<typename... Func>
void patch([[maybe_unused]] const entity_type entt, Func &&...func) {
ENTT_ASSERT(base_type::contains(entt), "Invalid entity");
(stl::forward<Func>(func)(), ...);
}
/**
* @brief Assigns entities to a storage.
* @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.
*/
void insert(stl::input_iterator auto first, stl::input_iterator auto last, const auto &...) {
for(; first != last; ++first) {
base_type::try_emplace(*first, true);
}
}
/**
* @brief Returns an iterable object to use to _visit_ a storage.
*
* The iterable object returns a tuple that contains the current entity.
*
* @return An iterable object to use to _visit_ the storage.
*/
[[nodiscard]] iterable each() noexcept {
return iterable{base_type::begin(), base_type::end()};
}
/*! @copydoc each */
[[nodiscard]] const_iterable each() const noexcept {
return const_iterable{base_type::cbegin(), base_type::cend()};
}
/**
* @brief Returns a reverse iterable object to use to _visit_ a storage.
*
* @sa each
*
* @return A reverse iterable object to use to _visit_ the storage.
*/
[[nodiscard]] reverse_iterable reach() noexcept {
return reverse_iterable{{base_type::rbegin()}, {base_type::rend()}};
}
/*! @copydoc reach */
[[nodiscard]] const_reverse_iterable reach() const noexcept {
return const_reverse_iterable{{base_type::crbegin()}, {base_type::crend()}};
}
};
/**
* @brief Swap-only entity storage specialization.
* @tparam Entity A valid entity type.
* @tparam Allocator Type of allocator used to manage memory and elements.
*/
template<typename Entity, typename Allocator>
class basic_storage<Entity, Entity, Allocator>
: public basic_sparse_set<Entity, Allocator> {
using alloc_traits = stl::allocator_traits<Allocator>;
static_assert(stl::is_same_v<typename alloc_traits::value_type, Entity>, "Invalid value type");
using underlying_iterator = basic_sparse_set<Entity, Allocator>::basic_iterator;
using traits_type = entt_traits<Entity>;
auto from_placeholder() noexcept {
const auto entt = traits_type::combine(static_cast<traits_type::entity_type>(placeholder), {});
ENTT_ASSERT(entt != null, "No more entities available");
placeholder += static_cast<size_type>(entt != null);
return entt;
}
auto next() noexcept {
entity_type entt = from_placeholder();
while(base_type::current(entt) != traits_type::to_version(tombstone) && entt != null) {
entt = from_placeholder();
}
return entt;
}
protected:
/*! @brief Erases all entities of a storage. */
void pop_all() override {
base_type::pop_all();
placeholder = {};
}
/**
* @brief Assigns an entity to a storage.
* @param hint A valid identifier.
* @return Iterator pointing to the emplaced element.
*/
underlying_iterator try_emplace(const Entity hint, const bool, const void *) override {
return base_type::find(generate(hint));
}
public:
/*! @brief Allocator type. */
using allocator_type = Allocator;
/*! @brief Base type. */
using base_type = basic_sparse_set<Entity, Allocator>;
/*! @brief Element type. */
using element_type = Entity;
/*! @brief Type of the objects assigned to entities. */
using value_type = void;
/*! @brief Underlying entity identifier. */
using entity_type = Entity;
/*! @brief Unsigned integer type. */
using size_type = stl::size_t;
/*! @brief Signed integer type. */
using difference_type = stl::ptrdiff_t;
/*! @brief Extended iterable storage proxy. */
using iterable = iterable_adaptor<internal::extended_storage_iterator<typename base_type::iterator>>;
/*! @brief Constant extended iterable storage proxy. */
using const_iterable = iterable_adaptor<internal::extended_storage_iterator<typename base_type::const_iterator>>;
/*! @brief Extended reverse iterable storage proxy. */
using reverse_iterable = iterable_adaptor<internal::extended_storage_iterator<typename base_type::reverse_iterator>>;
/*! @brief Constant extended reverse iterable storage proxy. */
using const_reverse_iterable = iterable_adaptor<internal::extended_storage_iterator<typename base_type::const_reverse_iterator>>;
/*! @brief Storage deletion policy. */
static constexpr deletion_policy storage_policy = deletion_policy::swap_only;
/*! @brief Default constructor. */
basic_storage()
: basic_storage{allocator_type{}} {
}
/**
* @brief Constructs an empty container with a given allocator.
* @param allocator The allocator to use.
*/
explicit basic_storage(const allocator_type &allocator)
: base_type{type_id<void>(), storage_policy, allocator} {}
/*! @brief Default copy constructor, deleted on purpose. */
basic_storage(const basic_storage &) = delete;
/**
* @brief Move constructor.
* @param other The instance to move from.
*/
// NOLINTNEXTLINE(cppcoreguidelines-rvalue-reference-param-not-moved)
basic_storage(basic_storage &&other) noexcept
: base_type{static_cast<base_type &&>(other)},
placeholder{other.placeholder} {}
/**
* @brief Allocator-extended move constructor.
* @param other The instance to move from.
* @param allocator The allocator to use.
*/
// NOLINTNEXTLINE(cppcoreguidelines-rvalue-reference-param-not-moved)
basic_storage(basic_storage &&other, const allocator_type &allocator)
: base_type{static_cast<base_type &&>(other), allocator},
placeholder{other.placeholder} {}
/*! @brief Default destructor. */
~basic_storage() override = default;
/**
* @brief Default copy assignment operator, deleted on purpose.
* @return This storage.
*/
basic_storage &operator=(const basic_storage &) = delete;
/**
* @brief Move assignment operator.
* @param other The instance to move from.
* @return This storage.
*/
basic_storage &operator=(basic_storage &&other) noexcept {
placeholder = other.placeholder;
base_type::operator=(stl::move(other));
return *this;
}
/**
* @brief Exchanges the contents with those of a given storage.
* @param other Storage to exchange the content with.
*/
void swap(basic_storage &other) noexcept {
using stl::swap;
swap(placeholder, other.placeholder);
base_type::swap(other);
}
/**
* @brief Returns the object assigned to an entity, that is `void`.
*
* @warning
* Attempting to use an entity that doesn't belong to the storage results in
* undefined behavior.
*
* @param entt A valid identifier.
*/
void get([[maybe_unused]] const entity_type entt) const noexcept {
ENTT_ASSERT(base_type::index(entt) < base_type::free_list(), "The requested entity is not a live one");
}
/**
* @brief Returns an empty tuple.
* @param entt A valid identifier.
* @return Returns an empty tuple.
*/
[[nodiscard]] stl::tuple<> get_as_tuple([[maybe_unused]] const entity_type entt) const noexcept {
ENTT_ASSERT(base_type::index(entt) < base_type::free_list(), "The requested entity is not a live one");
return stl::tuple{};
}
/**
* @brief Creates a new identifier or recycles a destroyed one.
* @return A valid identifier.
*/
entity_type generate() {
const auto len = base_type::free_list();
const auto entt = (len == base_type::size()) ? next() : base_type::data()[len];
return *base_type::try_emplace(entt, true);
}
/**
* @brief Creates a new identifier or recycles a destroyed one.
*
* If the requested identifier isn't in use, the suggested one is used.
* Otherwise, a new identifier is returned.
*
* @param hint Required identifier.
* @return A valid identifier.
*/
entity_type generate(const entity_type hint) {
if(hint != null && hint != tombstone) {
if(const auto curr = traits_type::construct(traits_type::to_entity(hint), base_type::current(hint)); curr == tombstone || !(base_type::index(curr) < base_type::free_list())) {
return *base_type::try_emplace(hint, true);
}
}
return generate();
}
/**
* @brief Assigns each element in a range an identifier.
* @tparam It Type of output 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<stl::output_iterator<entity_type> It>
void generate(It first, It last) {
for(const auto sz = base_type::size(); first != last && base_type::free_list() != sz; ++first) {
*first = *base_type::try_emplace(base_type::data()[base_type::free_list()], true);
}
for(; first != last; ++first) {
*first = *base_type::try_emplace(next(), true);
}
}
/**
* @brief Updates a given identifier.
* @tparam Func Types of the function objects to invoke.
* @param entt A valid identifier.
* @param func Valid function objects.
*/
template<typename... Func>
void patch([[maybe_unused]] const entity_type entt, Func &&...func) {
ENTT_ASSERT(base_type::index(entt) < base_type::free_list(), "The requested entity is not a live one");
(stl::forward<Func>(func)(), ...);
}
/**
* @brief Returns an iterable object to use to _visit_ a storage.
*
* The iterable object returns a tuple that contains the current entity.
*
* @return An iterable object to use to _visit_ the storage.
*/
[[nodiscard]] iterable each() noexcept {
return stl::as_const(*this).each();
}
/*! @copydoc each */
[[nodiscard]] const_iterable each() const noexcept {
const auto it = base_type::cend();
const auto offset = static_cast<difference_type>(base_type::free_list());
return const_iterable{it - offset, it};
}
/**
* @brief Returns a reverse iterable object to use to _visit_ a storage.
*
* @sa each
*
* @return A reverse iterable object to use to _visit_ the storage.
*/
[[nodiscard]] reverse_iterable reach() noexcept {
return stl::as_const(*this).reach();
}
/*! @copydoc reach */
[[nodiscard]] const_reverse_iterable reach() const noexcept {
const auto it = base_type::crbegin();
const auto offset = static_cast<difference_type>(base_type::free_list());
return const_reverse_iterable{it, it + offset};
}
/**
* @brief Sets the starting identifier for generation.
*
* The version is ignored, regardless of the value.
*
* @param hint A valid identifier.
*/
void start_from(const entity_type hint) {
placeholder = static_cast<size_type>(traits_type::to_entity(hint));
}
private:
size_type placeholder{};
};
} // namespace entt
#endif