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.
1182 lines
43 KiB
C++
1182 lines
43 KiB
C++
#ifndef ENTT_ENTITY_REGISTRY_HPP
|
|
#define ENTT_ENTITY_REGISTRY_HPP
|
|
|
|
#include <compare>
|
|
#include "../config/config.h"
|
|
#include "../container/dense_map.hpp"
|
|
#include "../core/algorithm.hpp"
|
|
#include "../core/any.hpp"
|
|
#include "../core/concepts.hpp"
|
|
#include "../core/fwd.hpp"
|
|
#include "../core/iterator.hpp"
|
|
#include "../core/memory.hpp"
|
|
#include "../core/type_info.hpp"
|
|
#include "../core/type_traits.hpp"
|
|
#include "../stl/algorithm.hpp"
|
|
#include "../stl/array.hpp"
|
|
#include "../stl/concepts.hpp"
|
|
#include "../stl/cstddef.hpp"
|
|
#include "../stl/functional.hpp"
|
|
#include "../stl/iterator.hpp"
|
|
#include "../stl/memory.hpp"
|
|
#include "../stl/tuple.hpp"
|
|
#include "../stl/type_traits.hpp"
|
|
#include "../stl/utility.hpp"
|
|
#include "entity.hpp"
|
|
#include "fwd.hpp"
|
|
#include "group.hpp"
|
|
#include "mixin.hpp"
|
|
#include "sparse_set.hpp"
|
|
#include "storage.hpp"
|
|
#include "view.hpp"
|
|
|
|
namespace entt {
|
|
|
|
/*! @cond ENTT_INTERNAL */
|
|
namespace internal {
|
|
|
|
template<typename It>
|
|
class registry_storage_iterator final {
|
|
template<typename>
|
|
friend class registry_storage_iterator;
|
|
|
|
using mapped_type = stl::remove_reference_t<decltype(stl::declval<It>()->second)>;
|
|
|
|
public:
|
|
using value_type = stl::pair<id_type, constness_as_t<typename mapped_type::element_type, mapped_type> &>;
|
|
using pointer = input_iterator_pointer<value_type>;
|
|
using reference = value_type;
|
|
using difference_type = stl::ptrdiff_t;
|
|
using iterator_category = stl::input_iterator_tag;
|
|
using iterator_concept = stl::random_access_iterator_tag;
|
|
|
|
constexpr registry_storage_iterator() noexcept
|
|
: it{} {}
|
|
|
|
constexpr registry_storage_iterator(It iter) noexcept
|
|
: it{iter} {}
|
|
|
|
template<typename Other>
|
|
requires (!stl::same_as<It, Other> && stl::constructible_from<It, Other>)
|
|
constexpr registry_storage_iterator(const registry_storage_iterator<Other> &other) noexcept
|
|
: registry_storage_iterator{other.it} {}
|
|
|
|
constexpr registry_storage_iterator &operator++() noexcept {
|
|
return ++it, *this;
|
|
}
|
|
|
|
constexpr registry_storage_iterator operator++(int) noexcept {
|
|
const registry_storage_iterator orig = *this;
|
|
return ++(*this), orig;
|
|
}
|
|
|
|
constexpr registry_storage_iterator &operator--() noexcept {
|
|
return --it, *this;
|
|
}
|
|
|
|
constexpr registry_storage_iterator operator--(int) noexcept {
|
|
const registry_storage_iterator orig = *this;
|
|
return operator--(), orig;
|
|
}
|
|
|
|
constexpr registry_storage_iterator &operator+=(const difference_type value) noexcept {
|
|
it += value;
|
|
return *this;
|
|
}
|
|
|
|
constexpr registry_storage_iterator operator+(const difference_type value) const noexcept {
|
|
registry_storage_iterator copy = *this;
|
|
return (copy += value);
|
|
}
|
|
|
|
constexpr registry_storage_iterator &operator-=(const difference_type value) noexcept {
|
|
return (*this += -value);
|
|
}
|
|
|
|
constexpr registry_storage_iterator operator-(const difference_type value) const noexcept {
|
|
return (*this + -value);
|
|
}
|
|
|
|
[[nodiscard]] constexpr reference operator[](const difference_type value) const noexcept {
|
|
return {it[value].first, *it[value].second};
|
|
}
|
|
|
|
[[nodiscard]] constexpr reference operator*() const noexcept {
|
|
return operator[](0);
|
|
}
|
|
|
|
[[nodiscard]] constexpr pointer operator->() const noexcept {
|
|
return operator*();
|
|
}
|
|
|
|
template<typename Other>
|
|
[[nodiscard]] constexpr stl::ptrdiff_t operator-(const registry_storage_iterator<Other> &other) const noexcept {
|
|
return it - other.it;
|
|
}
|
|
|
|
template<typename Other>
|
|
[[nodiscard]] constexpr bool operator==(const registry_storage_iterator<Other> &other) const noexcept {
|
|
return it == other.it;
|
|
}
|
|
|
|
template<typename Other>
|
|
[[nodiscard]] constexpr auto operator<=>(const registry_storage_iterator<Other> &other) const noexcept {
|
|
return it <=> other.it;
|
|
}
|
|
|
|
private:
|
|
It it;
|
|
};
|
|
|
|
template<typename Allocator>
|
|
class registry_context {
|
|
using alloc_traits = stl::allocator_traits<Allocator>;
|
|
using allocator_type = alloc_traits::template rebind_alloc<stl::pair<const id_type, basic_any<0u>>>;
|
|
|
|
public:
|
|
explicit registry_context(const allocator_type &allocator)
|
|
: ctx{allocator} {}
|
|
|
|
void clear() noexcept {
|
|
ctx.clear();
|
|
}
|
|
|
|
template<typename Type, typename... Args>
|
|
Type &emplace_as(const id_type id, Args &&...args) {
|
|
return any_cast<Type &>(ctx.try_emplace(id, stl::in_place_type<Type>, stl::forward<Args>(args)...).first->second);
|
|
}
|
|
|
|
template<typename Type, typename... Args>
|
|
Type &emplace(Args &&...args) {
|
|
return emplace_as<Type>(type_id<Type>().hash(), stl::forward<Args>(args)...);
|
|
}
|
|
|
|
template<typename Type>
|
|
Type &insert_or_assign(const id_type id, Type &&value) {
|
|
return any_cast<stl::remove_cvref_t<Type> &>(ctx.insert_or_assign(id, stl::forward<Type>(value)).first->second);
|
|
}
|
|
|
|
template<typename Type>
|
|
Type &insert_or_assign(Type &&value) {
|
|
return insert_or_assign(type_id<Type>().hash(), stl::forward<Type>(value));
|
|
}
|
|
|
|
template<typename Type>
|
|
bool erase(const id_type id = type_id<Type>().hash()) {
|
|
const auto it = ctx.find(id);
|
|
return it != ctx.end() && it->second.info() == type_id<Type>() ? (ctx.erase(it), true) : false;
|
|
}
|
|
|
|
template<typename Type>
|
|
[[nodiscard]] const Type &get(const id_type id = type_id<Type>().hash()) const {
|
|
return any_cast<const Type &>(ctx.at(id));
|
|
}
|
|
|
|
template<typename Type>
|
|
[[nodiscard]] Type &get(const id_type id = type_id<Type>().hash()) {
|
|
return any_cast<Type &>(ctx.at(id));
|
|
}
|
|
|
|
template<typename Type>
|
|
[[nodiscard]] const Type *find(const id_type id = type_id<Type>().hash()) const {
|
|
const auto it = ctx.find(id);
|
|
return it != ctx.cend() ? any_cast<const Type>(&it->second) : nullptr;
|
|
}
|
|
|
|
template<typename Type>
|
|
[[nodiscard]] Type *find(const id_type id = type_id<Type>().hash()) {
|
|
const auto it = ctx.find(id);
|
|
return it != ctx.end() ? any_cast<Type>(&it->second) : nullptr;
|
|
}
|
|
|
|
template<typename Type>
|
|
[[nodiscard]] bool contains(const id_type id = type_id<Type>().hash()) const {
|
|
const auto it = ctx.find(id);
|
|
return it != ctx.cend() && it->second.info() == type_id<Type>();
|
|
}
|
|
|
|
private:
|
|
dense_map<id_type, basic_any<0u>, stl::identity, stl::equal_to<>, allocator_type> ctx;
|
|
};
|
|
|
|
} // namespace internal
|
|
/*! @endcond */
|
|
|
|
/**
|
|
* @brief Fast and reliable entity-component system.
|
|
* @tparam Entity A valid entity type.
|
|
* @tparam Allocator Type of allocator used to manage memory and elements.
|
|
*/
|
|
template<typename Entity, typename Allocator>
|
|
class basic_registry {
|
|
using base_type = basic_sparse_set<Entity, Allocator>;
|
|
using alloc_traits = stl::allocator_traits<Allocator>;
|
|
static_assert(stl::is_same_v<typename alloc_traits::value_type, Entity>, "Invalid value type");
|
|
// stl::shared_ptr because of its type erased allocator which is useful here
|
|
using pool_container_type = dense_map<id_type, stl::shared_ptr<base_type>, stl::identity, stl::equal_to<>, typename alloc_traits::template rebind_alloc<stl::pair<const id_type, stl::shared_ptr<base_type>>>>;
|
|
using group_container_type = dense_map<id_type, stl::shared_ptr<internal::group_descriptor>, stl::identity, stl::equal_to<>, typename alloc_traits::template rebind_alloc<stl::pair<const id_type, stl::shared_ptr<internal::group_descriptor>>>>;
|
|
using traits_type = entt_traits<Entity>;
|
|
|
|
template<cvref_unqualified Type>
|
|
[[nodiscard]] auto &assure([[maybe_unused]] const id_type id = type_hash<Type>::value()) {
|
|
if constexpr(stl::is_same_v<Type, entity_type>) {
|
|
ENTT_ASSERT(id == type_hash<Type>::value(), "User entity storage not allowed");
|
|
return entities;
|
|
} else {
|
|
using storage_type = storage_for_type<Type>;
|
|
|
|
if(auto it = pools.find(id); it != pools.cend()) {
|
|
ENTT_ASSERT(it->second->info() == type_id<Type>(), "Unexpected type");
|
|
return static_cast<storage_type &>(*it->second);
|
|
}
|
|
|
|
typename pool_container_type::mapped_type cpool = stl::allocate_shared<storage_type>(get_allocator(), get_allocator());
|
|
pools.emplace(id, cpool);
|
|
cpool->bind(*this);
|
|
|
|
return static_cast<storage_type &>(*cpool);
|
|
}
|
|
}
|
|
|
|
template<cvref_unqualified Type>
|
|
[[nodiscard]] const auto *assure([[maybe_unused]] const id_type id = type_hash<Type>::value()) const {
|
|
if constexpr(stl::is_same_v<Type, entity_type>) {
|
|
ENTT_ASSERT(id == type_hash<Type>::value(), "User entity storage not allowed");
|
|
return &entities;
|
|
} else {
|
|
if(const auto it = pools.find(id); it != pools.cend()) {
|
|
ENTT_ASSERT(it->second->info() == type_id<Type>(), "Unexpected type");
|
|
return static_cast<const storage_for_type<Type> *>(it->second.get());
|
|
}
|
|
|
|
return static_cast<const storage_for_type<Type> *>(nullptr);
|
|
}
|
|
}
|
|
|
|
void rebind() {
|
|
entities.bind(*this);
|
|
|
|
for(auto &&curr: pools) {
|
|
curr.second->bind(*this);
|
|
}
|
|
}
|
|
|
|
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 Common type among all storage types. */
|
|
using common_type = base_type;
|
|
/*! @brief Context type. */
|
|
using context = internal::registry_context<allocator_type>;
|
|
/*! @brief Iterable registry type. */
|
|
using iterable = iterable_adaptor<internal::registry_storage_iterator<typename pool_container_type::iterator>>;
|
|
/*! @brief Constant iterable registry type. */
|
|
using const_iterable = iterable_adaptor<internal::registry_storage_iterator<typename pool_container_type::const_iterator>>;
|
|
|
|
/**
|
|
* @copybrief storage_for
|
|
* @tparam Type Storage value type, eventually const.
|
|
*/
|
|
template<typename Type>
|
|
using storage_for_type = storage_for<Type, Entity, typename alloc_traits::template rebind_alloc<stl::remove_const_t<Type>>>::type;
|
|
|
|
/*! @brief Default constructor. */
|
|
basic_registry()
|
|
: basic_registry{allocator_type{}} {}
|
|
|
|
/**
|
|
* @brief Constructs an empty registry with a given allocator.
|
|
* @param allocator The allocator to use.
|
|
*/
|
|
explicit basic_registry(const allocator_type &allocator)
|
|
: basic_registry{0u, allocator} {}
|
|
|
|
/**
|
|
* @brief Allocates enough memory upon construction to store `count` pools.
|
|
* @param count The number of pools to allocate memory for.
|
|
* @param allocator The allocator to use.
|
|
*/
|
|
basic_registry(const size_type count, const allocator_type &allocator = allocator_type{})
|
|
: vars{allocator},
|
|
pools{allocator},
|
|
groups{allocator},
|
|
entities{allocator} {
|
|
pools.reserve(count);
|
|
rebind();
|
|
}
|
|
|
|
/*! @brief Default copy constructor, deleted on purpose. */
|
|
basic_registry(const basic_registry &) = delete;
|
|
|
|
/**
|
|
* @brief Move constructor.
|
|
* @param other The instance to move from.
|
|
*/
|
|
basic_registry(basic_registry &&other) noexcept
|
|
: vars{stl::move(other.vars)},
|
|
pools{stl::move(other.pools)},
|
|
groups{stl::move(other.groups)},
|
|
entities{stl::move(other.entities)} {
|
|
rebind();
|
|
}
|
|
|
|
/*! @brief Default destructor. */
|
|
~basic_registry() = default;
|
|
|
|
/**
|
|
* @brief Default copy assignment operator, deleted on purpose.
|
|
* @return This mixin.
|
|
*/
|
|
basic_registry &operator=(const basic_registry &) = delete;
|
|
|
|
/**
|
|
* @brief Move assignment operator.
|
|
* @param other The instance to move from.
|
|
* @return This registry.
|
|
*/
|
|
basic_registry &operator=(basic_registry &&other) noexcept {
|
|
swap(other);
|
|
return *this;
|
|
}
|
|
|
|
/**
|
|
* @brief Exchanges the contents with those of a given registry.
|
|
* @param other Registry to exchange the content with.
|
|
*/
|
|
void swap(basic_registry &other) noexcept {
|
|
using stl::swap;
|
|
|
|
swap(vars, other.vars);
|
|
swap(pools, other.pools);
|
|
swap(groups, other.groups);
|
|
swap(entities, other.entities);
|
|
|
|
rebind();
|
|
other.rebind();
|
|
}
|
|
|
|
/**
|
|
* @brief Returns the associated allocator.
|
|
* @return The associated allocator.
|
|
*/
|
|
[[nodiscard]] constexpr allocator_type get_allocator() const noexcept {
|
|
return entities.get_allocator();
|
|
}
|
|
|
|
/**
|
|
* @brief Returns an iterable object to use to _visit_ a registry.
|
|
*
|
|
* The iterable object returns a pair that contains the name and a reference
|
|
* to the current storage.
|
|
*
|
|
* @return An iterable object to use to _visit_ the registry.
|
|
*/
|
|
[[nodiscard]] iterable storage() noexcept {
|
|
return iterable{pools.begin(), pools.end()};
|
|
}
|
|
|
|
/*! @copydoc storage */
|
|
[[nodiscard]] const_iterable storage() const noexcept {
|
|
return const_iterable{pools.cbegin(), pools.cend()};
|
|
}
|
|
|
|
/**
|
|
* @brief Finds the storage associated with a given name, if any.
|
|
* @param id Name used to map the storage within the registry.
|
|
* @return A pointer to the storage if it exists, a null pointer otherwise.
|
|
*/
|
|
[[nodiscard]] common_type *storage(const id_type id) {
|
|
return const_cast<common_type *>(stl::as_const(*this).storage(id));
|
|
}
|
|
|
|
/**
|
|
* @brief Finds the storage associated with a given name, if any.
|
|
* @param id Name used to map the storage within the registry.
|
|
* @return A pointer to the storage if it exists, a null pointer otherwise.
|
|
*/
|
|
[[nodiscard]] const common_type *storage(const id_type id) const {
|
|
const auto it = pools.find(id);
|
|
return it == pools.cend() ? nullptr : it->second.get();
|
|
}
|
|
|
|
/**
|
|
* @brief Returns the storage for a given element type.
|
|
* @tparam Type Type of element of which to return the storage.
|
|
* @param id Optional name used to map the storage within the registry.
|
|
* @return The storage for the given element type.
|
|
*/
|
|
template<typename Type>
|
|
storage_for_type<Type> &storage(const id_type id = type_hash<Type>::value()) {
|
|
return assure<stl::remove_const_t<Type>>(id);
|
|
}
|
|
|
|
/**
|
|
* @brief Returns the storage for a given element type, if any.
|
|
* @tparam Type Type of element of which to return the storage.
|
|
* @param id Optional name used to map the storage within the registry.
|
|
* @return The storage for the given element type.
|
|
*/
|
|
template<typename Type>
|
|
[[nodiscard]] const storage_for_type<Type> *storage(const id_type id = type_hash<Type>::value()) const {
|
|
return assure<stl::remove_const_t<Type>>(id);
|
|
}
|
|
|
|
/**
|
|
* @brief Discards the storage associated with a given name, if any.
|
|
* @param id Name used to map the storage within the registry.
|
|
* @return True in case of success, false otherwise.
|
|
*/
|
|
bool reset(const id_type id) {
|
|
ENTT_ASSERT(id != type_hash<entity_type>::value(), "Cannot reset entity storage");
|
|
return !(pools.erase(id) == 0u);
|
|
}
|
|
|
|
/**
|
|
* @brief Checks if an identifier refers to a valid entity.
|
|
* @param entt An identifier, either valid or not.
|
|
* @return True if the identifier is valid, false otherwise.
|
|
*/
|
|
[[nodiscard]] bool valid(const entity_type entt) const {
|
|
return static_cast<size_type>(entities.find(entt).index()) < entities.free_list();
|
|
}
|
|
|
|
/**
|
|
* @brief Returns the actual version for an identifier.
|
|
* @param entt A valid identifier.
|
|
* @return The version for the given identifier if valid, the tombstone
|
|
* version otherwise.
|
|
*/
|
|
[[nodiscard]] version_type current(const entity_type entt) const {
|
|
return entities.current(entt);
|
|
}
|
|
|
|
/**
|
|
* @brief Creates a new entity or recycles a destroyed one.
|
|
* @return A valid identifier.
|
|
*/
|
|
[[nodiscard]] entity_type create() {
|
|
return entities.generate();
|
|
}
|
|
|
|
/**
|
|
* @copybrief create
|
|
*
|
|
* If the requested entity isn't in use, the suggested identifier is used.
|
|
* Otherwise, a new identifier is generated.
|
|
*
|
|
* @param hint Required identifier.
|
|
* @return A valid identifier.
|
|
*/
|
|
[[nodiscard]] entity_type create(const entity_type hint) {
|
|
return entities.generate(hint);
|
|
}
|
|
|
|
/**
|
|
* @brief Assigns each element in a range an identifier.
|
|
*
|
|
* @sa create
|
|
*
|
|
* @tparam It Type of output iterator.
|
|
* @param first An iterator to the first element of the range to generate.
|
|
* @param last An iterator past the last element of the range to generate.
|
|
*/
|
|
template<stl::output_iterator<entity_type> It>
|
|
void create(It first, It last) {
|
|
entities.generate(stl::move(first), stl::move(last));
|
|
}
|
|
|
|
/**
|
|
* @brief Destroys an entity and releases its identifier.
|
|
*
|
|
* @warning
|
|
* Adding or removing elements to an entity that is being destroyed can
|
|
* result in undefined behavior.
|
|
*
|
|
* @param entt A valid identifier.
|
|
* @return The version of the recycled entity.
|
|
*/
|
|
version_type destroy(const entity_type entt) {
|
|
for(size_type pos = pools.size(); pos != 0u; --pos) {
|
|
pools.begin()[static_cast<pool_container_type::difference_type>(pos - 1u)].second->remove(entt);
|
|
}
|
|
|
|
entities.erase(entt);
|
|
return entities.current(entt);
|
|
}
|
|
|
|
/**
|
|
* @brief Destroys an entity and releases its identifier.
|
|
*
|
|
* The suggested version or the valid version closest to the suggested one
|
|
* is used instead of the implicitly generated version.
|
|
*
|
|
* @sa destroy
|
|
*
|
|
* @param entt A valid identifier.
|
|
* @param version A desired version upon destruction.
|
|
* @return The version actually assigned to the entity.
|
|
*/
|
|
version_type destroy(const entity_type entt, const version_type version) {
|
|
destroy(entt);
|
|
const auto elem = traits_type::construct(traits_type::to_entity(entt), version);
|
|
return entities.bump((elem == tombstone) ? traits_type::next(elem) : elem);
|
|
}
|
|
|
|
/**
|
|
* @brief Destroys all entities in a range and releases their identifiers.
|
|
*
|
|
* @sa destroy
|
|
*
|
|
* @param first An iterator to the first element of the range of entities.
|
|
* @param last An iterator past the last element of the range of entities.
|
|
*/
|
|
void destroy(stl::input_iterator auto first, stl::input_iterator auto last) {
|
|
const auto to = entities.sort_as(first, last);
|
|
const auto from = entities.cend() - static_cast<common_type::difference_type>(entities.free_list());
|
|
|
|
for(auto &&curr: pools) {
|
|
curr.second->remove(from, to);
|
|
}
|
|
|
|
entities.erase(from, to);
|
|
}
|
|
|
|
/**
|
|
* @brief Assigns the given element to an entity.
|
|
*
|
|
* The element must have a proper constructor or be of aggregate type.
|
|
*
|
|
* @warning
|
|
* Attempting to assign an element to an entity that already owns it results
|
|
* in undefined behavior.
|
|
*
|
|
* @tparam Type Type of element to create.
|
|
* @tparam Args Types of arguments to use to construct the element.
|
|
* @param entt A valid identifier.
|
|
* @param args Parameters to use to initialize the element.
|
|
* @return A reference to the newly created element.
|
|
*/
|
|
template<typename Type, typename... Args>
|
|
decltype(auto) emplace(const entity_type entt, Args &&...args) {
|
|
ENTT_ASSERT(valid(entt), "Invalid entity");
|
|
return assure<Type>().emplace(entt, stl::forward<Args>(args)...);
|
|
}
|
|
|
|
/**
|
|
* @brief Assigns each entity in a range the given element.
|
|
*
|
|
* @sa emplace
|
|
*
|
|
* @tparam Type Type of element to create.
|
|
* @param first An iterator to the first element of the range of entities.
|
|
* @param last An iterator past the last element of the range of entities.
|
|
* @param value An instance of the element to assign.
|
|
*/
|
|
template<typename Type>
|
|
void insert(stl::input_iterator auto first, stl::input_iterator auto last, const Type &value = {}) {
|
|
ENTT_ASSERT(stl::all_of(first, last, [this](const auto entt) { return valid(entt); }), "Invalid entity");
|
|
assure<Type>().insert(stl::move(first), stl::move(last), value);
|
|
}
|
|
|
|
/**
|
|
* @brief Assigns each entity in a range the given elements.
|
|
*
|
|
* @sa emplace
|
|
*
|
|
* @tparam Type Type of element to create.
|
|
* @tparam EIt Type of input iterator.
|
|
* @tparam CIt Type of input iterator.
|
|
* @param first An iterator to the first element of the range of entities.
|
|
* @param last An iterator past the last element of the range of entities.
|
|
* @param from An iterator to the first element of the range of elements.
|
|
*/
|
|
template<typename Type, typename EIt, typename CIt>
|
|
requires stl::same_as<typename stl::iterator_traits<CIt>::value_type, Type>
|
|
void insert(EIt first, EIt last, CIt from) {
|
|
ENTT_ASSERT(stl::all_of(first, last, [this](const auto entt) { return valid(entt); }), "Invalid entity");
|
|
assure<Type>().insert(first, last, from);
|
|
}
|
|
|
|
/**
|
|
* @brief Assigns or replaces the given element for an entity.
|
|
*
|
|
* @sa emplace
|
|
* @sa replace
|
|
*
|
|
* @tparam Type Type of element to assign or replace.
|
|
* @tparam Args Types of arguments to use to construct the element.
|
|
* @param entt A valid identifier.
|
|
* @param args Parameters to use to initialize the element.
|
|
* @return A reference to the newly created element.
|
|
*/
|
|
template<typename Type, typename... Args>
|
|
decltype(auto) emplace_or_replace(const entity_type entt, Args &&...args) {
|
|
auto &cpool = assure<Type>();
|
|
ENTT_ASSERT(valid(entt), "Invalid entity");
|
|
return cpool.contains(entt) ? cpool.patch(entt, [&args...](auto &...curr) { ((curr = Type{stl::forward<Args>(args)...}), ...); }) : cpool.emplace(entt, stl::forward<Args>(args)...);
|
|
}
|
|
|
|
/**
|
|
* @brief Patches the given element for an entity.
|
|
*
|
|
* The signature of the function should be equivalent to the following:
|
|
*
|
|
* @code{.cpp}
|
|
* void(Type &);
|
|
* @endcode
|
|
*
|
|
* @warning
|
|
* Attempting to patch an element of an entity that doesn't own it results
|
|
* in undefined behavior.
|
|
*
|
|
* @tparam Type Type of element to patch.
|
|
* @tparam Func Types of the function objects to invoke.
|
|
* @param entt A valid identifier.
|
|
* @param func Valid function objects.
|
|
* @return A reference to the patched element.
|
|
*/
|
|
template<typename Type, typename... Func>
|
|
decltype(auto) patch(const entity_type entt, Func &&...func) {
|
|
return assure<Type>().patch(entt, stl::forward<Func>(func)...);
|
|
}
|
|
|
|
/**
|
|
* @brief Replaces the given element for an entity.
|
|
*
|
|
* The element must have a proper constructor or be of aggregate type.
|
|
*
|
|
* @warning
|
|
* Attempting to replace an element of an entity that doesn't own it results
|
|
* in undefined behavior.
|
|
*
|
|
* @tparam Type Type of element to replace.
|
|
* @tparam Args Types of arguments to use to construct the element.
|
|
* @param entt A valid identifier.
|
|
* @param args Parameters to use to initialize the element.
|
|
* @return A reference to the element being replaced.
|
|
*/
|
|
template<typename Type, typename... Args>
|
|
decltype(auto) replace(const entity_type entt, Args &&...args) {
|
|
return patch<Type>(entt, [&args...](auto &...curr) { ((curr = Type{stl::forward<Args>(args)...}), ...); });
|
|
}
|
|
|
|
/**
|
|
* @brief Removes the given elements from an entity.
|
|
* @tparam Type Type of element to remove.
|
|
* @tparam Other Other types of elements to remove.
|
|
* @param entt A valid identifier.
|
|
* @return The number of elements actually removed.
|
|
*/
|
|
template<typename Type, typename... Other>
|
|
size_type remove(const entity_type entt) {
|
|
return (assure<Type>().remove(entt) + ... + assure<Other>().remove(entt));
|
|
}
|
|
|
|
/**
|
|
* @brief Removes the given elements from all the entities in a range.
|
|
*
|
|
* @sa remove
|
|
*
|
|
* @tparam Type Type of element to remove.
|
|
* @tparam Other Other types of elements to remove.
|
|
* @tparam It Type of input iterator.
|
|
* @param first An iterator to the first element of the range of entities.
|
|
* @param last An iterator past the last element of the range of entities.
|
|
* @return The number of elements actually removed.
|
|
*/
|
|
template<typename Type, typename... Other, stl::input_iterator It>
|
|
size_type remove(It first, It last) {
|
|
size_type count{};
|
|
|
|
if constexpr(stl::is_same_v<It, typename common_type::iterator>) {
|
|
stl::array cpools{static_cast<common_type *>(&assure<Type>()), static_cast<common_type *>(&assure<Other>())...};
|
|
|
|
for(auto from = cpools.begin(), to = cpools.end(); from != to; ++from) {
|
|
if constexpr(sizeof...(Other) != 0u) {
|
|
if((*from)->data() == first.data()) {
|
|
stl::swap((*from), cpools.back());
|
|
}
|
|
}
|
|
|
|
count += (*from)->remove(first, last);
|
|
}
|
|
|
|
} else {
|
|
for(auto cpools = stl::forward_as_tuple(assure<Type>(), assure<Other>()...); first != last; ++first) {
|
|
count += stl::apply([entt = *first](auto &...curr) { return (curr.remove(entt) + ... + 0u); }, cpools);
|
|
}
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
/**
|
|
* @brief Erases the given elements from an entity.
|
|
*
|
|
* @warning
|
|
* Attempting to erase an element from an entity that doesn't own it results
|
|
* in undefined behavior.
|
|
*
|
|
* @tparam Type Types of elements to erase.
|
|
* @tparam Other Other types of elements to erase.
|
|
* @param entt A valid identifier.
|
|
*/
|
|
template<typename Type, typename... Other>
|
|
void erase(const entity_type entt) {
|
|
(assure<Type>().erase(entt), (assure<Other>().erase(entt), ...));
|
|
}
|
|
|
|
/**
|
|
* @brief Erases the given elements from all the entities in a range.
|
|
*
|
|
* @sa erase
|
|
*
|
|
* @tparam Type Types of elements to erase.
|
|
* @tparam Other Other types of elements to erase.
|
|
* @tparam It Type of input iterator.
|
|
* @param first An iterator to the first element of the range of entities.
|
|
* @param last An iterator past the last element of the range of entities.
|
|
*/
|
|
template<typename Type, typename... Other, stl::input_iterator It>
|
|
void erase(It first, It last) {
|
|
if constexpr(stl::is_same_v<It, typename common_type::iterator>) {
|
|
stl::array cpools{static_cast<common_type *>(&assure<Type>()), static_cast<common_type *>(&assure<Other>())...};
|
|
|
|
for(auto from = cpools.begin(), to = cpools.end(); from != to; ++from) {
|
|
if constexpr(sizeof...(Other) != 0u) {
|
|
if((*from)->data() == first.data()) {
|
|
stl::swap(*from, cpools.back());
|
|
}
|
|
}
|
|
|
|
(*from)->erase(first, last);
|
|
}
|
|
} else {
|
|
for(auto cpools = stl::forward_as_tuple(assure<Type>(), assure<Other>()...); first != last; ++first) {
|
|
stl::apply([entt = *first](auto &...curr) { (curr.erase(entt), ...); }, cpools);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Erases elements satisfying specific criteria from an entity.
|
|
*
|
|
* The function type is equivalent to:
|
|
*
|
|
* @code{.cpp}
|
|
* void(const id_type, typename basic_registry<Entity>::common_type &);
|
|
* @endcode
|
|
*
|
|
* Only storage where the entity exists are passed to the function.
|
|
*
|
|
* @tparam Func Type of the function object to invoke.
|
|
* @param entt A valid identifier.
|
|
* @param func A valid function object.
|
|
*/
|
|
template<typename Func>
|
|
void erase_if(const entity_type entt, Func func) {
|
|
for(auto [id, cpool]: storage()) {
|
|
if(cpool.contains(entt) && func(id, stl::as_const(cpool))) {
|
|
cpool.erase(entt);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Removes all tombstones from a registry or only the pools for the
|
|
* given elements.
|
|
* @tparam Type Types of elements for which to clear all tombstones.
|
|
*/
|
|
template<typename... Type>
|
|
void compact() {
|
|
if constexpr(sizeof...(Type) == 0u) {
|
|
for(auto &&curr: pools) {
|
|
curr.second->compact();
|
|
}
|
|
} else {
|
|
(assure<Type>().compact(), ...);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Check if an entity is part of all the given storage.
|
|
* @tparam Type Type of storage to check for.
|
|
* @param entt A valid identifier.
|
|
* @return True if the entity is part of all the storage, false otherwise.
|
|
*/
|
|
template<typename... Type>
|
|
[[nodiscard]] bool all_of([[maybe_unused]] const entity_type entt) const {
|
|
if constexpr(sizeof...(Type) == 1u) {
|
|
auto *cpool = assure<stl::remove_const_t<Type>...>();
|
|
return cpool && cpool->contains(entt);
|
|
} else {
|
|
return (all_of<Type>(entt) && ...);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Check if an entity is part of at least one given storage.
|
|
* @tparam Type Type of storage to check for.
|
|
* @param entt A valid identifier.
|
|
* @return True if the entity is part of at least one storage, false
|
|
* otherwise.
|
|
*/
|
|
template<typename... Type>
|
|
[[nodiscard]] bool any_of([[maybe_unused]] const entity_type entt) const {
|
|
return (all_of<Type>(entt) || ...);
|
|
}
|
|
|
|
/**
|
|
* @brief Returns references to the given elements for an entity.
|
|
*
|
|
* @warning
|
|
* Attempting to get an element from an entity that doesn't own it results
|
|
* in undefined behavior.
|
|
*
|
|
* @tparam Type Types of elements to get.
|
|
* @param entt A valid identifier.
|
|
* @return References to the elements owned by the entity.
|
|
*/
|
|
template<typename... Type>
|
|
[[nodiscard]] decltype(auto) get([[maybe_unused]] const entity_type entt) const {
|
|
if constexpr(sizeof...(Type) == 1u) {
|
|
return (assure<stl::remove_const_t<Type>>()->get(entt), ...);
|
|
} else {
|
|
return stl::forward_as_tuple(get<Type>(entt)...);
|
|
}
|
|
}
|
|
|
|
/*! @copydoc get */
|
|
template<typename... Type>
|
|
[[nodiscard]] decltype(auto) get([[maybe_unused]] const entity_type entt) {
|
|
if constexpr(sizeof...(Type) == 1u) {
|
|
return (static_cast<storage_for_type<Type> &>(assure<stl::remove_const_t<Type>>()).get(entt), ...);
|
|
} else {
|
|
return stl::forward_as_tuple(get<Type>(entt)...);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Returns a reference to the given element for an entity.
|
|
*
|
|
* In case the entity doesn't own the element, the parameters provided are
|
|
* used to construct it.
|
|
*
|
|
* @sa get
|
|
* @sa emplace
|
|
*
|
|
* @tparam Type Type of element to get.
|
|
* @tparam Args Types of arguments to use to construct the element.
|
|
* @param entt A valid identifier.
|
|
* @param args Parameters to use to initialize the element.
|
|
* @return Reference to the element owned by the entity.
|
|
*/
|
|
template<typename Type, typename... Args>
|
|
[[nodiscard]] decltype(auto) get_or_emplace(const entity_type entt, Args &&...args) {
|
|
auto &cpool = assure<Type>();
|
|
ENTT_ASSERT(valid(entt), "Invalid entity");
|
|
return cpool.contains(entt) ? cpool.get(entt) : cpool.emplace(entt, stl::forward<Args>(args)...);
|
|
}
|
|
|
|
/**
|
|
* @brief Returns pointers to the given elements for an entity.
|
|
*
|
|
* @note
|
|
* The registry retains ownership of the pointed-to elements.
|
|
*
|
|
* @tparam Type Types of elements to get.
|
|
* @param entt A valid identifier.
|
|
* @return Pointers to the elements owned by the entity.
|
|
*/
|
|
template<typename... Type>
|
|
[[nodiscard]] auto try_get([[maybe_unused]] const entity_type entt) const {
|
|
if constexpr(sizeof...(Type) == 1u) {
|
|
const auto *cpool = assure<stl::remove_const_t<Type>...>();
|
|
return (cpool && cpool->contains(entt)) ? stl::addressof(cpool->get(entt)) : nullptr;
|
|
} else {
|
|
return stl::make_tuple(try_get<Type>(entt)...);
|
|
}
|
|
}
|
|
|
|
/*! @copydoc try_get */
|
|
template<typename... Type>
|
|
[[nodiscard]] auto try_get([[maybe_unused]] const entity_type entt) {
|
|
if constexpr(sizeof...(Type) == 1u) {
|
|
return (const_cast<Type *>(stl::as_const(*this).template try_get<Type>(entt)), ...);
|
|
} else {
|
|
return stl::make_tuple(try_get<Type>(entt)...);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Clears a whole registry or the pools for the given elements.
|
|
* @tparam Type Types of elements to remove from their entities.
|
|
*/
|
|
template<typename... Type>
|
|
void clear() {
|
|
if constexpr(sizeof...(Type) == 0u) {
|
|
for(size_type pos = pools.size(); pos; --pos) {
|
|
pools.begin()[static_cast<pool_container_type::difference_type>(pos - 1u)].second->clear();
|
|
}
|
|
|
|
const auto elem = entities.each();
|
|
entities.erase(elem.begin().base(), elem.end().base());
|
|
} else {
|
|
(assure<Type>().clear(), ...);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Checks if an entity has elements assigned.
|
|
* @param entt A valid identifier.
|
|
* @return True if the entity has no elements assigned, false otherwise.
|
|
*/
|
|
[[nodiscard]] bool orphan(const entity_type entt) const {
|
|
return stl::none_of(pools.cbegin(), pools.cend(), [entt](auto &&curr) { return curr.second->contains(entt); });
|
|
}
|
|
|
|
/**
|
|
* @brief Returns a sink object for the given element.
|
|
*
|
|
* Use this function to receive notifications whenever a new instance of the
|
|
* given element is created and assigned to an entity.<br/>
|
|
* The function type for a listener is equivalent to:
|
|
*
|
|
* @code{.cpp}
|
|
* void(basic_registry<Entity> &, Entity);
|
|
* @endcode
|
|
*
|
|
* Listeners are invoked **after** assigning the element to the entity.
|
|
*
|
|
* @sa sink
|
|
*
|
|
* @tparam Type Type of element of which to get the sink.
|
|
* @param id Optional name used to map the storage within the registry.
|
|
* @return A temporary sink object.
|
|
*/
|
|
template<typename Type>
|
|
[[nodiscard]] auto on_construct(const id_type id = type_hash<Type>::value()) {
|
|
return assure<Type>(id).on_construct();
|
|
}
|
|
|
|
/**
|
|
* @brief Returns a sink object for the given element.
|
|
*
|
|
* Use this function to receive notifications whenever an instance of the
|
|
* given element is explicitly updated.<br/>
|
|
* The function type for a listener is equivalent to:
|
|
*
|
|
* @code{.cpp}
|
|
* void(basic_registry<Entity> &, Entity);
|
|
* @endcode
|
|
*
|
|
* Listeners are invoked **after** updating the element.
|
|
*
|
|
* @sa sink
|
|
*
|
|
* @tparam Type Type of element of which to get the sink.
|
|
* @param id Optional name used to map the storage within the registry.
|
|
* @return A temporary sink object.
|
|
*/
|
|
template<typename Type>
|
|
[[nodiscard]] auto on_update(const id_type id = type_hash<Type>::value()) {
|
|
return assure<Type>(id).on_update();
|
|
}
|
|
|
|
/**
|
|
* @brief Returns a sink object for the given element.
|
|
*
|
|
* Use this function to receive notifications whenever an instance of the
|
|
* given element is removed from an entity and thus destroyed.<br/>
|
|
* The function type for a listener is equivalent to:
|
|
*
|
|
* @code{.cpp}
|
|
* void(basic_registry<Entity> &, Entity);
|
|
* @endcode
|
|
*
|
|
* Listeners are invoked **before** removing the element from the entity.
|
|
*
|
|
* @sa sink
|
|
*
|
|
* @tparam Type Type of element of which to get the sink.
|
|
* @param id Optional name used to map the storage within the registry.
|
|
* @return A temporary sink object.
|
|
*/
|
|
template<typename Type>
|
|
[[nodiscard]] auto on_destroy(const id_type id = type_hash<Type>::value()) {
|
|
return assure<Type>(id).on_destroy();
|
|
}
|
|
|
|
/**
|
|
* @brief Returns a view for the given elements.
|
|
* @tparam Type Type of element used to construct the view.
|
|
* @tparam Other Other types of elements used to construct the view.
|
|
* @tparam Exclude Types of elements used to filter the view.
|
|
* @return A newly created view.
|
|
*/
|
|
template<typename Type, typename... Other, typename... Exclude>
|
|
[[nodiscard]] basic_view<get_t<storage_for_type<const Type>, storage_for_type<const Other>...>, exclude_t<storage_for_type<const Exclude>...>>
|
|
view(exclude_t<Exclude...> = exclude_t{}) const {
|
|
basic_view<get_t<storage_for_type<const Type>, storage_for_type<const Other>...>, exclude_t<storage_for_type<const Exclude>...>> elem{};
|
|
[&elem](const auto *...curr) { ((curr ? elem.storage(*curr) : void()), ...); }(assure<stl::remove_const_t<Exclude>>()..., assure<stl::remove_const_t<Other>>()..., assure<stl::remove_const_t<Type>>());
|
|
return elem;
|
|
}
|
|
|
|
/*! @copydoc view */
|
|
template<typename Type, typename... Other, typename... Exclude>
|
|
[[nodiscard]] basic_view<get_t<storage_for_type<Type>, storage_for_type<Other>...>, exclude_t<storage_for_type<Exclude>...>>
|
|
view(exclude_t<Exclude...> = exclude_t{}) {
|
|
return {assure<stl::remove_const_t<Type>>(), assure<stl::remove_const_t<Other>>()..., assure<stl::remove_const_t<Exclude>>()...};
|
|
}
|
|
|
|
/**
|
|
* @brief Returns a group for the given elements.
|
|
* @tparam Owned Types of storage _owned_ by the group.
|
|
* @tparam Get Types of storage _observed_ by the group, if any.
|
|
* @tparam Exclude Types of storage used to filter the group, if any.
|
|
* @return A newly created group.
|
|
*/
|
|
template<typename... Owned, typename... Get, typename... Exclude>
|
|
basic_group<owned_t<storage_for_type<Owned>...>, get_t<storage_for_type<Get>...>, exclude_t<storage_for_type<Exclude>...>>
|
|
group(get_t<Get...> = get_t{}, exclude_t<Exclude...> = exclude_t{}) {
|
|
using group_type = basic_group<owned_t<storage_for_type<Owned>...>, get_t<storage_for_type<Get>...>, exclude_t<storage_for_type<Exclude>...>>;
|
|
using handler_type = group_type::handler;
|
|
|
|
if(auto it = groups.find(group_type::group_id()); it != groups.cend()) {
|
|
return {*stl::static_pointer_cast<handler_type>(it->second)};
|
|
}
|
|
|
|
stl::shared_ptr<handler_type> handler{};
|
|
|
|
if constexpr(sizeof...(Owned) == 0u) {
|
|
handler = stl::allocate_shared<handler_type>(get_allocator(), get_allocator(), stl::forward_as_tuple(assure<stl::remove_const_t<Get>>()...), stl::forward_as_tuple(assure<stl::remove_const_t<Exclude>>()...));
|
|
} else {
|
|
handler = stl::allocate_shared<handler_type>(get_allocator(), stl::forward_as_tuple(assure<stl::remove_const_t<Owned>>()..., assure<stl::remove_const_t<Get>>()...), stl::forward_as_tuple(assure<stl::remove_const_t<Exclude>>()...));
|
|
ENTT_ASSERT(stl::all_of(groups.cbegin(), groups.cend(), [](const auto &data) { return !(data.second->owned(type_id<Owned>().hash()) || ...); }), "Conflicting groups");
|
|
}
|
|
|
|
groups.emplace(group_type::group_id(), handler);
|
|
return {*handler};
|
|
}
|
|
|
|
/*! @copydoc group */
|
|
template<typename... Owned, typename... Get, typename... Exclude>
|
|
[[nodiscard]] basic_group<owned_t<storage_for_type<const Owned>...>, get_t<storage_for_type<const Get>...>, exclude_t<storage_for_type<const Exclude>...>>
|
|
group_if_exists(get_t<Get...> = get_t{}, exclude_t<Exclude...> = exclude_t{}) const {
|
|
using group_type = basic_group<owned_t<storage_for_type<const Owned>...>, get_t<storage_for_type<const Get>...>, exclude_t<storage_for_type<const Exclude>...>>;
|
|
using handler_type = group_type::handler;
|
|
|
|
if(auto it = groups.find(group_type::group_id()); it != groups.cend()) {
|
|
return {*stl::static_pointer_cast<handler_type>(it->second)};
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
/**
|
|
* @brief Checks whether the given elements belong to any group.
|
|
* @tparam Type Types of elements in which one is interested.
|
|
* @return True if the pools of the given elements are _free_, false
|
|
* otherwise.
|
|
*/
|
|
template<typename... Type>
|
|
[[nodiscard]] bool owned() const {
|
|
return stl::any_of(groups.cbegin(), groups.cend(), [](auto &&data) { return (data.second->owned(type_id<Type>().hash()) || ...); });
|
|
}
|
|
|
|
/**
|
|
* @brief Sorts the elements of a given element.
|
|
*
|
|
* The comparison function object returns `true` if the first element is
|
|
* _less_ than the second one, `false` otherwise. Its signature is also
|
|
* equivalent to one of the following:
|
|
*
|
|
* @code{.cpp}
|
|
* bool(const Entity, const Entity);
|
|
* bool(const Type &, const Type &);
|
|
* @endcode
|
|
*
|
|
* Moreover, it shall induce a _strict weak ordering_ on the values.<br/>
|
|
* The sort function object offers an `operator()` that accepts:
|
|
*
|
|
* * An iterator to the first element of the range to sort.
|
|
* * An iterator past the last element of the range to sort.
|
|
* * A comparison function object to use to compare the elements.
|
|
*
|
|
* The comparison function object hasn't necessarily the type of the one
|
|
* passed along with the other parameters to this member function.
|
|
*
|
|
* @warning
|
|
* Pools of elements owned by a group cannot be sorted.
|
|
*
|
|
* @tparam Type Type of elements to sort.
|
|
* @tparam Compare Type of comparison function object.
|
|
* @tparam Sort Type of sort function object.
|
|
* @tparam Args Types of arguments to forward to the sort function object.
|
|
* @param compare A valid comparison function object.
|
|
* @param algo A valid sort function object.
|
|
* @param args Arguments to forward to the sort function object, if any.
|
|
*/
|
|
template<typename Type, typename Compare, typename Sort = std_sort, typename... Args>
|
|
void sort(Compare compare, Sort algo = Sort{}, Args &&...args) {
|
|
ENTT_ASSERT(!owned<Type>(), "Cannot sort owned storage");
|
|
auto &cpool = assure<Type>();
|
|
|
|
if constexpr(stl::is_invocable_v<Compare, decltype(cpool.get({})), decltype(cpool.get({}))>) {
|
|
auto comp = [&cpool, compare = stl::move(compare)](const auto lhs, const auto rhs) { return compare(stl::as_const(cpool.get(lhs)), stl::as_const(cpool.get(rhs))); };
|
|
cpool.sort(stl::move(comp), stl::move(algo), stl::forward<Args>(args)...);
|
|
} else {
|
|
cpool.sort(stl::move(compare), stl::move(algo), stl::forward<Args>(args)...);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Sorts two pools of elements in the same way.
|
|
*
|
|
* Entities and elements in `To` which are part of both storage are sorted
|
|
* internally with the order they have in `From`. The others follow in no
|
|
* particular order.
|
|
*
|
|
* @warning
|
|
* Pools of elements owned by a group cannot be sorted.
|
|
*
|
|
* @tparam To Type of elements to sort.
|
|
* @tparam From Type of elements to use to sort.
|
|
*/
|
|
template<typename To, typename From>
|
|
void sort() {
|
|
ENTT_ASSERT(!owned<To>(), "Cannot sort owned storage");
|
|
const base_type &cpool = assure<From>();
|
|
assure<To>().sort_as(cpool.begin(), cpool.end());
|
|
}
|
|
|
|
/**
|
|
* @brief Returns the context object, that is, a general purpose container.
|
|
* @return The context object, that is, a general purpose container.
|
|
*/
|
|
[[nodiscard]] context &ctx() noexcept {
|
|
return vars;
|
|
}
|
|
|
|
/*! @copydoc ctx */
|
|
[[nodiscard]] const context &ctx() const noexcept {
|
|
return vars;
|
|
}
|
|
|
|
private:
|
|
context vars;
|
|
pool_container_type pools;
|
|
group_container_type groups;
|
|
storage_for_type<entity_type> entities;
|
|
};
|
|
|
|
} // namespace entt
|
|
|
|
#endif
|