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

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

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

* build: add EnTT library

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

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

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

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

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

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

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

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

* build: remove entt library

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

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

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

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

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

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

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

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

* refactor: move movement logic into SpeedSystem and Entity components

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

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

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

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

* build: add entt library

* refactor(gameplay): rename ClientPlayer to LocalPlayer

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

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

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

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

* feat(gameplay): add entity destruction support

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

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

* feat(gameplay): implement entity update packets

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

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

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

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

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

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

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

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

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

* fix: correct velocity clamping and hitbox loading

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

* feat(creatures): refine pig movement physics

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

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

* feat: sync entity direction and rotate models accordingly

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

* feat(render): interpolate entity transforms for rendering

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

* fix(server_world): send time before entity updates

* build: replace nlohmann json with rapidjson

* refactor(json): migrate from nlohmann to rapidjson

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

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

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

* refactor(item): decouple items from block types

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

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

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

* fix: stabilize block item registration and display

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

* feat: add pig spawn egg item

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

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

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

* perf(render): skip entities outside loaded chunks

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

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

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

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

* feat(gameplay): spawn creatures during chunk generation

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

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

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

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

* perf(server): parallelize entity update loop

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

* feat(creatures): animate creature walk cycles

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

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

* perf(render): batch model rendering with instancing

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

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

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

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

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

* fix: correct thread count and shadow projection uniform

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

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

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

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

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

* feat(audio): add ambient pig sounds

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

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

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

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

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

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

1143 lines
43 KiB
C++

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