mirror of
https://github.com/zhenyan121/Cubed.git
synced 2026-08-09 02:07:04 +08:00
* 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.
1077 lines
37 KiB
C++
1077 lines
37 KiB
C++
#ifndef ENTT_ENTITY_SPARSE_SET_HPP
|
|
#define ENTT_ENTITY_SPARSE_SET_HPP
|
|
|
|
#include <compare>
|
|
#include "../config/config.h"
|
|
#include "../core/algorithm.hpp"
|
|
#include "../core/any.hpp"
|
|
#include "../core/bit.hpp"
|
|
#include "../core/type_info.hpp"
|
|
#include "../stl/concepts.hpp"
|
|
#include "../stl/cstddef.hpp"
|
|
#include "../stl/iterator.hpp"
|
|
#include "../stl/memory.hpp"
|
|
#include "../stl/type_traits.hpp"
|
|
#include "../stl/utility.hpp"
|
|
#include "../stl/vector.hpp"
|
|
#include "entity.hpp"
|
|
#include "fwd.hpp"
|
|
|
|
namespace entt {
|
|
|
|
/*! @cond ENTT_INTERNAL */
|
|
namespace internal {
|
|
|
|
template<typename Container>
|
|
struct sparse_set_iterator final {
|
|
using value_type = Container::value_type;
|
|
using pointer = Container::const_pointer;
|
|
using reference = Container::const_reference;
|
|
using difference_type = Container::difference_type;
|
|
using iterator_category = stl::random_access_iterator_tag;
|
|
|
|
constexpr sparse_set_iterator() noexcept
|
|
: packed{},
|
|
offset{} {}
|
|
|
|
constexpr sparse_set_iterator(const Container &ref, const difference_type idx) noexcept
|
|
: packed{&ref},
|
|
offset{idx} {}
|
|
|
|
constexpr sparse_set_iterator &operator++() noexcept {
|
|
return --offset, *this;
|
|
}
|
|
|
|
constexpr sparse_set_iterator operator++(int) noexcept {
|
|
const sparse_set_iterator orig = *this;
|
|
return ++(*this), orig;
|
|
}
|
|
|
|
constexpr sparse_set_iterator &operator--() noexcept {
|
|
return ++offset, *this;
|
|
}
|
|
|
|
constexpr sparse_set_iterator operator--(int) noexcept {
|
|
const sparse_set_iterator orig = *this;
|
|
return operator--(), orig;
|
|
}
|
|
|
|
constexpr sparse_set_iterator &operator+=(const difference_type value) noexcept {
|
|
offset -= value;
|
|
return *this;
|
|
}
|
|
|
|
constexpr sparse_set_iterator operator+(const difference_type value) const noexcept {
|
|
sparse_set_iterator copy = *this;
|
|
return (copy += value);
|
|
}
|
|
|
|
constexpr sparse_set_iterator &operator-=(const difference_type value) noexcept {
|
|
return (*this += -value);
|
|
}
|
|
|
|
constexpr sparse_set_iterator operator-(const difference_type value) const noexcept {
|
|
return (*this + -value);
|
|
}
|
|
|
|
[[nodiscard]] constexpr reference operator[](const difference_type value) const noexcept {
|
|
return (*packed)[static_cast<Container::size_type>(index() - value)];
|
|
}
|
|
|
|
[[nodiscard]] constexpr pointer operator->() const noexcept {
|
|
return stl::addressof(operator[](0));
|
|
}
|
|
|
|
[[nodiscard]] constexpr reference operator*() const noexcept {
|
|
return operator[](0);
|
|
}
|
|
|
|
[[nodiscard]] constexpr stl::ptrdiff_t operator-(const sparse_set_iterator &other) const noexcept {
|
|
// intentionally reversed due to backward iteration
|
|
return other.offset - offset;
|
|
}
|
|
|
|
[[nodiscard]] constexpr bool operator==(const sparse_set_iterator &other) const noexcept {
|
|
return offset == other.offset;
|
|
}
|
|
|
|
[[nodiscard]] constexpr auto operator<=>(const sparse_set_iterator &other) const noexcept {
|
|
// intentionally reversed due to backward iteration
|
|
return other.offset <=> offset;
|
|
}
|
|
|
|
[[nodiscard]] constexpr pointer data() const noexcept {
|
|
return packed ? packed->data() : nullptr;
|
|
}
|
|
|
|
[[nodiscard]] constexpr difference_type index() const noexcept {
|
|
return offset - 1;
|
|
}
|
|
|
|
private:
|
|
const Container *packed;
|
|
difference_type offset;
|
|
};
|
|
|
|
} // namespace internal
|
|
/*! @endcond */
|
|
|
|
/**
|
|
* @brief Sparse set implementation.
|
|
*
|
|
* Sparse set or packed array or whatever is the name users give it.<br/>
|
|
* Two arrays: an _external_ one and an _internal_ one; a _sparse_ one and a
|
|
* _packed_ one; one used for direct access through contiguous memory, the other
|
|
* one used to get the data through an extra level of indirection.<br/>
|
|
* This type of data structure is widely documented in the literature and on the
|
|
* web. This is nothing more than a customized implementation suitable for the
|
|
* purpose of the framework.
|
|
*
|
|
* @note
|
|
* Internal data structures arrange elements to maximize performance. There are
|
|
* no guarantees that entities are returned in the insertion order when iterate
|
|
* a sparse set. Do not make assumption on the order in any case.
|
|
*
|
|
* @tparam Entity A valid entity type.
|
|
* @tparam Allocator Type of allocator used to manage memory and elements.
|
|
*/
|
|
template<typename Entity, typename Allocator>
|
|
class basic_sparse_set {
|
|
using alloc_traits = stl::allocator_traits<Allocator>;
|
|
static_assert(stl::is_same_v<typename alloc_traits::value_type, Entity>, "Invalid value type");
|
|
using sparse_container_type = stl::vector<typename alloc_traits::pointer, typename alloc_traits::template rebind_alloc<typename alloc_traits::pointer>>;
|
|
using packed_container_type = stl::vector<Entity, Allocator>;
|
|
using traits_type = entt_traits<Entity>;
|
|
|
|
static constexpr auto max_size = static_cast<stl::size_t>(traits_type::to_entity(null));
|
|
|
|
// it could be auto but gcc complains and emits a warning due to a false positive
|
|
[[nodiscard]] stl::size_t policy_to_head() const noexcept {
|
|
return static_cast<size_type>(max_size * static_cast<stl::remove_const_t<decltype(max_size)>>(mode != deletion_policy::swap_only));
|
|
}
|
|
|
|
[[nodiscard]] auto entity_to_pos(const Entity entt) const noexcept {
|
|
return static_cast<size_type>(traits_type::to_entity(entt));
|
|
}
|
|
|
|
[[nodiscard]] auto pos_to_page(const stl::size_t pos) const noexcept {
|
|
return static_cast<size_type>(pos / traits_type::page_size);
|
|
}
|
|
|
|
[[nodiscard]] auto sparse_ptr(const Entity entt) const {
|
|
const auto pos = entity_to_pos(entt);
|
|
const auto page = pos_to_page(pos);
|
|
return (page < sparse.size() && sparse[page]) ? (sparse[page] + fast_mod(pos, traits_type::page_size)) : nullptr;
|
|
}
|
|
|
|
[[nodiscard]] auto &sparse_ref(const Entity entt) const {
|
|
ENTT_ASSERT(sparse_ptr(entt), "Invalid element");
|
|
const auto pos = entity_to_pos(entt);
|
|
return sparse[pos_to_page(pos)][fast_mod(pos, traits_type::page_size)];
|
|
}
|
|
|
|
[[nodiscard]] auto to_iterator(const Entity entt) const {
|
|
return --(end() - static_cast<difference_type>(index(entt)));
|
|
}
|
|
|
|
[[nodiscard]] auto &assure_at_least(const Entity entt) {
|
|
const auto pos = entity_to_pos(entt);
|
|
const auto page = pos_to_page(pos);
|
|
|
|
if(!(page < sparse.size())) {
|
|
sparse.resize(page + 1u, nullptr);
|
|
}
|
|
|
|
if(!sparse[page]) {
|
|
constexpr entity_type init = null;
|
|
auto page_allocator{packed.get_allocator()};
|
|
sparse[page] = alloc_traits::allocate(page_allocator, traits_type::page_size);
|
|
stl::uninitialized_fill(sparse[page], sparse[page] + traits_type::page_size, init);
|
|
}
|
|
|
|
return sparse[page][fast_mod(pos, traits_type::page_size)];
|
|
}
|
|
|
|
void release_sparse_pages() {
|
|
for(auto page_allocator{packed.get_allocator()}; auto &&page: sparse) {
|
|
if(page != nullptr) {
|
|
stl::destroy(page, page + traits_type::page_size);
|
|
alloc_traits::deallocate(page_allocator, page, traits_type::page_size);
|
|
page = nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
void swap_at(const stl::size_t lhs, const stl::size_t rhs) {
|
|
auto &from = packed[lhs];
|
|
auto &to = packed[rhs];
|
|
|
|
sparse_ref(from) = traits_type::combine(static_cast<traits_type::entity_type>(rhs), traits_type::to_integral(from));
|
|
sparse_ref(to) = traits_type::combine(static_cast<traits_type::entity_type>(lhs), traits_type::to_integral(to));
|
|
|
|
stl::swap(from, to);
|
|
}
|
|
|
|
private:
|
|
[[nodiscard]] virtual const void *get_at(const stl::size_t) const {
|
|
return nullptr;
|
|
}
|
|
|
|
virtual void swap_or_move([[maybe_unused]] const stl::size_t lhs, [[maybe_unused]] const stl::size_t rhs) {
|
|
ENTT_ASSERT((mode != deletion_policy::swap_only) || ((lhs < head) == (rhs < head)), "Cross swapping is not supported");
|
|
}
|
|
|
|
protected:
|
|
/*! @brief Random access iterator type. */
|
|
using basic_iterator = internal::sparse_set_iterator<packed_container_type>;
|
|
|
|
/**
|
|
* @brief Erases an entity from a sparse set.
|
|
* @param entt A valid identifier for the element to pop.
|
|
*/
|
|
void swap_only(const Entity entt) {
|
|
ENTT_ASSERT(mode == deletion_policy::swap_only, "Deletion policy mismatch");
|
|
const auto pos = index(entt);
|
|
bump(traits_type::next(entt));
|
|
swap_at(pos, head -= (pos < head));
|
|
}
|
|
|
|
/**
|
|
* @brief Erases an entity from a sparse set.
|
|
* @param entt A valid identifier for the element to pop.
|
|
*/
|
|
void swap_and_pop(const Entity entt) {
|
|
ENTT_ASSERT(mode == deletion_policy::swap_and_pop, "Deletion policy mismatch");
|
|
auto &self = sparse_ref(entt);
|
|
const auto pos = traits_type::to_entity(self);
|
|
sparse_ref(packed.back()) = traits_type::combine(pos, traits_type::to_integral(packed.back()));
|
|
packed[static_cast<size_type>(pos)] = packed.back();
|
|
// unnecessary but it helps to detect nasty bugs
|
|
// NOLINTNEXTLINE(bugprone-assert-side-effect)
|
|
ENTT_ASSERT((packed.back() = null, true), "");
|
|
// lazy self-assignment guard
|
|
self = null;
|
|
packed.pop_back();
|
|
}
|
|
|
|
/**
|
|
* @brief Erases an entity from a sparse set.
|
|
* @param entt A valid identifier for the element to pop.
|
|
*/
|
|
void in_place_pop(const Entity entt) {
|
|
ENTT_ASSERT(mode == deletion_policy::in_place, "Deletion policy mismatch");
|
|
const auto pos = entity_to_pos(stl::exchange(sparse_ref(entt), null));
|
|
packed[pos] = traits_type::combine(static_cast<traits_type::entity_type>(stl::exchange(head, pos)), tombstone);
|
|
}
|
|
|
|
/**
|
|
* @brief Erases entities from a sparse set.
|
|
* @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.
|
|
*/
|
|
virtual void pop(basic_iterator first, basic_iterator last) {
|
|
switch(mode) {
|
|
case deletion_policy::swap_and_pop:
|
|
for(; first != last; ++first) {
|
|
swap_and_pop(*first);
|
|
}
|
|
break;
|
|
case deletion_policy::in_place:
|
|
for(; first != last; ++first) {
|
|
in_place_pop(*first);
|
|
}
|
|
break;
|
|
case deletion_policy::swap_only:
|
|
for(; first != last; ++first) {
|
|
swap_only(*first);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*! @brief Erases all entities of a sparse set. */
|
|
virtual void pop_all() {
|
|
if(!packed.empty()) {
|
|
// suboptimal with few entities, but exploits cache way more with many
|
|
for(auto &&elem: sparse) {
|
|
if(elem) {
|
|
for(size_type pos{}; pos < traits_type::page_size; ++pos) {
|
|
elem[pos] = null;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
head = policy_to_head();
|
|
packed.clear();
|
|
}
|
|
|
|
/**
|
|
* @brief Assigns an entity to a sparse set.
|
|
* @param entt A valid identifier.
|
|
* @param force_back Force back insertion.
|
|
* @return Iterator pointing to the emplaced element.
|
|
*/
|
|
virtual basic_iterator try_emplace(const Entity entt, const bool force_back, const void * = nullptr) {
|
|
ENTT_ASSERT(entt != null && entt != tombstone, "Invalid element");
|
|
auto &elem = assure_at_least(entt);
|
|
auto pos = size();
|
|
|
|
switch(mode) {
|
|
case deletion_policy::in_place:
|
|
if(head != max_size && !force_back) {
|
|
pos = head;
|
|
ENTT_ASSERT(elem == null, "Slot not available");
|
|
elem = traits_type::combine(static_cast<traits_type::entity_type>(head), traits_type::to_integral(entt));
|
|
head = entity_to_pos(stl::exchange(packed[pos], entt));
|
|
break;
|
|
}
|
|
[[fallthrough]];
|
|
case deletion_policy::swap_and_pop:
|
|
packed.push_back(entt);
|
|
ENTT_ASSERT(elem == null, "Slot not available");
|
|
elem = traits_type::combine(static_cast<traits_type::entity_type>(packed.size() - 1u), traits_type::to_integral(entt));
|
|
break;
|
|
case deletion_policy::swap_only:
|
|
if(elem == null) {
|
|
packed.push_back(entt);
|
|
elem = traits_type::combine(static_cast<traits_type::entity_type>(packed.size() - 1u), traits_type::to_integral(entt));
|
|
} else {
|
|
ENTT_ASSERT(!(entity_to_pos(elem) < head), "Slot not available");
|
|
bump(entt);
|
|
}
|
|
|
|
pos = head++;
|
|
swap_at(entity_to_pos(elem), pos);
|
|
break;
|
|
}
|
|
|
|
return iterator{packed, static_cast<difference_type>(++pos)};
|
|
}
|
|
|
|
/*! @brief Forwards variables to derived classes, if any. */
|
|
// NOLINTNEXTLINE(performance-unnecessary-value-param)
|
|
virtual void bind_any(any) noexcept {}
|
|
|
|
public:
|
|
/*! @brief Allocator type. */
|
|
using allocator_type = Allocator;
|
|
/*! @brief Underlying entity identifier. */
|
|
using entity_type = traits_type::value_type;
|
|
/*! @brief Underlying version type. */
|
|
using version_type = traits_type::version_type;
|
|
/*! @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 entities. */
|
|
using pointer = packed_container_type::const_pointer;
|
|
/*! @brief Random access iterator type. */
|
|
using iterator = basic_iterator;
|
|
/*! @brief Constant random access iterator type. */
|
|
using const_iterator = iterator;
|
|
/*! @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 Default constructor. */
|
|
basic_sparse_set()
|
|
: basic_sparse_set{type_id<void>()} {}
|
|
|
|
/**
|
|
* @brief Constructs an empty container with a given allocator.
|
|
* @param allocator The allocator to use.
|
|
*/
|
|
explicit basic_sparse_set(const allocator_type &allocator)
|
|
: basic_sparse_set{deletion_policy::swap_and_pop, allocator} {}
|
|
|
|
/**
|
|
* @brief Constructs an empty container with the given policy and allocator.
|
|
* @param pol Type of deletion policy.
|
|
* @param allocator The allocator to use (possibly default-constructed).
|
|
*/
|
|
explicit basic_sparse_set(deletion_policy pol, const allocator_type &allocator = {})
|
|
: basic_sparse_set{type_id<void>(), pol, allocator} {}
|
|
|
|
/**
|
|
* @brief Constructs an empty container with the given value type, policy
|
|
* and allocator.
|
|
* @param elem Returned value type, if any.
|
|
* @param pol Type of deletion policy.
|
|
* @param allocator The allocator to use (possibly default-constructed).
|
|
*/
|
|
explicit basic_sparse_set(const type_info &elem, deletion_policy pol = deletion_policy::swap_and_pop, const allocator_type &allocator = {})
|
|
: sparse{allocator},
|
|
packed{allocator},
|
|
descriptor{&elem},
|
|
mode{pol},
|
|
head{policy_to_head()} {
|
|
ENTT_ASSERT(traits_type::version_mask || mode != deletion_policy::in_place, "Policy does not support zero-sized versions");
|
|
}
|
|
|
|
/*! @brief Default copy constructor, deleted on purpose. */
|
|
basic_sparse_set(const basic_sparse_set &) = delete;
|
|
|
|
/**
|
|
* @brief Move constructor.
|
|
* @param other The instance to move from.
|
|
*/
|
|
basic_sparse_set(basic_sparse_set &&other) noexcept
|
|
: sparse{stl::move(other.sparse)},
|
|
packed{stl::move(other.packed)},
|
|
descriptor{other.descriptor},
|
|
mode{other.mode},
|
|
head{stl::exchange(other.head, policy_to_head())} {}
|
|
|
|
/**
|
|
* @brief Allocator-extended move constructor.
|
|
* @param other The instance to move from.
|
|
* @param allocator The allocator to use.
|
|
*/
|
|
basic_sparse_set(basic_sparse_set &&other, const allocator_type &allocator)
|
|
: sparse{stl::move(other.sparse), allocator},
|
|
packed{stl::move(other.packed), allocator},
|
|
descriptor{other.descriptor},
|
|
mode{other.mode},
|
|
head{stl::exchange(other.head, policy_to_head())} {
|
|
ENTT_ASSERT(alloc_traits::is_always_equal::value || get_allocator() == other.get_allocator(), "Copying a sparse set is not allowed");
|
|
}
|
|
|
|
/*! @brief Default destructor. */
|
|
virtual ~basic_sparse_set() {
|
|
release_sparse_pages();
|
|
}
|
|
|
|
/**
|
|
* @brief Default copy assignment operator, deleted on purpose.
|
|
* @return This sparse set.
|
|
*/
|
|
basic_sparse_set &operator=(const basic_sparse_set &) = delete;
|
|
|
|
/**
|
|
* @brief Move assignment operator.
|
|
* @param other The instance to move from.
|
|
* @return This sparse set.
|
|
*/
|
|
basic_sparse_set &operator=(basic_sparse_set &&other) noexcept {
|
|
ENTT_ASSERT(alloc_traits::is_always_equal::value || get_allocator() == other.get_allocator(), "Copying a sparse set is not allowed");
|
|
swap(other);
|
|
return *this;
|
|
}
|
|
|
|
/**
|
|
* @brief Exchanges the contents with those of a given sparse set.
|
|
* @param other Sparse set to exchange the content with.
|
|
*/
|
|
void swap(basic_sparse_set &other) noexcept {
|
|
using stl::swap;
|
|
swap(sparse, other.sparse);
|
|
swap(packed, other.packed);
|
|
swap(descriptor, other.descriptor);
|
|
swap(mode, other.mode);
|
|
swap(head, other.head);
|
|
}
|
|
|
|
/**
|
|
* @brief Returns the associated allocator.
|
|
* @return The associated allocator.
|
|
*/
|
|
[[nodiscard]] constexpr allocator_type get_allocator() const noexcept {
|
|
return packed.get_allocator();
|
|
}
|
|
|
|
/**
|
|
* @brief Returns the deletion policy of a sparse set.
|
|
* @return The deletion policy of the sparse set.
|
|
*/
|
|
[[nodiscard]] deletion_policy policy() const noexcept {
|
|
return mode;
|
|
}
|
|
|
|
/**
|
|
* @brief Returns data on the free list whose meaning depends on the mode.
|
|
* @return Free list information that is mode dependent.
|
|
*/
|
|
[[nodiscard]] size_type free_list() const noexcept {
|
|
return head;
|
|
}
|
|
|
|
/**
|
|
* @brief Sets data on the free list whose meaning depends on the mode.
|
|
* @param value Free list information that is mode dependent.
|
|
*/
|
|
void free_list(const size_type value) noexcept {
|
|
ENTT_ASSERT((mode == deletion_policy::swap_only) && !(value > packed.size()), "Invalid value");
|
|
head = value;
|
|
}
|
|
|
|
/**
|
|
* @brief Increases the capacity of a sparse set.
|
|
*
|
|
* If the new capacity is greater than the current capacity, new storage is
|
|
* allocated, otherwise the method does nothing.
|
|
*
|
|
* @param cap Desired capacity.
|
|
*/
|
|
virtual void reserve(const size_type cap) {
|
|
packed.reserve(cap);
|
|
}
|
|
|
|
/**
|
|
* @brief Returns the number of elements that a sparse set has currently
|
|
* allocated space for.
|
|
* @return Capacity of the sparse set.
|
|
*/
|
|
[[nodiscard]] virtual size_type capacity() const noexcept {
|
|
return packed.capacity();
|
|
}
|
|
|
|
/*! @brief Requests the removal of unused capacity. */
|
|
virtual void shrink_to_fit() {
|
|
sparse_container_type other{sparse.get_allocator()};
|
|
const auto len = sparse.size();
|
|
other.reserve(len);
|
|
|
|
for(size_type cnt{}; auto &&elem: stl::as_const(packed)) {
|
|
if(elem != tombstone) {
|
|
if(const auto page = pos_to_page(entity_to_pos(elem)); sparse[page] != nullptr) {
|
|
if(const auto sz = page + 1u; sz > other.size()) {
|
|
other.resize(sz, nullptr);
|
|
}
|
|
|
|
other[page] = stl::exchange(sparse[page], nullptr);
|
|
|
|
if(++cnt == len) {
|
|
// early exit due to lack of pages
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
release_sparse_pages();
|
|
sparse.swap(other);
|
|
|
|
sparse.shrink_to_fit();
|
|
packed.shrink_to_fit();
|
|
}
|
|
|
|
/**
|
|
* @brief Returns the extent of a sparse set.
|
|
*
|
|
* The extent of a sparse set is also the size of the internal sparse array.
|
|
* There is no guarantee that all pages have been allocated, nor that the
|
|
* internal packed array is be the same size.
|
|
*
|
|
* @return Extent of the sparse set.
|
|
*/
|
|
[[nodiscard]] size_type extent() const noexcept {
|
|
return sparse.size() * traits_type::page_size;
|
|
}
|
|
|
|
/**
|
|
* @brief Returns the number of elements in a sparse set.
|
|
*
|
|
* The number of elements is also the size of the internal packed array.
|
|
* There is no guarantee that the internal sparse array has the same size.
|
|
* Usually the size of the internal sparse array is equal or greater than
|
|
* the one of the internal packed array.
|
|
*
|
|
* @return Number of elements.
|
|
*/
|
|
[[nodiscard]] size_type size() const noexcept {
|
|
return packed.size();
|
|
}
|
|
|
|
/**
|
|
* @brief Checks whether a sparse set is empty.
|
|
* @return True if the sparse set is empty, false otherwise.
|
|
*/
|
|
[[nodiscard]] bool empty() const noexcept {
|
|
return packed.empty();
|
|
}
|
|
|
|
/**
|
|
* @brief Checks whether a sparse set is fully packed.
|
|
* @return True if the sparse set is fully packed, false otherwise.
|
|
*/
|
|
[[nodiscard]] bool contiguous() const noexcept {
|
|
return (mode != deletion_policy::in_place) || (head == max_size);
|
|
}
|
|
|
|
/**
|
|
* @brief Direct access to the internal packed array.
|
|
* @return A pointer to the internal packed array.
|
|
*/
|
|
[[nodiscard]] pointer data() const noexcept {
|
|
return packed.data();
|
|
}
|
|
|
|
/**
|
|
* @brief Returns an iterator to the beginning.
|
|
*
|
|
* If the sparse set is empty, the returned iterator will be equal to
|
|
* `end()`.
|
|
*
|
|
* @return An iterator to the first entity of the sparse set.
|
|
*/
|
|
[[nodiscard]] iterator begin() const noexcept {
|
|
const auto pos = static_cast<difference_type>(packed.size());
|
|
return iterator{packed, pos};
|
|
}
|
|
|
|
/*! @copydoc begin */
|
|
[[nodiscard]] const_iterator cbegin() const noexcept {
|
|
return begin();
|
|
}
|
|
|
|
/**
|
|
* @brief Returns an iterator to the end.
|
|
* @return An iterator to the element following the last entity of a sparse
|
|
* set.
|
|
*/
|
|
[[nodiscard]] iterator end() const noexcept {
|
|
return iterator{packed, {}};
|
|
}
|
|
|
|
/*! @copydoc end */
|
|
[[nodiscard]] const_iterator cend() const noexcept {
|
|
return end();
|
|
}
|
|
|
|
/**
|
|
* @brief Returns a reverse iterator to the beginning.
|
|
*
|
|
* If the sparse set is empty, the returned iterator will be equal to
|
|
* `rend()`.
|
|
*
|
|
* @return An iterator to the first entity of the reversed internal packed
|
|
* array.
|
|
*/
|
|
[[nodiscard]] reverse_iterator rbegin() const noexcept {
|
|
return stl::make_reverse_iterator(end());
|
|
}
|
|
|
|
/*! @copydoc rbegin */
|
|
[[nodiscard]] const_reverse_iterator crbegin() const noexcept {
|
|
return rbegin();
|
|
}
|
|
|
|
/**
|
|
* @brief Returns a reverse iterator to the end.
|
|
* @return An iterator to the element following the last entity of the
|
|
* reversed sparse set.
|
|
*/
|
|
[[nodiscard]] reverse_iterator rend() const noexcept {
|
|
return stl::make_reverse_iterator(begin());
|
|
}
|
|
|
|
/*! @copydoc rend */
|
|
[[nodiscard]] const_reverse_iterator crend() const noexcept {
|
|
return rend();
|
|
}
|
|
|
|
/**
|
|
* @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]] const_iterator find(const entity_type entt) const noexcept {
|
|
return contains(entt) ? to_iterator(entt) : end();
|
|
}
|
|
|
|
/**
|
|
* @brief Checks if a sparse set contains an entity.
|
|
* @param entt A valid identifier.
|
|
* @return True if the sparse set contains the entity, false otherwise.
|
|
*/
|
|
[[nodiscard]] bool contains(const entity_type entt) const noexcept {
|
|
const auto *elem = sparse_ptr(entt);
|
|
constexpr auto cap = traits_type::entity_mask;
|
|
constexpr auto mask = traits_type::to_integral(null) & ~cap;
|
|
// testing versions permits to avoid accessing the packed array
|
|
return elem && (((mask & traits_type::to_integral(entt)) ^ traits_type::to_integral(*elem)) < cap);
|
|
}
|
|
|
|
/**
|
|
* @brief Returns the contained version for an identifier.
|
|
* @param entt A valid identifier.
|
|
* @return The version for the given identifier if present, the tombstone
|
|
* version otherwise.
|
|
*/
|
|
[[nodiscard]] version_type current(const entity_type entt) const noexcept {
|
|
const auto *elem = sparse_ptr(entt);
|
|
constexpr auto fallback = traits_type::to_version(tombstone);
|
|
return elem ? traits_type::to_version(*elem) : fallback;
|
|
}
|
|
|
|
/**
|
|
* @brief Returns the position of an entity in a sparse set.
|
|
*
|
|
* @warning
|
|
* Attempting to get the position of an entity that doesn't belong to the
|
|
* sparse set results in undefined behavior.
|
|
*
|
|
* @param entt A valid identifier.
|
|
* @return The position of the entity in the sparse set.
|
|
*/
|
|
[[nodiscard]] size_type index(const entity_type entt) const noexcept {
|
|
ENTT_ASSERT(contains(entt), "Set does not contain entity");
|
|
return entity_to_pos(sparse_ref(entt));
|
|
}
|
|
|
|
/**
|
|
* @brief Returns the entity at specified location.
|
|
* @param pos The position for which to return the entity.
|
|
* @return The entity at specified location.
|
|
*/
|
|
[[nodiscard]] entity_type operator[](const size_type pos) const noexcept {
|
|
ENTT_ASSERT(pos < packed.size(), "Index out of bounds");
|
|
return packed[pos];
|
|
}
|
|
|
|
/**
|
|
* @brief Returns the element assigned to an entity, if any.
|
|
*
|
|
* @warning
|
|
* Attempting to use an entity that doesn't belong to the sparse set results
|
|
* in undefined behavior.
|
|
*
|
|
* @param entt A valid identifier.
|
|
* @return An opaque pointer to the element assigned to the entity, if any.
|
|
*/
|
|
[[nodiscard]] const void *value(const entity_type entt) const noexcept {
|
|
return get_at(index(entt));
|
|
}
|
|
|
|
/*! @copydoc value */
|
|
[[nodiscard]] void *value(const entity_type entt) noexcept {
|
|
return const_cast<void *>(stl::as_const(*this).value(entt));
|
|
}
|
|
|
|
/**
|
|
* @brief Assigns an entity to a sparse set.
|
|
*
|
|
* @warning
|
|
* Attempting to assign an entity that already belongs to the sparse set
|
|
* results in undefined behavior.
|
|
*
|
|
* @param entt A valid identifier.
|
|
* @param elem Optional opaque element to forward to mixins, if any.
|
|
* @return Iterator pointing to the emplaced element in case of success, the
|
|
* `end()` iterator otherwise.
|
|
*/
|
|
iterator push(const entity_type entt, const void *elem = nullptr) {
|
|
return try_emplace(entt, false, elem);
|
|
}
|
|
|
|
/**
|
|
* @brief Assigns one or more entities to a sparse set.
|
|
*
|
|
* @warning
|
|
* Attempting to assign an entity that already belongs to the sparse set
|
|
* 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.
|
|
* @return Iterator pointing to the first element inserted in case of
|
|
* success, the `end()` iterator otherwise.
|
|
*/
|
|
iterator push(stl::input_iterator auto first, stl::input_iterator auto last) {
|
|
auto curr = end();
|
|
|
|
for(; first != last; ++first) {
|
|
curr = try_emplace(*first, true);
|
|
}
|
|
|
|
return curr;
|
|
}
|
|
|
|
/**
|
|
* @brief Bump the version number of an entity.
|
|
*
|
|
* @warning
|
|
* Attempting to bump the version of an entity that doesn't belong to the
|
|
* sparse set results in undefined behavior.
|
|
*
|
|
* @param entt A valid identifier.
|
|
* @return The version of the given identifier.
|
|
*/
|
|
version_type bump(const entity_type entt) {
|
|
auto &elem = sparse_ref(entt);
|
|
ENTT_ASSERT(entt != null && elem != tombstone, "Cannot set the required version");
|
|
elem = traits_type::combine(traits_type::to_integral(elem), traits_type::to_integral(entt));
|
|
packed[entity_to_pos(elem)] = entt;
|
|
return traits_type::to_version(entt);
|
|
}
|
|
|
|
/**
|
|
* @brief Erases an entity from a sparse set.
|
|
*
|
|
* @warning
|
|
* Attempting to erase an entity that doesn't belong to the sparse set
|
|
* results in undefined behavior.
|
|
*
|
|
* @param entt A valid identifier.
|
|
*/
|
|
void erase(const entity_type entt) {
|
|
const auto it = to_iterator(entt);
|
|
pop(it, it + 1u);
|
|
}
|
|
|
|
/**
|
|
* @brief Erases entities from a set.
|
|
*
|
|
* @sa erase
|
|
*
|
|
* @tparam It Type of input iterator.
|
|
* @param first An iterator to the first element of the range of entities.
|
|
* @param last An iterator past the last element of the range of entities.
|
|
*/
|
|
template<stl::input_iterator It>
|
|
void erase(It first, It last) {
|
|
if constexpr(stl::is_same_v<It, basic_iterator>) {
|
|
pop(first, last);
|
|
} else {
|
|
for(; first != last; ++first) {
|
|
erase(*first);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Removes an entity from a sparse set if it exists.
|
|
* @param entt A valid identifier.
|
|
* @return True if the entity is actually removed, false otherwise.
|
|
*/
|
|
bool remove(const entity_type entt) {
|
|
return contains(entt) && (erase(entt), true);
|
|
}
|
|
|
|
/**
|
|
* @brief Removes entities from a sparse set if they exist.
|
|
* @tparam It Type of input iterator.
|
|
* @param first An iterator to the first element of the range of entities.
|
|
* @param last An iterator past the last element of the range of entities.
|
|
* @return The number of entities actually removed.
|
|
*/
|
|
template<stl::input_iterator It>
|
|
size_type remove(It first, It last) {
|
|
size_type count{};
|
|
|
|
if constexpr(stl::is_same_v<It, basic_iterator>) {
|
|
while(first != last) {
|
|
while(first != last && !contains(*first)) {
|
|
++first;
|
|
}
|
|
|
|
const auto it = first;
|
|
|
|
while(first != last && contains(*first)) {
|
|
++first;
|
|
}
|
|
|
|
count += static_cast<size_type>(stl::distance(it, first));
|
|
erase(it, first);
|
|
}
|
|
} else {
|
|
for(; first != last; ++first) {
|
|
count += remove(*first);
|
|
}
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
/*! @brief Removes all tombstones from a sparse set. */
|
|
void compact() {
|
|
if(mode == deletion_policy::in_place) {
|
|
size_type from = packed.size();
|
|
size_type pos = stl::exchange(head, max_size);
|
|
|
|
for(; from && packed[from - 1u] == tombstone; --from) {}
|
|
|
|
while(pos != max_size) {
|
|
if(const auto to = stl::exchange(pos, entity_to_pos(packed[pos])); to < from) {
|
|
--from;
|
|
swap_or_move(from, to);
|
|
|
|
packed[to] = packed[from];
|
|
const auto elem = static_cast<traits_type::entity_type>(to);
|
|
sparse_ref(packed[to]) = traits_type::combine(elem, traits_type::to_integral(packed[to]));
|
|
|
|
for(; from && packed[from - 1u] == tombstone; --from) {}
|
|
}
|
|
}
|
|
|
|
packed.erase(packed.begin() + static_cast<difference_type>(from), packed.end());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Swaps two entities in a sparse set.
|
|
*
|
|
* For what it's worth, this function affects both the internal sparse array
|
|
* and the internal packed array. Users should not care of that anyway.
|
|
*
|
|
* @warning
|
|
* Attempting to swap entities that don't belong to the sparse set results
|
|
* in undefined behavior.
|
|
*
|
|
* @param lhs A valid identifier.
|
|
* @param rhs A valid identifier.
|
|
*/
|
|
void swap_elements(const entity_type lhs, const entity_type rhs) {
|
|
const auto from = index(lhs);
|
|
const auto to = index(rhs);
|
|
|
|
// basic no-leak guarantee if swapping throws
|
|
swap_or_move(from, to);
|
|
swap_at(from, to);
|
|
}
|
|
|
|
/**
|
|
* @brief Sort the first count elements according to the given comparison
|
|
* function.
|
|
*
|
|
* The comparison function object must return `true` if the first element
|
|
* is _less_ than the second one, `false` otherwise. The signature of the
|
|
* comparison function should be equivalent to the following:
|
|
*
|
|
* @code{.cpp}
|
|
* bool(const Entity, const Entity);
|
|
* @endcode
|
|
*
|
|
* Moreover, the comparison function object shall induce a
|
|
* _strict weak ordering_ on the values.
|
|
*
|
|
* The sort function object must offer a member function template
|
|
* `operator()` that accepts three arguments:
|
|
*
|
|
* * An iterator to the first element of the range to sort.
|
|
* * An iterator past the last element of the range to sort.
|
|
* * A comparison function to use to compare the elements.
|
|
*
|
|
* @tparam Compare Type of comparison function object.
|
|
* @tparam Sort Type of sort function object.
|
|
* @tparam Args Types of arguments to forward to the sort function object.
|
|
* @param length Number of elements to sort.
|
|
* @param compare A valid comparison function object.
|
|
* @param algo A valid sort function object.
|
|
* @param args Arguments to forward to the sort function object, if any.
|
|
*/
|
|
template<typename Compare, typename Sort = std_sort, typename... Args>
|
|
void sort_n(const size_type length, Compare compare, Sort algo = Sort{}, Args &&...args) {
|
|
ENTT_ASSERT((mode != deletion_policy::in_place) || (head == max_size), "Sorting with tombstones not allowed");
|
|
ENTT_ASSERT(!(length > packed.size()), "Length exceeds the number of elements");
|
|
|
|
algo(packed.rend() - static_cast<difference_type>(length), packed.rend(), stl::move(compare), stl::forward<Args>(args)...);
|
|
|
|
for(size_type pos{}; pos < length; ++pos) {
|
|
auto curr = pos;
|
|
auto next = index(packed[curr]);
|
|
|
|
while(curr != next) {
|
|
const auto idx = index(packed[next]);
|
|
const auto entt = packed[curr];
|
|
|
|
swap_or_move(next, idx);
|
|
const auto elem = static_cast<traits_type::entity_type>(curr);
|
|
sparse_ref(entt) = traits_type::combine(elem, traits_type::to_integral(packed[curr]));
|
|
curr = stl::exchange(next, idx);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Sort all elements according to the given comparison function.
|
|
*
|
|
* @sa sort_n
|
|
*
|
|
* @tparam Compare Type of comparison function object.
|
|
* @tparam Sort Type of sort function object.
|
|
* @tparam Args Types of arguments to forward to the sort function object.
|
|
* @param compare A valid comparison function object.
|
|
* @param algo A valid sort function object.
|
|
* @param args Arguments to forward to the sort function object, if any.
|
|
*/
|
|
template<typename Compare, typename Sort = std_sort, typename... Args>
|
|
void sort(Compare compare, Sort algo = Sort{}, Args &&...args) {
|
|
const size_type len = (mode == deletion_policy::swap_only) ? head : packed.size();
|
|
sort_n(len, stl::move(compare), stl::move(algo), stl::forward<Args>(args)...);
|
|
}
|
|
|
|
/**
|
|
* @brief Sort entities according to their order in a range.
|
|
*
|
|
* Entities that are part of both the sparse set and the range are ordered
|
|
* internally according to the order they have in the range.<br/>
|
|
* All other entities goes to the end of the sparse set and there are no
|
|
* guarantees on their order.
|
|
*
|
|
* @tparam It Type of input iterator.
|
|
* @param first An iterator to the first element of the range of entities.
|
|
* @param last An iterator past the last element of the range of entities.
|
|
* @return An iterator past the last of the elements actually shared.
|
|
*/
|
|
template<stl::input_iterator It>
|
|
iterator sort_as(It first, It last) {
|
|
ENTT_ASSERT((mode != deletion_policy::in_place) || (head == max_size), "Sorting with tombstones not allowed");
|
|
const size_type len = (mode == deletion_policy::swap_only) ? head : packed.size();
|
|
auto it = end() - static_cast<difference_type>(len);
|
|
|
|
for(const auto other = end(); (it != other) && (first != last); ++first) {
|
|
if(const auto curr = *first; contains(curr)) {
|
|
if(const auto entt = *it; entt != curr) {
|
|
// basic no-leak guarantee (with invalid state) if swapping throws
|
|
swap_elements(entt, curr);
|
|
}
|
|
|
|
++it;
|
|
}
|
|
}
|
|
|
|
return it;
|
|
}
|
|
|
|
/*! @brief Clears a sparse set. */
|
|
void clear() {
|
|
pop_all();
|
|
// sanity check to avoid subtle issues due to storage classes
|
|
ENTT_ASSERT((compact(), size()) == 0u, "Non-empty set");
|
|
head = policy_to_head();
|
|
packed.clear();
|
|
}
|
|
|
|
/**
|
|
* @brief Returns a type info object for the value type, if any.
|
|
* @return A type info object for the value type, if any.
|
|
*/
|
|
[[nodiscard]] const type_info &info() const noexcept {
|
|
return *descriptor;
|
|
}
|
|
|
|
/**
|
|
* @brief Forwards variables to derived classes, if any.
|
|
* @tparam Type Type of the element to forward.
|
|
* @param value The element to forward.
|
|
*/
|
|
template<typename Type>
|
|
void bind(Type &&value) noexcept {
|
|
bind_any(forward_as_any(stl::forward<Type>(value)));
|
|
}
|
|
|
|
private:
|
|
sparse_container_type sparse;
|
|
packed_container_type packed;
|
|
const type_info *descriptor;
|
|
deletion_policy mode;
|
|
size_type head;
|
|
};
|
|
|
|
} // namespace entt
|
|
|
|
#endif
|