mirror of
https://github.com/zhenyan121/Cubed.git
synced 2026-08-08 17:57:02 +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.
891 lines
31 KiB
C++
891 lines
31 KiB
C++
#ifndef ENTT_CONTAINER_DENSE_SET_HPP
|
|
#define ENTT_CONTAINER_DENSE_SET_HPP
|
|
|
|
#include <compare>
|
|
#include "../config/config.h"
|
|
#include "../core/bit.hpp"
|
|
#include "../core/compressed_pair.hpp"
|
|
#include "../core/type_traits.hpp"
|
|
#include "../stl/bit.hpp"
|
|
#include "../stl/cmath.hpp"
|
|
#include "../stl/concepts.hpp"
|
|
#include "../stl/cstddef.hpp"
|
|
#include "../stl/functional.hpp"
|
|
#include "../stl/iterator.hpp"
|
|
#include "../stl/limits.hpp"
|
|
#include "../stl/memory.hpp"
|
|
#include "../stl/tuple.hpp"
|
|
#include "../stl/type_traits.hpp"
|
|
#include "../stl/utility.hpp"
|
|
#include "../stl/vector.hpp"
|
|
#include "fwd.hpp"
|
|
|
|
namespace entt {
|
|
|
|
/*! @cond ENTT_INTERNAL */
|
|
namespace internal {
|
|
|
|
static constexpr stl::size_t dense_set_placeholder_position = (stl::numeric_limits<stl::size_t>::max)();
|
|
|
|
template<typename It>
|
|
class dense_set_iterator final {
|
|
template<typename>
|
|
friend class dense_set_iterator;
|
|
|
|
static_assert(stl::is_pointer_v<It>, "Not a pointer type");
|
|
|
|
public:
|
|
using value_type = stl::remove_const_t<stl::remove_pointer_t<It>>::second_type;
|
|
using pointer = const value_type *;
|
|
using reference = const value_type &;
|
|
using difference_type = stl::ptrdiff_t;
|
|
using iterator_category = stl::random_access_iterator_tag;
|
|
|
|
constexpr dense_set_iterator() noexcept
|
|
: it{} {}
|
|
|
|
constexpr dense_set_iterator(const It iter) noexcept
|
|
: it{iter} {}
|
|
|
|
template<typename Other>
|
|
requires (!stl::same_as<It, Other> && stl::constructible_from<It, Other>)
|
|
constexpr dense_set_iterator(const dense_set_iterator<Other> &other) noexcept
|
|
: it{other.it} {}
|
|
|
|
constexpr dense_set_iterator &operator++() noexcept {
|
|
return ++it, *this;
|
|
}
|
|
|
|
constexpr dense_set_iterator operator++(int) noexcept {
|
|
const dense_set_iterator orig = *this;
|
|
return ++(*this), orig;
|
|
}
|
|
|
|
constexpr dense_set_iterator &operator--() noexcept {
|
|
return --it, *this;
|
|
}
|
|
|
|
constexpr dense_set_iterator operator--(int) noexcept {
|
|
const dense_set_iterator orig = *this;
|
|
return operator--(), orig;
|
|
}
|
|
|
|
constexpr dense_set_iterator &operator+=(const difference_type value) noexcept {
|
|
it += value;
|
|
return *this;
|
|
}
|
|
|
|
constexpr dense_set_iterator operator+(const difference_type value) const noexcept {
|
|
dense_set_iterator copy = *this;
|
|
return (copy += value);
|
|
}
|
|
|
|
constexpr dense_set_iterator &operator-=(const difference_type value) noexcept {
|
|
return (*this += -value);
|
|
}
|
|
|
|
constexpr dense_set_iterator operator-(const difference_type value) const noexcept {
|
|
return (*this + -value);
|
|
}
|
|
|
|
[[nodiscard]] constexpr reference operator[](const difference_type value) const noexcept {
|
|
return it[value].second;
|
|
}
|
|
|
|
[[nodiscard]] constexpr pointer operator->() const noexcept {
|
|
return stl::addressof(operator[](0));
|
|
}
|
|
|
|
[[nodiscard]] constexpr reference operator*() const noexcept {
|
|
return operator[](0);
|
|
}
|
|
|
|
template<typename Other>
|
|
[[nodiscard]] constexpr stl::ptrdiff_t operator-(const dense_set_iterator<Other> &other) const noexcept {
|
|
return it - other.it;
|
|
}
|
|
|
|
template<typename Other>
|
|
[[nodiscard]] constexpr bool operator==(const dense_set_iterator<Other> &other) const noexcept {
|
|
return it == other.it;
|
|
}
|
|
|
|
template<typename Other>
|
|
[[nodiscard]] constexpr auto operator<=>(const dense_set_iterator<Other> &other) const noexcept {
|
|
return it <=> other.it;
|
|
}
|
|
|
|
private:
|
|
It it;
|
|
};
|
|
|
|
template<typename It>
|
|
class dense_set_local_iterator final {
|
|
template<typename>
|
|
friend class dense_set_local_iterator;
|
|
|
|
static_assert(stl::is_pointer_v<It>, "Not a pointer type");
|
|
|
|
public:
|
|
using value_type = stl::remove_const_t<stl::remove_pointer_t<It>>::second_type;
|
|
using pointer = const value_type *;
|
|
using reference = const value_type &;
|
|
using difference_type = stl::ptrdiff_t;
|
|
using iterator_category = stl::forward_iterator_tag;
|
|
|
|
constexpr dense_set_local_iterator() noexcept = default;
|
|
|
|
constexpr dense_set_local_iterator(It iter, const stl::size_t pos) noexcept
|
|
: it{iter},
|
|
offset{pos} {}
|
|
|
|
template<typename Other>
|
|
requires (!stl::same_as<It, Other> && stl::constructible_from<It, Other>)
|
|
constexpr dense_set_local_iterator(const dense_set_local_iterator<Other> &other) noexcept
|
|
: it{other.it},
|
|
offset{other.offset} {}
|
|
|
|
constexpr dense_set_local_iterator &operator++() noexcept {
|
|
return offset = it[static_cast<difference_type>(offset)].first, *this;
|
|
}
|
|
|
|
constexpr dense_set_local_iterator operator++(int) noexcept {
|
|
const dense_set_local_iterator orig = *this;
|
|
return ++(*this), orig;
|
|
}
|
|
|
|
[[nodiscard]] constexpr pointer operator->() const noexcept {
|
|
return stl::addressof(it[static_cast<difference_type>(offset)].second);
|
|
}
|
|
|
|
[[nodiscard]] constexpr reference operator*() const noexcept {
|
|
return *operator->();
|
|
}
|
|
|
|
template<typename Other>
|
|
[[nodiscard]] constexpr bool operator==(const dense_set_local_iterator<Other> &other) const noexcept {
|
|
return offset == other.offset;
|
|
}
|
|
|
|
[[nodiscard]] constexpr stl::size_t index() const noexcept {
|
|
return offset;
|
|
}
|
|
|
|
private:
|
|
It it{};
|
|
stl::size_t offset{dense_set_placeholder_position};
|
|
};
|
|
|
|
} // namespace internal
|
|
/*! @endcond */
|
|
|
|
/**
|
|
* @brief Associative container for unique objects of a given type.
|
|
*
|
|
* Internally, elements are organized into buckets. Which bucket an element is
|
|
* placed into depends entirely on its hash. Elements with the same hash code
|
|
* appear in the same bucket.
|
|
*
|
|
* @tparam Type Value type of the associative container.
|
|
* @tparam Hash Type of function to use to hash the values.
|
|
* @tparam KeyEqual Type of function to use to compare the values for equality.
|
|
* @tparam Allocator Type of allocator used to manage memory and elements.
|
|
*/
|
|
template<typename Type, typename Hash, typename KeyEqual, typename Allocator>
|
|
class dense_set {
|
|
static constexpr float default_threshold = 0.875f;
|
|
static constexpr stl::size_t minimum_capacity = 8u;
|
|
static constexpr stl::size_t placeholder_position = internal::dense_set_placeholder_position;
|
|
|
|
using node_type = stl::pair<stl::size_t, Type>;
|
|
using alloc_traits = stl::allocator_traits<Allocator>;
|
|
static_assert(stl::is_same_v<typename alloc_traits::value_type, Type>, "Invalid value type");
|
|
using sparse_container_type = stl::vector<stl::size_t, typename alloc_traits::template rebind_alloc<stl::size_t>>;
|
|
using packed_container_type = stl::vector<node_type, typename alloc_traits::template rebind_alloc<node_type>>;
|
|
|
|
[[nodiscard]] stl::size_t value_to_bucket(const auto &value) const noexcept {
|
|
return fast_mod(static_cast<size_type>(sparse.second()(value)), bucket_count());
|
|
}
|
|
|
|
[[nodiscard]] auto constrained_find(const auto &value, const stl::size_t bucket) {
|
|
for(auto offset = sparse.first()[bucket]; offset != placeholder_position; offset = packed.first()[offset].first) {
|
|
if(packed.second()(packed.first()[offset].second, value)) {
|
|
return begin() + static_cast<iterator::difference_type>(offset);
|
|
}
|
|
}
|
|
|
|
return end();
|
|
}
|
|
|
|
[[nodiscard]] auto constrained_find(const auto &value, const stl::size_t bucket) const {
|
|
for(auto offset = sparse.first()[bucket]; offset != placeholder_position; offset = packed.first()[offset].first) {
|
|
if(packed.second()(packed.first()[offset].second, value)) {
|
|
return cbegin() + static_cast<const_iterator::difference_type>(offset);
|
|
}
|
|
}
|
|
|
|
return cend();
|
|
}
|
|
|
|
template<typename Other>
|
|
[[nodiscard]] auto insert_or_do_nothing(Other &&value) {
|
|
const auto index = value_to_bucket(value);
|
|
|
|
if(auto it = constrained_find(value, index); it != end()) {
|
|
return stl::make_pair(it, false);
|
|
}
|
|
|
|
packed.first().emplace_back(sparse.first()[index], stl::forward<Other>(value));
|
|
sparse.first()[index] = packed.first().size() - 1u;
|
|
rehash_if_required();
|
|
|
|
return stl::make_pair(--end(), true);
|
|
}
|
|
|
|
void move_and_pop(const stl::size_t pos) {
|
|
if(const auto last = size() - 1u; pos != last) {
|
|
size_type *curr = &sparse.first()[value_to_bucket(packed.first().back().second)];
|
|
packed.first()[pos] = stl::move(packed.first().back());
|
|
for(; *curr != last; curr = &packed.first()[*curr].first) {}
|
|
*curr = pos;
|
|
}
|
|
|
|
packed.first().pop_back();
|
|
}
|
|
|
|
void rehash_if_required() {
|
|
if(const auto bc = bucket_count(); size() > static_cast<size_type>(static_cast<float>(bc) * max_load_factor())) {
|
|
rehash(bc * 2u);
|
|
}
|
|
}
|
|
|
|
public:
|
|
/*! @brief Allocator type. */
|
|
using allocator_type = Allocator;
|
|
/*! @brief Key type of the container. */
|
|
using key_type = Type;
|
|
/*! @brief Value type of the container. */
|
|
using value_type = Type;
|
|
/*! @brief Unsigned integer type. */
|
|
using size_type = stl::size_t;
|
|
/*! @brief Signed integer type. */
|
|
using difference_type = stl::ptrdiff_t;
|
|
/*! @brief Type of function to use to hash the elements. */
|
|
using hasher = Hash;
|
|
/*! @brief Type of function to use to compare the elements for equality. */
|
|
using key_equal = KeyEqual;
|
|
/*! @brief Random access iterator type. */
|
|
using iterator = internal::dense_set_iterator<typename packed_container_type::pointer>;
|
|
/*! @brief Constant random access iterator type. */
|
|
using const_iterator = internal::dense_set_iterator<typename packed_container_type::const_pointer>;
|
|
/*! @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 Forward iterator type. */
|
|
using local_iterator = internal::dense_set_local_iterator<typename packed_container_type::pointer>;
|
|
/*! @brief Constant forward iterator type. */
|
|
using const_local_iterator = internal::dense_set_local_iterator<typename packed_container_type::const_pointer>;
|
|
|
|
/*! @brief Default constructor. */
|
|
dense_set()
|
|
: dense_set{minimum_capacity} {}
|
|
|
|
/**
|
|
* @brief Constructs an empty container with a given allocator.
|
|
* @param allocator The allocator to use.
|
|
*/
|
|
explicit dense_set(const allocator_type &allocator)
|
|
: dense_set{minimum_capacity, hasher{}, key_equal{}, allocator} {}
|
|
|
|
/**
|
|
* @brief Constructs an empty container with a given allocator and user
|
|
* supplied minimal number of buckets.
|
|
* @param cnt Minimal number of buckets.
|
|
* @param allocator The allocator to use.
|
|
*/
|
|
dense_set(const size_type cnt, const allocator_type &allocator)
|
|
: dense_set{cnt, hasher{}, key_equal{}, allocator} {}
|
|
|
|
/**
|
|
* @brief Constructs an empty container with a given allocator, hash
|
|
* function and user supplied minimal number of buckets.
|
|
* @param cnt Minimal number of buckets.
|
|
* @param hash Hash function to use.
|
|
* @param allocator The allocator to use.
|
|
*/
|
|
dense_set(const size_type cnt, const hasher &hash, const allocator_type &allocator)
|
|
: dense_set{cnt, hash, key_equal{}, allocator} {}
|
|
|
|
/**
|
|
* @brief Constructs an empty container with a given allocator, hash
|
|
* function, compare function and user supplied minimal number of buckets.
|
|
* @param cnt Minimal number of buckets.
|
|
* @param hash Hash function to use.
|
|
* @param equal Compare function to use.
|
|
* @param allocator The allocator to use.
|
|
*/
|
|
explicit dense_set(const size_type cnt, const hasher &hash = hasher{}, const key_equal &equal = key_equal{}, const allocator_type &allocator = allocator_type{})
|
|
: sparse{allocator, hash},
|
|
packed{allocator, equal} {
|
|
rehash(cnt);
|
|
}
|
|
|
|
/*! @brief Default copy constructor. */
|
|
dense_set(const dense_set &) = default;
|
|
|
|
/**
|
|
* @brief Allocator-extended copy constructor.
|
|
* @param other The instance to copy from.
|
|
* @param allocator The allocator to use.
|
|
*/
|
|
dense_set(const dense_set &other, const allocator_type &allocator)
|
|
: sparse{stl::piecewise_construct, stl::forward_as_tuple(other.sparse.first(), allocator), stl::forward_as_tuple(other.sparse.second())},
|
|
packed{stl::piecewise_construct, stl::forward_as_tuple(other.packed.first(), allocator), stl::forward_as_tuple(other.packed.second())},
|
|
threshold{other.threshold} {}
|
|
|
|
/*! @brief Default move constructor. */
|
|
dense_set(dense_set &&) noexcept = default;
|
|
|
|
/**
|
|
* @brief Allocator-extended move constructor.
|
|
* @param other The instance to move from.
|
|
* @param allocator The allocator to use.
|
|
*/
|
|
dense_set(dense_set &&other, const allocator_type &allocator)
|
|
: sparse{stl::piecewise_construct, stl::forward_as_tuple(stl::move(other.sparse.first()), allocator), stl::forward_as_tuple(stl::move(other.sparse.second()))},
|
|
packed{stl::piecewise_construct, stl::forward_as_tuple(stl::move(other.packed.first()), allocator), stl::forward_as_tuple(stl::move(other.packed.second()))},
|
|
threshold{other.threshold} {}
|
|
|
|
/*! @brief Default destructor. */
|
|
~dense_set() = default;
|
|
|
|
/**
|
|
* @brief Default copy assignment operator.
|
|
* @return This container.
|
|
*/
|
|
dense_set &operator=(const dense_set &) = default;
|
|
|
|
/**
|
|
* @brief Default move assignment operator.
|
|
* @return This container.
|
|
*/
|
|
dense_set &operator=(dense_set &&) noexcept = default;
|
|
|
|
/**
|
|
* @brief Exchanges the contents with those of a given container.
|
|
* @param other Container to exchange the content with.
|
|
*/
|
|
void swap(dense_set &other) noexcept {
|
|
using stl::swap;
|
|
swap(sparse, other.sparse);
|
|
swap(packed, other.packed);
|
|
swap(threshold, other.threshold);
|
|
}
|
|
|
|
/**
|
|
* @brief Returns the associated allocator.
|
|
* @return The associated allocator.
|
|
*/
|
|
[[nodiscard]] constexpr allocator_type get_allocator() const noexcept {
|
|
return sparse.first().get_allocator();
|
|
}
|
|
|
|
/**
|
|
* @brief Returns an iterator to the beginning.
|
|
*
|
|
* If the array 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 {
|
|
return packed.first().data();
|
|
}
|
|
|
|
/*! @copydoc cbegin */
|
|
[[nodiscard]] const_iterator begin() const noexcept {
|
|
return cbegin();
|
|
}
|
|
|
|
/*! @copydoc begin */
|
|
[[nodiscard]] iterator begin() noexcept {
|
|
return packed.first().data();
|
|
}
|
|
|
|
/**
|
|
* @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 packed.first().data() + packed.first().size();
|
|
}
|
|
|
|
/*! @copydoc cend */
|
|
[[nodiscard]] const_iterator end() const noexcept {
|
|
return cend();
|
|
}
|
|
|
|
/*! @copydoc end */
|
|
[[nodiscard]] iterator end() noexcept {
|
|
return packed.first().data() + packed.first().size();
|
|
}
|
|
|
|
/**
|
|
* @brief Returns a reverse iterator to the beginning.
|
|
*
|
|
* If the array 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 Checks whether a container is empty.
|
|
* @return True if the container is empty, false otherwise.
|
|
*/
|
|
[[nodiscard]] bool empty() const noexcept {
|
|
return packed.first().empty();
|
|
}
|
|
|
|
/**
|
|
* @brief Returns the number of elements in a container.
|
|
* @return Number of elements in a container.
|
|
*/
|
|
[[nodiscard]] size_type size() const noexcept {
|
|
return packed.first().size();
|
|
}
|
|
|
|
/**
|
|
* @brief Returns the maximum possible number of elements.
|
|
* @return Maximum possible number of elements.
|
|
*/
|
|
[[nodiscard]] size_type max_size() const noexcept {
|
|
return packed.first().max_size();
|
|
}
|
|
|
|
/*! @brief Clears the container. */
|
|
void clear() noexcept {
|
|
sparse.first().clear();
|
|
packed.first().clear();
|
|
rehash(0u);
|
|
}
|
|
|
|
/**
|
|
* @brief Inserts an element into the container, if it does not exist.
|
|
* @param value An element to insert into the container.
|
|
* @return A pair consisting of an iterator to the inserted element (or to
|
|
* the element that prevented the insertion) and a bool denoting whether the
|
|
* insertion took place.
|
|
*/
|
|
stl::pair<iterator, bool> insert(const value_type &value) {
|
|
return insert_or_do_nothing(value);
|
|
}
|
|
|
|
/*! @copydoc insert */
|
|
stl::pair<iterator, bool> insert(value_type &&value) {
|
|
return insert_or_do_nothing(stl::move(value));
|
|
}
|
|
|
|
/**
|
|
* @brief Inserts elements into the container, if they do not exist.
|
|
* @param first An iterator to the first element of the range of elements.
|
|
* @param last An iterator past the last element of the range of elements.
|
|
*/
|
|
void insert(stl::input_iterator auto first, stl::input_iterator auto last) {
|
|
for(; first != last; ++first) {
|
|
insert(*first);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Constructs an element in-place, if it does not exist.
|
|
*
|
|
* The element is also constructed when the container already has the key,
|
|
* in which case the newly constructed object is destroyed immediately.
|
|
*
|
|
* @tparam Args Types of arguments to forward to the constructor of the
|
|
* element.
|
|
* @param args Arguments to forward to the constructor of the element.
|
|
* @return A pair consisting of an iterator to the inserted element (or to
|
|
* the element that prevented the insertion) and a bool denoting whether the
|
|
* insertion took place.
|
|
*/
|
|
template<typename... Args>
|
|
stl::pair<iterator, bool> emplace(Args &&...args) {
|
|
if constexpr(((sizeof...(Args) == 1u) && ... && stl::is_same_v<stl::decay_t<Args>, value_type>)) {
|
|
return insert_or_do_nothing(stl::forward<Args>(args)...);
|
|
} else {
|
|
auto &node = packed.first().emplace_back(stl::piecewise_construct, stl::make_tuple(packed.first().size()), stl::forward_as_tuple(stl::forward<Args>(args)...));
|
|
const auto index = value_to_bucket(node.second);
|
|
|
|
if(auto it = constrained_find(node.second, index); it != end()) {
|
|
packed.first().pop_back();
|
|
return stl::make_pair(it, false);
|
|
}
|
|
|
|
stl::swap(node.first, sparse.first()[index]);
|
|
rehash_if_required();
|
|
|
|
return stl::make_pair(--end(), true);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Removes an element from a given position.
|
|
* @param pos An iterator to the element to remove.
|
|
* @return An iterator following the removed element.
|
|
*/
|
|
iterator erase(const_iterator pos) {
|
|
const auto diff = pos - cbegin();
|
|
erase(*pos);
|
|
return begin() + diff;
|
|
}
|
|
|
|
/**
|
|
* @brief Removes the given elements from a container.
|
|
* @param first An iterator to the first element of the range of elements.
|
|
* @param last An iterator past the last element of the range of elements.
|
|
* @return An iterator following the last removed element.
|
|
*/
|
|
iterator erase(const_iterator first, const_iterator last) {
|
|
const auto dist = first - cbegin();
|
|
|
|
for(auto from = last - cbegin(); from != dist; --from) {
|
|
erase(packed.first()[static_cast<size_type>(from) - 1u].second);
|
|
}
|
|
|
|
return (begin() + dist);
|
|
}
|
|
|
|
/**
|
|
* @brief Removes the element associated with a given value.
|
|
* @param value Value of an element to remove.
|
|
* @return Number of elements removed (either 0 or 1).
|
|
*/
|
|
size_type erase(const value_type &value) {
|
|
for(size_type *curr = &sparse.first()[value_to_bucket(value)]; *curr != placeholder_position; curr = &packed.first()[*curr].first) {
|
|
if(packed.second()(packed.first()[*curr].second, value)) {
|
|
const auto index = *curr;
|
|
*curr = packed.first()[*curr].first;
|
|
move_and_pop(index);
|
|
return 1u;
|
|
}
|
|
}
|
|
|
|
return 0u;
|
|
}
|
|
|
|
/**
|
|
* @brief Returns the number of elements matching a value (either 1 or 0).
|
|
* @param key Key value of an element to search for.
|
|
* @return Number of elements matching the key (either 1 or 0).
|
|
*/
|
|
[[nodiscard]] size_type count(const value_type &key) const {
|
|
return find(key) != end();
|
|
}
|
|
|
|
/**
|
|
* @brief Returns the number of elements matching a key (either 1 or 0).
|
|
* @param key Key value of an element to search for.
|
|
* @return Number of elements matching the key (either 1 or 0).
|
|
*/
|
|
[[nodiscard]] size_type count(const auto &key) const
|
|
requires is_transparent_v<hasher> && is_transparent_v<key_equal> {
|
|
return find(key) != end();
|
|
}
|
|
|
|
/**
|
|
* @brief Finds an element with a given value.
|
|
* @param value Value of an element to search for.
|
|
* @return An iterator to an element with the given value. If no such
|
|
* element is found, a past-the-end iterator is returned.
|
|
*/
|
|
[[nodiscard]] iterator find(const value_type &value) {
|
|
return constrained_find(value, value_to_bucket(value));
|
|
}
|
|
|
|
/*! @copydoc find */
|
|
[[nodiscard]] const_iterator find(const value_type &value) const {
|
|
return constrained_find(value, value_to_bucket(value));
|
|
}
|
|
|
|
/**
|
|
* @brief Finds an element that compares _equivalent_ to a given value.
|
|
* @param value Value of an element to search for.
|
|
* @return An iterator to an element with the given value. If no such
|
|
* element is found, a past-the-end iterator is returned.
|
|
*/
|
|
[[nodiscard]] iterator find(const auto &value)
|
|
requires is_transparent_v<hasher> && is_transparent_v<key_equal> {
|
|
return constrained_find(value, value_to_bucket(value));
|
|
}
|
|
|
|
/*! @copydoc find */
|
|
[[nodiscard]] const_iterator find(const auto &value) const
|
|
requires is_transparent_v<hasher> && is_transparent_v<key_equal> {
|
|
return constrained_find(value, value_to_bucket(value));
|
|
}
|
|
|
|
/**
|
|
* @brief Returns a range containing all elements with a given value.
|
|
* @param value Value of an element to search for.
|
|
* @return A pair of iterators pointing to the first element and past the
|
|
* last element of the range.
|
|
*/
|
|
[[nodiscard]] stl::pair<iterator, iterator> equal_range(const value_type &value) {
|
|
const auto it = find(value);
|
|
return {it, it + !(it == end())};
|
|
}
|
|
|
|
/*! @copydoc equal_range */
|
|
[[nodiscard]] stl::pair<const_iterator, const_iterator> equal_range(const value_type &value) const {
|
|
const auto it = find(value);
|
|
return {it, it + !(it == cend())};
|
|
}
|
|
|
|
/**
|
|
* @brief Returns a range containing all elements that compare _equivalent_
|
|
* to a given value.
|
|
* @param value Value of an element to search for.
|
|
* @return A pair of iterators pointing to the first element and past the
|
|
* last element of the range.
|
|
*/
|
|
[[nodiscard]] stl::pair<iterator, iterator> equal_range(const auto &value)
|
|
requires is_transparent_v<hasher> && is_transparent_v<key_equal> {
|
|
const auto it = find(value);
|
|
return {it, it + !(it == end())};
|
|
}
|
|
|
|
/*! @copydoc equal_range */
|
|
[[nodiscard]] stl::pair<const_iterator, const_iterator> equal_range(const auto &value) const
|
|
requires is_transparent_v<hasher> && is_transparent_v<key_equal> {
|
|
const auto it = find(value);
|
|
return {it, it + !(it == cend())};
|
|
}
|
|
|
|
/**
|
|
* @brief Checks if the container contains an element with a given value.
|
|
* @param value Value of an element to search for.
|
|
* @return True if there is such an element, false otherwise.
|
|
*/
|
|
[[nodiscard]] bool contains(const value_type &value) const {
|
|
return (find(value) != cend());
|
|
}
|
|
|
|
/**
|
|
* @brief Checks if the container contains an element that compares
|
|
* _equivalent_ to a given value.
|
|
* @param value Value of an element to search for.
|
|
* @return True if there is such an element, false otherwise.
|
|
*/
|
|
[[nodiscard]] bool contains(const auto &value) const
|
|
requires is_transparent_v<hasher> && is_transparent_v<key_equal> {
|
|
return (find(value) != cend());
|
|
}
|
|
|
|
/**
|
|
* @brief Returns an iterator to the beginning of a given bucket.
|
|
* @param index An index of a bucket to access.
|
|
* @return An iterator to the beginning of the given bucket.
|
|
*/
|
|
[[nodiscard]] const_local_iterator cbegin(const size_type index) const {
|
|
return {packed.first().data(), sparse.first()[index]};
|
|
}
|
|
|
|
/**
|
|
* @brief Returns an iterator to the beginning of a given bucket.
|
|
* @param index An index of a bucket to access.
|
|
* @return An iterator to the beginning of the given bucket.
|
|
*/
|
|
[[nodiscard]] const_local_iterator begin(const size_type index) const {
|
|
return cbegin(index);
|
|
}
|
|
|
|
/**
|
|
* @brief Returns an iterator to the beginning of a given bucket.
|
|
* @param index An index of a bucket to access.
|
|
* @return An iterator to the beginning of the given bucket.
|
|
*/
|
|
[[nodiscard]] local_iterator begin(const size_type index) {
|
|
return {packed.first().data(), sparse.first()[index]};
|
|
}
|
|
|
|
/**
|
|
* @brief Returns an iterator to the end of a given bucket.
|
|
* @param index An index of a bucket to access.
|
|
* @return An iterator to the end of the given bucket.
|
|
*/
|
|
[[nodiscard]] const_local_iterator cend([[maybe_unused]] const size_type index) const {
|
|
return {};
|
|
}
|
|
|
|
/**
|
|
* @brief Returns an iterator to the end of a given bucket.
|
|
* @param index An index of a bucket to access.
|
|
* @return An iterator to the end of the given bucket.
|
|
*/
|
|
[[nodiscard]] const_local_iterator end(const size_type index) const {
|
|
return cend(index);
|
|
}
|
|
|
|
/**
|
|
* @brief Returns an iterator to the end of a given bucket.
|
|
* @param index An index of a bucket to access.
|
|
* @return An iterator to the end of the given bucket.
|
|
*/
|
|
[[nodiscard]] local_iterator end([[maybe_unused]] const size_type index) {
|
|
return {};
|
|
}
|
|
|
|
/**
|
|
* @brief Returns the number of buckets.
|
|
* @return The number of buckets.
|
|
*/
|
|
[[nodiscard]] size_type bucket_count() const {
|
|
return sparse.first().size();
|
|
}
|
|
|
|
/**
|
|
* @brief Returns the maximum number of buckets.
|
|
* @return The maximum number of buckets.
|
|
*/
|
|
[[nodiscard]] size_type max_bucket_count() const {
|
|
return sparse.first().max_size();
|
|
}
|
|
|
|
/**
|
|
* @brief Returns the number of elements in a given bucket.
|
|
* @param index The index of the bucket to examine.
|
|
* @return The number of elements in the given bucket.
|
|
*/
|
|
[[nodiscard]] size_type bucket_size(const size_type index) const {
|
|
return static_cast<size_type>(stl::distance(begin(index), end(index)));
|
|
}
|
|
|
|
/**
|
|
* @brief Returns the bucket for a given element.
|
|
* @param value The value of the element to examine.
|
|
* @return The bucket for the given element.
|
|
*/
|
|
[[nodiscard]] size_type bucket(const value_type &value) const {
|
|
return value_to_bucket(value);
|
|
}
|
|
|
|
/**
|
|
* @brief Returns the average number of elements per bucket.
|
|
* @return The average number of elements per bucket.
|
|
*/
|
|
[[nodiscard]] float load_factor() const {
|
|
return static_cast<float>(size()) / static_cast<float>(bucket_count());
|
|
}
|
|
|
|
/**
|
|
* @brief Returns the maximum average number of elements per bucket.
|
|
* @return The maximum average number of elements per bucket.
|
|
*/
|
|
[[nodiscard]] float max_load_factor() const {
|
|
return threshold;
|
|
}
|
|
|
|
/**
|
|
* @brief Sets the desired maximum average number of elements per bucket.
|
|
* @param value A desired maximum average number of elements per bucket.
|
|
*/
|
|
void max_load_factor(const float value) {
|
|
ENTT_ASSERT(value > 0.f, "Invalid load factor");
|
|
threshold = value;
|
|
rehash(0u);
|
|
}
|
|
|
|
/**
|
|
* @brief Reserves at least the specified number of buckets and regenerates
|
|
* the hash table.
|
|
* @param cnt New number of buckets.
|
|
*/
|
|
void rehash(const size_type cnt) {
|
|
auto value = cnt > minimum_capacity ? cnt : minimum_capacity;
|
|
const auto cap = static_cast<size_type>(static_cast<float>(size()) / max_load_factor());
|
|
value = value > cap ? value : cap;
|
|
|
|
if(const auto sz = stl::bit_ceil(value); sz != bucket_count()) {
|
|
sparse.first().resize(sz);
|
|
|
|
for(auto &&elem: sparse.first()) {
|
|
elem = placeholder_position;
|
|
}
|
|
|
|
for(size_type pos{}, last = size(); pos < last; ++pos) {
|
|
const auto index = value_to_bucket(packed.first()[pos].second);
|
|
packed.first()[pos].first = stl::exchange(sparse.first()[index], pos);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Reserves space for at least the specified number of elements and
|
|
* regenerates the hash table.
|
|
* @param cnt New number of elements.
|
|
*/
|
|
void reserve(const size_type cnt) {
|
|
packed.first().reserve(cnt);
|
|
rehash(static_cast<size_type>(stl::ceil(static_cast<float>(cnt) / max_load_factor())));
|
|
}
|
|
|
|
/**
|
|
* @brief Returns the function used to hash the elements.
|
|
* @return The function used to hash the elements.
|
|
*/
|
|
[[nodiscard]] hasher hash_function() const {
|
|
return sparse.second();
|
|
}
|
|
|
|
/**
|
|
* @brief Returns the function used to compare elements for equality.
|
|
* @return The function used to compare elements for equality.
|
|
*/
|
|
[[nodiscard]] key_equal key_eq() const {
|
|
return packed.second();
|
|
}
|
|
|
|
private:
|
|
compressed_pair<sparse_container_type, hasher> sparse;
|
|
compressed_pair<packed_container_type, key_equal> packed;
|
|
float threshold{default_threshold};
|
|
};
|
|
|
|
} // namespace entt
|
|
|
|
#endif
|