34 Commits

Author SHA1 Message Date
a54858e4c6 feat(server): send entity position updates to all sessions 2026-08-02 14:01:56 +08:00
18e308bd03 feat(gameplay): add pig wander AI and refine speed physics 2026-08-02 13:51:01 +08:00
2e4a32fed7 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.
2026-07-31 20:51:14 +08:00
c5ce91e653 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.
2026-07-31 20:39:40 +08:00
412f88565a 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.
2026-07-31 19:59:33 +08:00
8451921161 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.
2026-07-31 17:44:33 +08:00
d6f304e6ca 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.
2026-07-30 15:53:09 +08:00
826c6600d0 feat(gameplay): add entity destruction support 2026-07-30 15:24:19 +08:00
b166bab4f3 feat(gameplay): implement entity system with concurrent task handling and fix model loading 2026-07-30 15:05:56 +08:00
efa49a98b7 refactor(gameplay): replace client thread with timer-based system and add entity manager 2026-07-29 18:17:15 +08:00
3a9ab6a14a feat(gameplay): add entity managers and refactor to ECS components 2026-07-29 18:06:04 +08:00
d15037e7a3 refactor(gameplay): separate player logic into manager and ECS components 2026-07-28 21:00:04 +08:00
0ff5820e6e refactor(gameplay): rename ClientPlayer to LocalPlayer 2026-07-28 18:09:43 +08:00
5e96c1573b build: add entt library 2026-07-28 18:03:51 +08:00
90273e87f2 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]]
2026-07-28 14:28:22 +08:00
1e106d75a3 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.
2026-07-28 11:43:10 +08:00
bd1a9557a5 refactor: move movement logic into SpeedSystem and Entity components 2026-07-28 10:54:13 +08:00
831569cec6 feat(player): split max speed into horizontal/vertical, set spawn pos 2026-07-28 10:10:01 +08:00
a5c1c7d4aa 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.
2026-07-28 09:27:28 +08:00
802d2ecb5a refactor(gameplay): extract movement, gravity, orientation, and walk pose into structs 2026-07-28 08:53:51 +08:00
ab605f7590 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
2026-07-27 21:03:31 +08:00
15d5af34b9 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.
2026-07-27 20:21:44 +08:00
6d0e60e241 build: remove entt library 2026-07-27 09:33:51 +08:00
7b530968af fix(gameplay): correct hitbox insertion in HitboxManager::load 2026-07-25 17:52:13 +08:00
930226cc47 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
2026-07-25 17:47:13 +08:00
f9bf2f4b89 refactor(gameplay): add base classes Chunk and World for shared logic 2026-07-25 15:47:46 +08:00
df3ac5b5db refactor(collision): convert AABB to center-half representation and migrate player data to ECS 2026-07-25 14:53:19 +08:00
2568eb7d18 refactor(render): unify model and player rendering with single shader pipeline 2026-07-25 13:29:52 +08:00
2e3b4f4f0f feat(render): add shadow pass for entity models 2026-07-25 11:57:00 +08:00
5951a5e81c feat(entity): add ECS-based entity rendering 2026-07-25 11:19:27 +08:00
849386a3bc build: add EnTT library 2026-07-25 10:29:04 +08:00
3e7ce71219 refactor(render): rename depth player shaders to depth model and clean up 2026-07-24 19:53:35 +08:00
94fed9a810 feat(render): add model loading and rendering pipeline 2026-07-24 18:35:09 +08:00
f4174724c6 build(deps): add assimp library as dependency 2026-07-24 10:59:05 +08:00
201 changed files with 27891 additions and 967 deletions

View File

@@ -148,6 +148,7 @@ target_link_libraries(${PROJECT_NAME}
OpenAL::OpenAL
harfbuzz::harfbuzz
Opus::Opus
assimp::assimp
$<$<PLATFORM_ID:Windows>:ws2_32>
)

View File

@@ -0,0 +1,16 @@
{
"boxes": [
{
"center": [
0,
0.78,
0
],
"half": [
0.53,
0.78,
0.406
]
}
]
}

Binary file not shown.

Binary file not shown.

View File

@@ -7,7 +7,6 @@ uniform mat4 lightSpaceMatrix;
uniform mat4 modelMatrix;
out vec2 tc;
flat out int tex_layer;
void main() {
tc = texCoord;

View File

@@ -3,7 +3,6 @@
layout (location = 0) in vec3 pos;
layout (location = 1) in vec2 texCoord;
layout (location = 2) in vec3 aNormal;
layout (location = 3) in vec3 aTangent;
uniform mat4 mv_matrix;
uniform mat4 proj_matrix;

View File

@@ -10,7 +10,7 @@ find_package(harfbuzz REQUIRED)
find_package(Freetype REQUIRED)
find_package(SDL3 REQUIRED)
find_package(Opus REQUIRED)
find_package(assimp REQUIRED)
# Third-party libraries
if (WIN32)

View File

@@ -1,20 +0,0 @@
#pragma once
#include <glm/glm.hpp>
namespace Cubed {
struct AABB {
glm::vec3 min{0.0f, 0.0f, 0.0f};
glm::vec3 max{0.0f, 0.0f, 0.0f};
AABB(glm::vec3 min_point, glm::vec3 max_point)
: min(min_point), max(max_point) {}
bool intersects(const AABB& other) const {
return (min.x <= other.max.x && max.x >= other.min.x) &&
(min.y <= other.max.y && max.y >= other.min.y) &&
(min.z <= other.max.z && max.z >= other.min.z);
}
};
} // namespace Cubed

View File

@@ -49,7 +49,6 @@ private:
Window m_window;
TextureManager m_texture_manager;
AudioEngine m_audio;
Renderer m_renderer;

View File

@@ -8,7 +8,7 @@
namespace Cubed {
class ClientPlayer;
class LocalPlayer;
class Camera {
private:
@@ -18,7 +18,7 @@ private:
THIRD_PERSON_FRONT,
};
ClientPlayer* m_player;
LocalPlayer* m_player;
float m_last_mouse_x, m_last_mouse_y;
glm::vec3 m_camera_pos;
bool m_under_water = false;
@@ -35,7 +35,7 @@ public:
void update_move_camera();
void camera_init(ClientPlayer* player);
void camera_init(LocalPlayer* player);
void hot_reload();
void update_cursor_position_camera(float offset_x, float offset_y);
@@ -47,7 +47,7 @@ public:
void change_perspective();
bool is_first_person() const;
bool handle_event(const Event& e);
ClientPlayer* player();
LocalPlayer* player();
};
} // namespace Cubed

View File

@@ -7,7 +7,7 @@
namespace Cubed {
class WorldScene;
class ClientPlayer;
class LocalPlayer;
class App;
class DevPanel {
struct ConfigView {
@@ -42,7 +42,7 @@ private:
WorldScene& m_world_scene;
Config& m_config;
ConfigView m_config_view;
ClientPlayer* m_player;
LocalPlayer* m_player;
PlayerProfile m_player_profile;
bool m_need_save_config = false;
bool m_gen_thread_running = true;

View File

@@ -0,0 +1,27 @@
#pragma once
#include "Cubed/gameplay/chunk_pos.hpp"
#include <glm/glm.hpp>
#include <tuple>
namespace Cubed {
class Chunk {
public:
Chunk() = default;
Chunk(const Chunk&) = delete;
Chunk(Chunk&&) = delete;
Chunk& operator=(const Chunk&) = delete;
Chunk& operator=(Chunk&&) = delete;
virtual ~Chunk() = default;
static int index(int x, int y, int z);
static int index(const glm::vec3& pos);
static std::tuple<int, int, int> world_to_block(int world_x, int world_y,
int world_z, int chunk_x,
int chunk_z);
static std::tuple<int, int, int> world_to_block(const glm::ivec3& block_pos,
ChunkPos chunk_pos);
static std::tuple<int, int, int> block_to_world(int x, int y, int z,
int chunk_x, int chunk_z);
static std::tuple<int, int, int> block_to_world(const glm::ivec3& block_pos,
ChunkPos chunk_pos);
};
} // namespace Cubed

View File

@@ -2,6 +2,7 @@
#include "Cubed/constants.hpp"
#include "Cubed/gameplay/biome.hpp"
#include "Cubed/gameplay/block.hpp"
#include "Cubed/gameplay/chunk.hpp"
#include "Cubed/gameplay/chunk_pos.hpp"
#include "Cubed/gameplay/vertex_data.hpp"
#include "world/chunk_data.pb.h"
@@ -26,7 +27,7 @@ struct ChunkRenderSnapshot {
glm::vec3 center;
glm::vec3 half_extents;
};
class ClientChunk {
class ClientChunk : public Chunk {
public:
ClientChunk(ClientWorld& world);
~ClientChunk();
@@ -35,17 +36,6 @@ public:
ClientChunk(ClientChunk&&) noexcept;
ClientChunk& operator=(ClientChunk&&) noexcept;
static int index(int x, int y, int z);
static int index(const glm::vec3& pos);
static std::tuple<int, int, int> world_to_block(int world_x, int world_y,
int world_z, int chunk_x,
int chunk_z);
static std::tuple<int, int, int> world_to_block(const glm::ivec3& block_pos,
ChunkPos chunk_pos);
static std::tuple<int, int, int> block_to_world(int x, int y, int z,
int chunk_x, int chunk_z);
static std::tuple<int, int, int> block_to_world(const glm::ivec3& block_pos,
ChunkPos chunk_pos);
BiomeType get_biome() const;
ChunkPos get_chunk_pos() const;
const std::vector<BlockType>& get_chunk_blocks() const;

View File

@@ -0,0 +1,76 @@
#pragma once
#include "Cubed/gameplay/ecs/entity.hpp"
#include "glm/ext/vector_float3.hpp"
#include "world/entity.pb.h"
#include <entt/entt.hpp>
#include <tbb/concurrent_hash_map.h>
#include <tbb/concurrent_queue.h>
namespace Cubed {
class ClientWorld;
class ClientEntityManager {
public:
enum class Command { CREATE, DESTORY, UPDATE };
ClientEntityManager(ClientWorld& world);
void update();
void init();
void receive_entity_create(S2CEntityCreate& msg);
void receive_entity_destory(EntityID id);
void receive_entity_update(S2CEntityUpdate& msg);
void destory(EntityID id);
void create(std::string_view name, const glm::vec3& pos);
const entt::registry& get_registry() const;
private:
struct EntityCreateElement {
EntityID id;
std::string name;
glm::vec3 pos;
};
struct UpdateInfo {
EntityID id;
glm::vec3 pos;
};
using EntityMap = tbb::concurrent_hash_map<EntityID, entt::entity>;
using acc = EntityMap::accessor;
using cacc = EntityMap::const_accessor;
using CreateFunc = std::function<void(EntityID id)>;
using TaskElement = std::variant<EntityCreateElement, EntityID, UpdateInfo>;
using TaskPair = std::pair<Command, TaskElement>;
ClientWorld& m_world;
entt::registry m_registry;
EntityMap m_entities;
std::unordered_map<std::string_view, CreateFunc> m_factories;
tbb::concurrent_queue<TaskPair> m_tasks;
void handle_task();
void handle_entity_destory(EntityID id);
// not thread safe
void handle_entity_create(EntityID id, std::string_view name,
const glm::vec3& pos);
void handle_entity_update(UpdateInfo& info);
template <typename... Args>
void create_entity_in_registry(EntityID id, Args&&... args) {
{
cacc a;
if (m_entities.find(a, id)) {
return;
}
}
auto entity = m_registry.create();
((m_registry.emplace<std::remove_cvref_t<Args>>(
entity, std::forward<Args>(args))),
...);
m_entities.emplace(id, entity);
return;
}
};
} // namespace Cubed

View File

@@ -1,167 +1,25 @@
#pragma once
#include "Cubed/AABB.hpp"
#include "Cubed/constants.hpp"
#include "Cubed/gameplay/block.hpp"
#include "Cubed/gameplay/chunk_pos.hpp"
#include "Cubed/gameplay/game_mode.hpp"
#include "Cubed/gameplay/game_time.hpp"
#include "Cubed/gameplay/item_stack.hpp"
#include "Cubed/gameplay/player.hpp"
#include "Cubed/input/event.hpp"
#include "Cubed/input/input.hpp"
#include <absl/container/flat_hash_set.h>
#include <glm/glm.hpp>
#include <optional>
#include <shared_mutex>
#include "Cubed/gameplay/ecs/animation.hpp"
#include "Cubed/gameplay/ecs/identity.hpp"
#include "Cubed/gameplay/ecs/transform.hpp"
#include "Cubed/gameplay/player.hpp"
namespace Cubed {
class ClientWorld;
class ClientPlayer {
public:
static constexpr size_t HOTBAR_SUM = 10;
static constexpr float WALK_SOUND_INTERVAL = 0.45f;
static constexpr float RUN_SOUND_INTERVAL = 0.3f;
using ChunkPosSet = absl::flat_hash_set<ChunkPos, ChunkPos::Hash>;
ClientPlayer(ClientWorld& world);
~ClientPlayer();
bool handle_mouse_button_event(const MouseButtonEvent& e);
bool handle_key_event(const KeyEvent& e);
bool handle_mouse_wheel_event(const MouseWheelEvent& e);
void update_front_vec(float offset_x, float offset_y);
bool update_player_move_state(Key key, KeyAction action);
bool update_scroll(float yoffset);
void update_chunk_set(const ChunkPosSet& set);
const ChunkPosSet& get_chunk_pos_set() const;
ChunkPosSet get_chunk_pos_set();
static AABB get_aabb(const glm::vec3& pos);
const glm::vec3& get_front() const;
Gait get_gait() const;
const std::optional<LookBlock>& get_look_block_pos() const;
// thread safe
glm::vec3 get_player_pos() const;
const MoveState& get_move_state() const;
void change_mode(GameMode mode);
void reload_config();
void set_player_pos(const glm::vec3& pos);
void update(float delta_time);
float& max_walk_speed();
float& max_run_speed();
float& max_speed();
float& acceleration();
float& deceleration();
float& g();
float& fly_y_speed();
const ItemStack& get_current_itemstack() const;
void set_gait(Gait gait);
GameMode& game_mode();
ClientWorld& get_world();
void set_uuid(std::string_view uuid);
std::string get_uuid() const;
const std::string& get_name() const;
void reset_key_status();
void init(std::string_view name);
float yaw() const;
float pitch() const;
float& angle();
float& walk_time();
bool ray_cast(const glm::vec3& start, const glm::vec3& dir,
glm::ivec3& block_pos, glm::vec3& normal,
float distance = 4.0f);
bool is_underwater() const;
void set_underwater(bool u);
void place_block(float dt);
int selected_hotbar() const;
void set_hotbar(int pos, const ItemStack& item);
std::span<const ItemStack, HOTBAR_SUM> get_hotbar() const;
private:
using enum GameMode;
float m_max_walk_speed = DEFAULT_MAX_WALK_SPEED;
float m_max_run_speed = DEFAULT_MAX_RUN_SPEED;
float m_acceleration = DEFAULT_ACCELERATION;
float m_deceleration = DEFAULT_DECELERATION;
float m_g = DEFAULT_G;
static constexpr float MAX_SPACE_ON_TIME = 0.3f;
static constexpr float PLACE_BLOCK_INTERVAL = 0.2f;
float m_place_time = PLACE_BLOCK_INTERVAL;
std::atomic<float> m_yaw = 0.0f;
std::atomic<float> m_pitch = 0.0f;
std::array<ItemStack, HOTBAR_SUM> m_hotbar;
float m_sensitivity = 0.15f;
float m_max_speed = m_max_walk_speed;
float m_y_speed = 0.0f;
float m_fly_y_speed = 7.5f;
bool can_up = true;
float space_on_time = 0.0f;
bool space_on = false;
bool is_fly = false;
float m_xz_speed = 0.0f;
int m_selected_hotbar = 0;
bool m_moving = false;
bool m_sprinting = false;
bool m_underwater = false;
glm::vec3 direction = glm::vec3(0.0f, 0.0f, 0.0f);
glm::vec3 move_distance{0.0f, 0.0f, 0.0f};
// player is tow block tall, the pos is the lower pos
glm::vec3 m_player_pos{0.0f, 255.0f, 0.0f};
ChunkPos m_last_chunk_pos{0, 0};
glm::vec3 m_front{0, 0, -1};
glm::vec3 m_right{0, 0, 0};
static constexpr glm::vec3 M_SIZE{0.6f, 1.8f, 0.6f};
std::atomic<Gait> m_gait = Gait::STOP;
MoveState m_move_state{};
MouseState m_mouse_state{};
GameMode m_game_mode = CREATIVE;
std::optional<LookBlock> m_look_block = std::nullopt;
std::string m_name{};
mutable std::shared_mutex m_uuid_mutex;
std::string m_uuid;
ClientWorld& m_world;
float m_angle{0.0f};
float m_walk_time{0.0f};
std::unordered_map<std::string, Timer> m_timers;
mutable std::shared_mutex m_player_pos_mutex;
mutable std::shared_mutex m_chunk_pos_mutex;
ChunkPosSet m_player_chunk_pos_set;
void update_direction();
void update_lookup_block();
void update_move(float delta_time);
void update_x_move(glm::vec3& player_pos);
void update_y_move(glm::vec3& player_pos);
void update_z_move(glm::vec3& player_pos);
void update_player_chunk();
void play_walk_sound(float dt);
Gait compute_gait() const;
struct ClientPlayer {
Position pos{};
Position render_pos{};
EntityInfo entity{};
WalkPose walk{};
Orientation angle{};
Orientation render_angle{};
};
struct PlayerRenderData {
EntityInfo info{};
Position render_pos{};
Orientation angle{};
Gait gait{};
};
} // namespace Cubed

View File

@@ -0,0 +1,45 @@
#pragma once
#include "Cubed/gameplay/client_player.hpp"
#include "Cubed/gameplay/local_player.hpp"
#include "Cubed/gameplay/network_client.hpp"
#include "Cubed/tools/sparse_vector.hpp"
namespace Cubed {
class ClientWorld;
class ClientPlayerManager {
public:
ClientPlayerManager(const ClientPlayerManager&) = delete;
ClientPlayerManager(ClientPlayerManager&&) = delete;
ClientPlayerManager& operator=(const ClientPlayerManager&) = delete;
ClientPlayerManager& operator=(ClientPlayerManager&&) = delete;
ClientPlayerManager(ClientWorld& world);
~ClientPlayerManager();
void init(std::string_view local_name);
void update(float dt);
std::span<PlayerRenderData> render_player_data();
bool has_player(const Hitbox& hitbox) const;
LocalPlayer& get_local();
const LocalPlayer& get_local() const;
void receive_remote_player(const PlayerInfoRsp& rsp);
void receive_player_logout(const LogoutRsp& rsp);
void reload_config();
void report_player_info(NetworkClient* client);
private:
ClientWorld& m_world;
mutable std::shared_mutex m_players_mutex;
using PlayerHandle = SparseVector<ClientPlayer>::Handle;
SparseVector<ClientPlayer> m_players;
std::vector<PlayerRenderData> m_render_data;
std::unordered_map<std::string, PlayerHandle> m_players_handle;
LocalPlayer m_local;
void update_players_data(float dt);
};
} // namespace Cubed

View File

@@ -5,9 +5,12 @@
#include "Cubed/gameplay/chat_message.hpp"
#include "Cubed/gameplay/chunk_pos.hpp"
#include "Cubed/gameplay/client_chunk.hpp"
#include "Cubed/gameplay/client_player.hpp"
#include "Cubed/gameplay/client_entity_manager.hpp"
#include "Cubed/gameplay/client_player_manager.hpp"
#include "Cubed/gameplay/game_time.hpp"
#include "Cubed/gameplay/local_player.hpp"
#include "Cubed/gameplay/network_client.hpp"
#include "Cubed/gameplay/world.hpp"
#include "Cubed/input/event.hpp"
#include "Cubed/tools/cubed_random.hpp"
#include "Cubed/tools/priority_thread_pool.hpp"
@@ -19,46 +22,26 @@
#include <tbb/concurrent_unordered_map.h>
namespace Cubed {
struct PlayerInfo {
std::string name;
std::string uuid;
glm::vec3 render_pos;
glm::vec3 target_pos;
float render_yaw;
float yaw;
float render_pitch;
float pitch;
Gait gait;
float angle = 0.0f;
float walk_time = 0.0f;
float moving_time = 0.0f;
};
struct PlayerRenderData {
std::string name;
std::string uuid;
glm::vec3 render_pos;
float yaw;
float pitch;
Gait gait;
float angle;
};
class WorldScene;
class ClientWorld {
class ClientWorld : public World {
public:
ClientWorld(const ClientWorld&) = delete;
ClientWorld(ClientWorld&&) = delete;
ClientWorld& operator=(const ClientWorld&) = delete;
ClientWorld& operator=(ClientWorld&&) = delete;
ClientWorld(AudioEngine& auido, Config& config, WorldScene& scene);
~ClientWorld();
void init(std::string_view player_name,
std::shared_ptr<NetworkClient> client);
void update(float delta_time);
void update(float dt);
bool handle_event(const Event& e);
const std::optional<LookBlock>& get_look_block_pos() const;
ClientPlayer& get_player();
const ClientPlayer& get_player() const;
int get_block(const glm::ivec3& block_pos) const;
bool is_solid(const glm::ivec3& block_pos) const;
bool can_pass_block(const glm::ivec3& block_pos) const;
BlockType get_block_tpye(const glm::ivec3& block_pos) const;
LocalPlayer& get_player();
const LocalPlayer& get_player() const;
int get_block(const glm::ivec3& block_pos) const override;
bool is_solid(const glm::ivec3& block_pos) const override;
bool can_pass_block(const glm::ivec3& block_pos) const override;
BlockType get_block_tpye(const glm::ivec3& block_pos) const override;
void rebuild_world();
@@ -71,7 +54,6 @@ public:
void receive_block_change(const BlockChangeRsp& rsp);
void receive_time(const UpdateTime& rsp);
void receive_remote_player(const PlayerInfoRsp& rsp);
void receive_player_logout(const LogoutRsp& rsp);
void receive_player_water_sound(const PlayerWaterSound& rsp);
void send_player_water_sound(bool underwater, const glm::vec3& pos);
@@ -90,8 +72,6 @@ public:
void reset_key_status();
std::vector<glm::vec4>& planes();
const std::vector<const ChunkRenderSnapshot*>& render_snapshots() const;
const std::vector<PlayerRenderData>& render_player_data() const;
std::vector<PlayerRenderData>& render_player_data();
glm::vec3 sunlight_dir() const;
bool sphere_collide_world(glm::vec3 center, float radius) const;
@@ -99,22 +79,25 @@ public:
void request_exit();
bool is_receive_exit();
int chunk_size() const;
static AABB get_block_aabb(const glm::ivec3& pos);
AudioEngine& get_audio();
const AudioEngine& get_audio() const;
Config& get_config();
WorldScene& world_scene();
ClientPlayerManager& player_manager();
ClientEntityManager& entity_manager();
std::shared_ptr<NetworkClient> get_client() const;
void set_direct_exit();
void receive_chat_message(ChatMsg& msg);
void send_chat_message(ChatMessage& message);
void receive_voice_message(VoiceMsg& msg);
bool enable_voice_chat() const;
int get_per_tick_time() const override;
template <typename Fn>
void register_ticktimer(std::string_view id, TickType threshold, Fn&& f) {
m_ticktimers.emplace(
std::piecewise_construct, std::forward_as_tuple(std::string(id)),
void register_timer(std::string_view id, float threshold, Fn&& f) {
m_timers.emplace(std::piecewise_construct,
std::forward_as_tuple(std::string(id)),
std::forward_as_tuple(threshold, std::forward<Fn>(f)));
}
@@ -136,7 +119,6 @@ private:
ChunkPos::TBBHash>;
using ChunkPosSet = absl::flat_hash_set<ChunkPos, ChunkPos::Hash>;
using ChunkPosVector = std::vector<ChunkPos>;
using OtherPlayerHashMap = std::unordered_map<std::string, PlayerInfo>;
using chunk_acc = ChunkHashMap::accessor;
using chunk_cacc = ChunkHashMap::const_accessor;
@@ -147,16 +129,13 @@ private:
static constexpr int WORLD_EXIT_TIMEOUT = 200;
static constexpr int MAX_UPLOAD_CHUNK_SUM = 16;
ClientPlayer m_player;
OtherPlayerHashMap m_player_info;
ClientEntityManager m_entity_manager;
ClientPlayerManager m_player_manager;
ChunkHashMap m_chunks;
AudioEngine& m_audio;
Config& m_config;
WorldScene& m_world_scene;
std::vector<glm::vec4> m_planes;
std::jthread m_client_thread;
mutable std::shared_mutex m_player_info_mutex;
tbb::concurrent_queue<std::unique_ptr<ClientChunk>> m_pending_upload_queue;
tbb::concurrent_queue<ChunkPos> m_dirty_chunk_queue;
@@ -166,9 +145,7 @@ private:
std::deque<ChunkPos> m_dirty_queue;
std::vector<const ChunkRenderSnapshot*> m_render_snapshots;
std::vector<PlayerRenderData> m_render_player_data;
tbb::concurrent_unordered_map<std::string, TickTimer> m_ticktimers;
std::unordered_map<std::string, Timer> m_timers;
std::atomic<bool> m_exit_direct{false};
std::atomic<bool> m_game_running{false};
@@ -176,6 +153,7 @@ private:
std::atomic<int> m_rendering_distance{24};
std::atomic<TickType> m_game_ticks{0};
std::atomic<TickType> m_day_tick{6000};
std::atomic<int> m_per_tick_time = DEFAULT_PER_TICK_TIME;
std::atomic<bool> m_requesting_chunk{false};
std::atomic<bool> m_is_rebuilding{false};
std::atomic<int> m_chunk_task_id{0};
@@ -187,10 +165,6 @@ private:
Random m_random;
void client_run(std::stop_token token);
void report_player_info();
void set_block(const glm::ivec3& pos, unsigned id);
void update_chunk(const ChunkPosSet& old, const ChunkPosSet& now);

View File

@@ -0,0 +1,6 @@
#pragma once
namespace Cubed {
struct PigTag {};
} // namespace Cubed

View File

@@ -0,0 +1,17 @@
#pragma once
#include "Cubed/gameplay/game_time.hpp"
namespace Cubed {
struct AIBase {
TickType interval = 1;
TickType count = 0;
};
struct WanderAITag {};
struct MoveBoost {
TickType duration = 0;
TickType count = 0;
};
} // namespace Cubed

View File

@@ -0,0 +1,13 @@
#pragma once
#include "Cubed/gameplay/player.hpp"
namespace Cubed {
struct WalkPose {
Gait gait = Gait::STOP;
// for arm roll caculate
float walk_time = 0.0f;
// for sound play
float moving_time = 0.0f;
};
} // namespace Cubed

View File

@@ -0,0 +1,14 @@
#pragma once
#include "Cubed/gameplay/ecs/animation.hpp"
#include "Cubed/gameplay/ecs/transform.hpp"
#include "Cubed/gameplay/model.hpp"
namespace Cubed {
struct BaseClientCreature {
Transform transform;
WalkPose pose;
ModelID model;
};
} // namespace Cubed

View File

@@ -0,0 +1,11 @@
#pragma once
#include <cstdint>
namespace Cubed {
using EntityID = uint64_t;
struct Entity {
EntityID id;
explicit Entity(EntityID id) : id(id) {}
};
} // namespace Cubed

View File

@@ -0,0 +1,10 @@
#pragma once
namespace Cubed {
struct Health {
float hp = 20;
float max_hp = 20;
};
} // namespace Cubed

View File

@@ -0,0 +1,10 @@
#pragma once
#include <string>
namespace Cubed {
struct EntityInfo {
std::string name;
std::string uuid;
};
} // namespace Cubed

View File

@@ -0,0 +1,34 @@
#pragma once
#include "Cubed/constants.hpp"
#include <glm/glm.hpp>
namespace Cubed {
struct TickVelocity {
glm::vec3 value{0.0f};
// blocks/tick!!! -1 for in
glm::vec3 max{1.0f, -1.0f, 1.0f};
};
struct Velocity {
glm::vec3 value{0.0f};
// blocks/second!!!
glm::vec3 max{4.5f, 7.5f, 7.5f};
};
struct Movement {
float acceleration = DEFAULT_ACCELERATION;
float deceleration = DEFAULT_DECELERATION;
float jump_power = 7.5f;
};
struct Gravity {
float value = DEFAULT_G;
};
} // namespace Cubed

View File

@@ -0,0 +1,24 @@
#pragma once
#include "Cubed/gameplay/ecs/health.hpp"
#include "Cubed/gameplay/ecs/movement.hpp"
#include "Cubed/gameplay/ecs/transform.hpp"
#include "Cubed/gameplay/hitbox.hpp"
namespace Cubed {
struct BaseServerCreature {
Transform transform{};
TickVelocity velocity{};
Movement movement{};
Direction direction{};
Gravity gravity{};
Health health{};
HitboxID hitbox{};
};
} // namespace Cubed

View File

@@ -0,0 +1,18 @@
#pragma once
namespace Cubed {
struct MoveState {
bool forward = false;
bool back = false;
bool left = false;
bool right = false;
bool down = false;
bool up = false;
bool is_fly = false;
bool can_up = true;
};
} // namespace Cubed

View File

@@ -0,0 +1,24 @@
#pragma once
#include <glm/glm.hpp>
namespace Cubed {
struct Position {
glm::vec3 value{0.0f};
};
struct Orientation {
float yaw = 0.0f;
float pitch = 0.0f;
float roll = 0.0f;
};
struct Direction {
glm::vec3 value{0.0f};
};
struct Transform {
Position position{};
Orientation orientation{};
Direction direction{};
};
} // namespace Cubed

View File

@@ -0,0 +1,26 @@
#pragma once
#include <glm/glm.hpp>
namespace Cubed {
using HitboxID = uint32_t;
struct Hitbox {
glm::vec3 center{0.0f};
glm::vec3 half{0.0f};
Hitbox(glm::vec3 center_point, glm::vec3 half_size)
: center(center_point), half(half_size) {}
glm::vec3 min() const { return center - half; }
glm::vec3 max() const { return center + half; }
bool intersects(const Hitbox& other) const {
return (glm::abs(center.x - other.center.x) <= half.x + other.half.x) &&
(glm::abs(center.y - other.center.y) <= half.y + other.half.y) &&
(glm::abs(center.z - other.center.z) <= half.z + other.half.z);
}
};
} // namespace Cubed

View File

@@ -0,0 +1,37 @@
#pragma once
#include "Cubed/gameplay/hitbox.hpp"
#include <tbb/concurrent_hash_map.h>
namespace Cubed {
class HitboxManager {
public:
struct Handle {
Hitbox box;
HitboxID id = 0;
};
HitboxManager();
~HitboxManager();
static HitboxManager& instance();
[[nodiscard]]
Handle get_hitbox(const std::string& key);
[[nodiscard]]
Handle get_hitbox(HitboxID id);
[[nodiscard]]
static Handle hitbox(const std::string& name);
[[nodiscard]]
static Handle hitbox(HitboxID id);
HitboxID get_hitbox_id(const std::string& name);
const std::string& get_hitbox_name(HitboxID id);
private:
using HitboxMap = tbb::concurrent_hash_map<HitboxID, Hitbox>;
using IDMap = tbb::concurrent_hash_map<std::string, HitboxID>;
using NameMap = tbb::concurrent_hash_map<HitboxID, std::string>;
HitboxID m_next = 0;
IDMap m_id_map;
NameMap m_name_map;
HitboxMap m_hitboxes;
Handle load(std::string_view name);
};
} // namespace Cubed

View File

@@ -0,0 +1,162 @@
#pragma once
#include "Cubed/constants.hpp"
#include "Cubed/gameplay/block.hpp"
#include "Cubed/gameplay/chunk_pos.hpp"
#include "Cubed/gameplay/ecs/animation.hpp"
#include "Cubed/gameplay/ecs/identity.hpp"
#include "Cubed/gameplay/ecs/movement.hpp"
#include "Cubed/gameplay/ecs/state.hpp"
#include "Cubed/gameplay/ecs/transform.hpp"
#include "Cubed/gameplay/game_mode.hpp"
#include "Cubed/gameplay/game_time.hpp"
#include "Cubed/gameplay/hitbox.hpp"
#include "Cubed/gameplay/item_stack.hpp"
#include "Cubed/gameplay/player.hpp"
#include "Cubed/input/event.hpp"
#include "Cubed/input/input.hpp"
#include <absl/container/flat_hash_set.h>
#include <glm/glm.hpp>
#include <optional>
#include <shared_mutex>
namespace Cubed {
class ClientWorld;
class LocalPlayer {
public:
static constexpr size_t HOTBAR_SUM = 10;
static constexpr float WALK_SOUND_INTERVAL = 0.45f;
static constexpr float RUN_SOUND_INTERVAL = 0.3f;
using ChunkPosSet = absl::flat_hash_set<ChunkPos, ChunkPos::Hash>;
LocalPlayer(ClientWorld& world);
~LocalPlayer();
bool handle_mouse_button_event(const MouseButtonEvent& e);
bool handle_key_event(const KeyEvent& e);
bool handle_mouse_wheel_event(const MouseWheelEvent& e);
void update_front_vec(float offset_x, float offset_y);
bool update_player_move_state(Key key, KeyAction action);
bool update_scroll(float yoffset);
void update_chunk_set(const ChunkPosSet& set);
const ChunkPosSet& get_chunk_pos_set() const;
ChunkPosSet get_chunk_pos_set();
const glm::vec3& get_front() const;
const std::optional<LookBlock>& get_look_block_pos() const;
// thread safe
glm::vec3 get_player_pos() const;
const MoveState& get_move_state() const;
void change_mode(GameMode mode);
void reload_config();
void set_player_pos(const glm::vec3& pos);
void update(float delta_time);
float& max_walk_speed();
float& max_run_speed();
float& fly_y_speed();
const ItemStack& get_current_itemstack() const;
GameMode& game_mode();
ClientWorld& get_world();
void set_uuid(std::string_view uuid);
std::string get_uuid() const;
const std::string& get_name() const;
void reset_input_status();
void init(std::string_view name);
bool ray_cast(const glm::vec3& start, const glm::vec3& dir,
glm::ivec3& block_pos, glm::vec3& normal,
float distance = 4.0f);
bool is_underwater() const;
void set_underwater(bool u);
void place_block(float dt);
int selected_hotbar() const;
void set_hotbar(int pos, const ItemStack& item);
std::span<const ItemStack, HOTBAR_SUM> get_hotbar() const;
glm::vec3& max_speed();
float& acceleration();
float& deceleration();
float& g();
void set_gait(Gait gait);
float yaw() const;
float pitch() const;
float& roll();
float& walk_time();
Gait get_gait() const;
private:
using enum GameMode;
float m_max_walk_speed = DEFAULT_MAX_WALK_SPEED;
float m_max_run_speed = DEFAULT_MAX_RUN_SPEED;
float m_max_y_speed = 7.5f;
static constexpr float MAX_SPACE_ON_TIME = 0.3f;
static constexpr float PLACE_BLOCK_INTERVAL = 0.2f;
EntityInfo m_info;
Position m_pos;
WalkPose m_walk_pose;
Velocity m_velocity;
Orientation m_angle;
Movement m_movement;
Gravity m_gravity;
MoveState m_move_state;
Direction m_direction;
HitboxID m_hitbox = 0;
float m_place_time = PLACE_BLOCK_INTERVAL;
std::array<ItemStack, HOTBAR_SUM> m_hotbar;
float m_sensitivity = 0.15f;
float space_on_time = 0.0f;
bool space_on = false;
int m_selected_hotbar = 0;
bool m_moving = false;
bool m_sprinting = false;
bool m_underwater = false;
// player is tow block tall, the pos is the lower pos
ChunkPos m_last_chunk_pos{0, 0};
glm::vec3 m_front{0, 0, -1};
glm::vec3 m_right{0, 0, 0};
MouseState m_mouse_state{};
GameMode m_game_mode = CREATIVE;
std::optional<LookBlock> m_look_block = std::nullopt;
std::string m_name{};
mutable std::shared_mutex m_uuid_mutex;
std::string m_uuid;
ClientWorld& m_world;
std::unordered_map<std::string, Timer> m_timers;
mutable std::shared_mutex m_player_pos_mutex;
mutable std::shared_mutex m_chunk_pos_mutex;
ChunkPosSet m_player_chunk_pos_set;
void update_direction();
void update_lookup_block();
void update_move(float dt);
void update_player_chunk();
void play_walk_sound(float dt);
Gait compute_gait() const;
void update_speed(float dt);
std::tuple<bool, bool, bool> update_physical(float dt, glm::vec3& pos);
glm::vec3 get_move_distance(float dt);
};
} // namespace Cubed

View File

@@ -0,0 +1,7 @@
#pragma once
#include <cstdint>
using ModelID = uint32_t;
struct Model {
ModelID id;
};

View File

@@ -51,18 +51,27 @@ enum class PacketEnum : uint16_t {
LOGIN_RSP = 1002,
LOGOUT_REQ = 1003,
LOGOUT_RSP = 1004,
PLAYER_INFO = 2001,
C2S_PLAYER_INFO = 2002,
PLAYER_INFO_RSP = 2003,
PLAYER_WATER_SOUND = 2004,
CHUNK_DATA_REQ = 3001,
CHUNK_DATA_RSP = 3002,
BLOCK_CHANGE_REQ = 3003,
BLOCK_CHANGE_RSP = 3004,
S2C_CLEAR_ALL_CHUNKS = 3005,
UPDATE_TIME = 3006,
S2C_ENTITY_CREATE = 3007,
S2C_ENTITY_DESTORY = 3008,
C2S_ENTITY_CREATE_REQUEST = 3009,
C2S_ENTITY_DESTORY_REQUEST = 3010,
S2C_ENTITY_UPDATE = 3011,
CHAT_MSG = 4001,
VOICE_MSG = 4002,
PING = 9001,
PONG = 9002
@@ -111,6 +120,18 @@ template <> constexpr uint16_t get_packet_id<BlockChangeRsp>() {
template <> constexpr uint16_t get_packet_id<S2C_ClearAllChunks>() {
return std::to_underlying(PacketEnum::S2C_CLEAR_ALL_CHUNKS);
}
template <> constexpr uint16_t get_packet_id<S2CEntityCreate>() {
return std::to_underlying(PacketEnum::S2C_ENTITY_CREATE);
}
template <> constexpr uint16_t get_packet_id<S2CEntityDestory>() {
return std::to_underlying(PacketEnum::S2C_ENTITY_DESTORY);
}
template <> constexpr uint16_t get_packet_id<C2SEntityCreateRequest>() {
return std::to_underlying(PacketEnum::C2S_ENTITY_CREATE_REQUEST);
}
template <> constexpr uint16_t get_packet_id<C2SEntityDestoryRequest>() {
return std::to_underlying(PacketEnum::C2S_ENTITY_DESTORY_REQUEST);
}
template <> constexpr uint16_t get_packet_id<UpdateTime>() {
return std::to_underlying(PacketEnum::UPDATE_TIME);
}
@@ -129,6 +150,9 @@ template <> constexpr uint16_t get_packet_id<ChatMsg>() {
template <> constexpr uint16_t get_packet_id<VoiceMsg>() {
return std::to_underlying(PacketEnum::VOICE_MSG);
}
template <> constexpr uint16_t get_packet_id<S2CEntityUpdate>() {
return std::to_underlying(PacketEnum::S2C_ENTITY_UPDATE);
}
template <typename T>
requires std::derived_from<T, google::protobuf::Message>
@@ -180,6 +204,12 @@ Packet make_packet(const T& msg) {
return packet;
}
template <typename T>
requires std::derived_from<T, google::protobuf::Message>
Packet make_packet(const T* msg) {
return make_packet(*msg);
}
inline PacketHeader decode_packet_header(std::span<const uint8_t> header) {
if (header.size() < HEADER_LEN)
throw std::runtime_error("Invalid header");

View File

@@ -1,4 +1,6 @@
#pragma once
#include "glm/ext/vector_float3.hpp"
#include <stdexcept>
#include <utility>
namespace Cubed {
@@ -17,5 +19,5 @@ inline Gait get_gait_from_id(int id) {
throw std::runtime_error("Unknown Gait");
}
}
static constexpr glm::vec3 PLAYER_SIZE{0.6f, 1.8f, 0.6f};
} // namespace Cubed

View File

@@ -2,6 +2,7 @@
#include "Cubed/constants.hpp"
#include "Cubed/gameplay/biome.hpp"
#include "Cubed/gameplay/block.hpp"
#include "Cubed/gameplay/chunk.hpp"
#include "Cubed/gameplay/chunk_generator.hpp"
#include "Cubed/gameplay/chunk_pos.hpp"
@@ -11,7 +12,7 @@
#include <tuple>
namespace Cubed {
class ServerWorld;
class ServerChunk {
class ServerChunk : public Chunk {
public:
ServerChunk(ServerWorld& world, ChunkPos chunk_pos,
bool temp_chunk = false);

View File

@@ -0,0 +1,62 @@
#pragma once
#include "Cubed/gameplay/ecs/entity.hpp"
#include "glm/ext/vector_float3.hpp"
#include <entt/entt.hpp>
#include <tbb/concurrent_hash_map.h>
#include <tbb/concurrent_queue.h>
namespace Cubed {
class ServerWorld;
class Session;
class ServerEntityManager {
public:
ServerEntityManager(ServerWorld& world);
void init();
void update();
// not thread safe
void add_entity(std::string_view name, const glm::vec3& pos);
void destory(EntityID id);
void handle_player_login(std::shared_ptr<Session> session);
private:
enum class Command { CREATE, SEND_ALL_ENTITIES, DESTORY };
struct EntityCreateElement {
std::string name;
glm::vec3 pos;
};
using EntityMap = tbb::concurrent_hash_map<EntityID, entt::entity>;
using acc = EntityMap::accessor;
using cacc = EntityMap::const_accessor;
using CreateFunc = std::function<EntityID()>;
using TaskElement =
std::variant<std::shared_ptr<Session>, EntityCreateElement, EntityID>;
using TaskPair = std::pair<Command, TaskElement>;
ServerWorld& m_world;
tbb::concurrent_queue<TaskPair> m_tasks;
entt::registry m_registry;
EntityID m_next = 0;
EntityMap m_entities;
std::unordered_map<std::string_view, CreateFunc> m_factories;
void create_entity(std::string_view name, const glm::vec3& pos);
void handle_entity_create(EntityID id, std::string_view name,
const glm::vec3& pos);
void handle_entity_destory(EntityID id);
void handle_task();
void send_all_entities(std::shared_ptr<Session>& session);
void update_ai();
void update_move();
void update_send();
template <typename... Args>
EntityID create_entity_in_factory(Args&&... args) {
auto entity = m_registry.create();
((m_registry.emplace<std::remove_cvref_t<Args>>(
entity, std::forward<Args>(args))),
...);
auto id = m_next++;
m_entities.emplace(id, entity);
return id;
}
};
} // namespace Cubed

View File

@@ -7,7 +7,9 @@
#include "Cubed/gameplay/packet.hpp" // IWYU pragma: keep
#include "Cubed/gameplay/river_worm.hpp"
#include "Cubed/gameplay/server_chunk.hpp"
#include "Cubed/gameplay/server_entity_manager.hpp"
#include "Cubed/gameplay/server_player.hpp"
#include "Cubed/gameplay/world.hpp"
#include "Cubed/tools/priority_thread_pool.hpp"
#include "Cubed/tools/recent_queue.hpp"
#include "Cubed/tools/sensitive_filter.hpp"
@@ -25,7 +27,7 @@
#include <vector>
namespace Cubed {
class Session;
class ServerWorld {
class ServerWorld : public World {
public:
enum class ThreadPoolKind { NET, GEN };
ServerWorld(Config& config);
@@ -84,7 +86,19 @@ public:
void handle_chat_message(ChatMsg& msg);
void handle_voice_message(VoiceMsg& msg);
void handle_entity_create(C2SEntityCreateRequest& req);
void handle_entity_destory(C2SEntityDestoryRequest& req);
int chunk_size() const;
std::vector<std::shared_ptr<Session>> get_all_session() const;
int get_block(const glm::ivec3& block_pos) const override;
bool is_solid(const glm::ivec3& block_pos) const override;
bool can_pass_block(const glm::ivec3& block_pos) const override;
BlockType get_block_tpye(const glm::ivec3& block_pos) const override;
int get_per_tick_time() const override;
template <typename Fn>
void register_timer(std::string_view id, TickType threshold, Fn&& f) {
m_timers.emplace(std::piecewise_construct,
@@ -119,13 +133,13 @@ private:
using PlayerUUIDMap = tbb::concurrent_hash_map<std::string, std::string>;
using chunk_acc = ChunkHashMap::accessor;
using chunk_caac = ChunkHashMap::const_accessor;
using chunk_cacc = ChunkHashMap::const_accessor;
using uuid_acc = PlayerUUIDMap::accessor;
using uuid_cacc = PlayerUUIDMap::const_accessor;
Config& m_config;
ServerEntityManager m_entity_manager;
// key = uuid
PlayerHashMap m_players;
ChunkHashMap m_chunks;
@@ -155,7 +169,7 @@ private:
std::atomic<bool> m_tick_running{true};
std::atomic<int> m_per_tick_time = DEFAULT_PER_TICK_TIME; // ms
mutable std::shared_mutex m_player_mutex;
mutable std::shared_mutex m_players_mutex;
std::mutex m_need_gen_queue_mutex;
std::condition_variable_any m_gen_cv;

View File

@@ -0,0 +1,17 @@
#pragma once
#include "Cubed/gameplay/ecs/movement.hpp"
#include "Cubed/gameplay/ecs/transform.hpp"
#include <entt/entt.hpp>
namespace Cubed {
class ServerWorld;
class PhysicalSystem {
public:
static glm::vec3 get_move_distance(const Direction& d,
const TickVelocity& v);
static void update(ServerWorld& world, entt::registry& registry);
private:
};
} // namespace Cubed

View File

@@ -0,0 +1,11 @@
#pragma once
#include <entt/entt.hpp>
namespace Cubed {
class SpeedSystem {
public:
static void update(float dt, entt::registry& registry);
private:
};
} // namespace Cubed

View File

@@ -0,0 +1,14 @@
#pragma once
#include "Cubed/gameplay/ecs/ai_struct.hpp"
#include "Cubed/gameplay/ecs/server_entity.hpp"
#include <entt/entt.hpp>
namespace Cubed {
class WanderAISystem {
public:
static void update(entt::registry& registry);
private:
static void do_ai(BaseServerCreature& creature, MoveBoost& move_boost);
};
} // namespace Cubed

View File

@@ -0,0 +1,29 @@
#pragma once
#include "Cubed/gameplay/block.hpp"
#include "Cubed/gameplay/hitbox.hpp"
#include <glm/glm.hpp>
namespace Cubed {
class World {
public:
World() = default;
World(const World&) = delete;
World(World&&) = delete;
World& operator=(const World&) = delete;
World& operator=(World&&) = delete;
virtual ~World() = default;
virtual int get_block(const glm::ivec3& block_pos) const = 0;
virtual bool is_solid(const glm::ivec3& block_pos) const = 0;
virtual bool can_pass_block(const glm::ivec3& block_pos) const = 0;
virtual BlockType get_block_tpye(const glm::ivec3& block_pos) const = 0;
virtual int get_per_tick_time() const = 0;
static Hitbox get_block_aabb(const glm::ivec3& pos) {
return {glm::vec3{static_cast<float>(pos.x) + 0.5f,
static_cast<float>(pos.y) + 0.5f,
static_cast<float>(pos.z) + 0.5f},
glm::vec3{0.5f, 0.5f, 0.5f}};
}
};
} // namespace Cubed

View File

@@ -2,15 +2,6 @@
namespace Cubed {
struct MoveState {
bool forward = false;
bool back = false;
bool left = false;
bool right = false;
bool down = false;
bool up = false;
};
struct MouseState {
bool left = false;
bool right = false;

View File

@@ -0,0 +1,45 @@
#pragma once
#include "Cubed/gameplay/model.hpp"
#include "Cubed/render/model_node.hpp"
#include "Cubed/tools/model_loader.hpp"
#include <tbb/concurrent_hash_map.h>
namespace Cubed {
class ModelManager {
public:
struct Handle {
const ModelNode& node;
ModelID id = 0;
};
ModelManager();
ModelManager(const ModelManager&) = delete;
ModelManager(ModelManager&&) = delete;
ModelManager& operator=(const ModelManager&) = delete;
ModelManager& operator=(ModelManager&&) = delete;
static ModelManager& instance();
~ModelManager();
[[nodiscard]]
Handle get_model(const std::string& model_name);
[[nodiscard]]
Handle get_model(ModelID id);
[[nodiscard]]
static Handle model(const std::string& model_name);
[[nodiscard]]
static Handle model(ModelID id);
ModelID get_model_id(const std::string& name);
const std::string& get_model_name(ModelID id);
void init();
private:
ModelLoader m_loader;
ModelID m_next = 0;
using ModelMap = tbb::concurrent_hash_map<ModelID, ModelNode>;
using IDMap = tbb::concurrent_hash_map<std::string, ModelID>;
using NameMap = tbb::concurrent_hash_map<ModelID, std::string>;
ModelMap m_models;
IDMap m_id_map;
NameMap m_name_map;
Handle load_model(std::string_view model_name);
};
} // namespace Cubed

View File

@@ -0,0 +1,42 @@
#pragma once
#include "Cubed/primitive_data.hpp"
#include "Cubed/render/texture.hpp"
#include "Cubed/render/vertex_array.hpp"
#include "Cubed/render/vertex_buffer.hpp"
#include <glad/glad.h>
#include <glm/glm.hpp>
#include <memory>
#include <string>
namespace Cubed {
struct Mesh {
std::vector<Vertex3D> vertices;
std::vector<uint32_t> indices;
std::unique_ptr<VertexBuffer> vbo;
std::unique_ptr<VertexBuffer> ebo;
std::unique_ptr<VertexArray> vao;
std::unique_ptr<Texture> texture;
void upload() {
vao = std::make_unique<VertexArray>();
vao->bind();
vbo = std::make_unique<VertexBuffer>();
vbo->buffer_data(vertices.data(), vertices.size() * sizeof(Vertex3D));
ebo = std::make_unique<VertexBuffer>(BufferType::ELEMENT_ARRAY_BUFFER);
ebo->buffer_data(indices.data(), indices.size() * sizeof(uint32_t));
vao->attribute(0, 3, GL_FLOAT, sizeof(Vertex3D), (void*)0);
vao->attribute(1, 2, GL_FLOAT, sizeof(Vertex3D),
(void*)offsetof(Vertex3D, s));
vao->attribute(2, 3, GL_FLOAT, sizeof(Vertex3D),
(void*)offsetof(Vertex3D, nx));
};
};
struct ModelNode {
std::string name;
glm::mat4 transform{1.0f};
std::vector<Mesh> meshes;
std::vector<ModelNode> children;
};
} // namespace Cubed

View File

@@ -0,0 +1,24 @@
#pragma once
#include "Cubed/gameplay/model.hpp"
#include "Cubed/render/model_node.hpp"
#include "Cubed/shader.hpp"
#include <glm/glm.hpp>
namespace Cubed {
class Renderer;
class Camera;
class ModelRender {
public:
ModelRender(Renderer& renderer);
void render_model(ModelID id, const glm::vec3& pos, Camera& camera);
void shadow_pass(ModelID id, const glm::vec3& pos, Camera& camera);
private:
Renderer& m_renderer;
void render_node(const ModelNode& node, const glm::mat4& parent,
const glm::mat4& view, const Shader& shader, bool shadow);
void render_mesh(const Mesh& mesh, bool shadow);
};
} // namespace Cubed

View File

@@ -13,9 +13,7 @@ public:
PlayerRenderer(Renderer& renderer);
~PlayerRenderer();
void init();
void render(const Shader& shader, ClientWorld& world);
void shadow_render(const Shader& shader, glm::mat4& light_matrix,
ClientWorld& world);
void render(const Shader& shader, ClientWorld& world, bool shadow_render);
private:
struct PlayerVertex {

View File

@@ -4,6 +4,7 @@
#include "Cubed/constants.hpp"
#include "Cubed/input/event.hpp"
#include "Cubed/primitive_data.hpp"
#include "Cubed/render/model_renderer.hpp"
#include "Cubed/render/player_renderer.hpp"
#include "Cubed/render/shader_manager.hpp"
#include "Cubed/render/vertex_array.hpp"
@@ -19,6 +20,7 @@
namespace Cubed {
class TextureManager;
class ClientWorld;
class ModelManager;
class DevPanel;
class Renderer {
public:
@@ -79,9 +81,10 @@ public:
bool handle_event(const Event& e);
ModelRender& model_renderer();
private:
TextureManager& m_texture_manager;
bool m_init = false;
float m_aspect = 0.0f;
@@ -118,6 +121,7 @@ private:
std::vector<Vertex2D> m_ui;
WorldRenderer m_world_renderer;
ModelRender m_model_renderer;
Config& m_config;
bool handle_window_resize_event(const WindowResizeEvent& e);

View File

@@ -38,9 +38,11 @@ enum TextureFormat : GLenum {
R8 = GL_R8,
RGB = GL_RGB,
RGBA8 = GL_RGBA8,
BGRA = GL_BGRA
};
// You need to set the texture scaling method, otherwise it will render as
// black.
class Texture {
public:
explicit Texture(TextureType type);

View File

@@ -132,7 +132,8 @@ private:
void render_underwater(ClientWorld& world);
void render_outline(ClientWorld& world);
void render_player(ClientWorld& world);
void shadow_entity(ClientWorld& world, const glm::mat4& light_matrix);
void render_entity(ClientWorld& world);
void render_normal_block(const glm::mat4& model_mat,
const glm::mat4& mv_mat, const glm::mat4& norm_mat,

View File

@@ -0,0 +1,7 @@
#pragma once
#include <type_traits>
namespace Cubed {
template <typename T>
concept Ptr = std::is_pointer_v<T>;
}

View File

@@ -1,4 +1,6 @@
#pragma once
#include "glm/ext/vector_float3.hpp"
#include <random>
namespace Cubed {
@@ -14,6 +16,8 @@ public:
int random_int(int min, int max);
float random_float(float min, float max);
glm::vec3 random_direction_horizontal();
private:
unsigned int m_seed = 0;
std::mt19937 m_engine;

View File

@@ -0,0 +1,22 @@
#pragma once
#include "Cubed/render/model_node.hpp"
#include <assimp/Importer.hpp>
#include <assimp/scene.h>
namespace Cubed {
class ModelLoader {
public:
ModelLoader();
ModelNode load(const std::string& path);
private:
Assimp::Importer m_importer;
ModelNode process_node(aiNode* node, const aiScene* scene);
Mesh process_mesh(aiMesh* mesh, const aiScene* scene);
bool process_texture(Mesh& mesh, aiMaterial* material, const aiScene* scene,
aiTextureType type);
glm::mat4 convert_matrix(const aiMatrix4x4& matrix);
};
} // namespace Cubed

View File

@@ -0,0 +1,19 @@
#pragma once
#include <string_view>
#include <vector>
namespace Cubed {
inline std::vector<std::string_view> parse_namespace(std::string_view str) {
std::vector<std::string_view> space;
space.reserve(4);
std::size_t p = str.find(':');
std::size_t start = 0;
while (p != std::string_view::npos) {
space.emplace_back(str.substr(start, p));
start = p + 1;
p = str.find(':', p + 1);
}
space.emplace_back(str.substr(start));
return space;
}
} // namespace Cubed

View File

@@ -0,0 +1,21 @@
#pragma once
#include "Cubed/tools/cubed_concepts.hpp"
#include "common/vector3.pb.h"
#include "glm/ext/vector_float3.hpp"
namespace Cubed {
namespace Tools {
template <Ptr T> void set_net_pos(T ptr, const glm::vec3& pos) {
Vec3* p = ptr->mutable_pos();
p->set_x(pos.x);
p->set_y(pos.y);
p->set_z(pos.z);
}
inline glm::vec3 get_net_pos(const Vec3* p) {
return glm::vec3{p->x(), p->y(), p->z()};
}
inline glm::vec3 get_net_pos(const Vec3& p) {
return glm::vec3{p.x(), p.y(), p.z()};
}
} // namespace Tools
} // namespace Cubed

View File

@@ -47,7 +47,7 @@ bool check_opengl_error();
std::string read_shader_source(const std::string& file_path);
ImageData load_image_data(const std::string& tex_image_path,
bool check_exist = true);
bool check_exist = true, bool full_path = false);
} // namespace Tools

View File

@@ -0,0 +1,318 @@
#pragma once
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <iterator>
#include <optional>
#include <utility>
#include <vector>
namespace Cubed {
template <typename T> class SparseVector {
private:
std::vector<std::optional<T>> m_data;
std::vector<uint32_t> m_free_list;
std::vector<uint32_t> m_generation;
std::vector<uint32_t> m_dense;
std::vector<uint32_t> m_dense_index;
public:
struct Handle {
uint32_t index;
uint32_t generation;
uint64_t value() const noexcept {
return (uint64_t(index) << 32) | generation;
}
bool operator==(const Handle&) const = default;
struct Hash {
size_t operator()(const Handle& h) const noexcept {
return h.value();
}
};
};
using value_type = T;
using reference = T&;
using const_reference = const T&;
using pointer = T*;
using size_type = size_t;
class iterator { // NOLINT
private:
SparseVector* m_owner;
size_t m_index;
public:
using iterator_category = std::random_access_iterator_tag;
using iterator_concept = std::random_access_iterator_tag;
using value_type = T;
using difference_type = std::ptrdiff_t;
using pointer = T*;
using reference = T&;
iterator(SparseVector* owner, size_t index)
: m_owner(owner), m_index(index) {}
reference operator*() {
return m_owner->m_data[m_owner->m_dense[m_index]].value();
}
pointer operator->() { return &(**this); }
iterator& operator++() {
++m_index;
return *this;
}
iterator operator++(int) {
iterator tmp = *this;
++(*this);
return tmp;
}
iterator& operator--() {
--m_index;
return *this;
}
iterator operator--(int) {
iterator tmp = *this;
--(*this);
return tmp;
}
bool operator==(const iterator& other) const {
return m_owner == other.m_owner && m_index == other.m_index;
}
bool operator!=(const iterator& other) const {
return !(*this == other);
}
iterator operator+(difference_type n) const {
return iterator(m_owner, m_index + n);
}
iterator operator-(difference_type n) const {
return iterator(m_owner, m_index - n);
}
difference_type operator-(const iterator& other) const {
return static_cast<difference_type>(m_index) -
static_cast<difference_type>(other.m_index);
}
iterator& operator+=(difference_type n) {
m_index += n;
return *this;
}
iterator& operator-=(difference_type n) {
m_index -= n;
return *this;
}
reference operator[](difference_type n) const { return *(*this + n); }
bool operator<(const iterator& other) const {
return m_index < other.m_index;
}
bool operator>(const iterator& other) const {
return m_index > other.m_index;
}
bool operator<=(const iterator& other) const {
return m_index <= other.m_index;
}
bool operator>=(const iterator& other) const {
return m_index >= other.m_index;
}
friend iterator operator+(difference_type n, const iterator& it) {
return it + n;
}
};
class const_iterator { // NOLINT
private:
const SparseVector* m_owner;
size_t m_index;
public:
using iterator_category = std::random_access_iterator_tag;
using iterator_concept = std::random_access_iterator_tag;
using value_type = T;
using difference_type = std::ptrdiff_t;
using pointer = const T*;
using reference = const T&;
const_iterator(const SparseVector* owner, size_t index)
: m_owner(owner), m_index(index) {}
reference operator*() const {
return m_owner->m_data[m_owner->m_dense[m_index]].value();
}
pointer operator->() const { return &(**this); }
const_iterator& operator++() {
++m_index;
return *this;
}
const_iterator operator++(int) {
const_iterator tmp = *this;
++(*this);
return tmp;
}
const_iterator& operator--() {
--m_index;
return *this;
}
const_iterator operator--(int) {
const_iterator tmp = *this;
--(*this);
return tmp;
}
bool operator==(const const_iterator& other) const {
return m_owner == other.m_owner && m_index == other.m_index;
}
bool operator!=(const const_iterator& other) const {
return !(*this == other);
}
const_iterator operator+(difference_type n) const {
return const_iterator(m_owner, m_index + n);
}
const_iterator operator-(difference_type n) const {
return const_iterator(m_owner, m_index - n);
}
difference_type operator-(const const_iterator& other) const {
return static_cast<difference_type>(m_index) -
static_cast<difference_type>(other.m_index);
}
const_iterator& operator+=(difference_type n) {
m_index += n;
return *this;
}
const_iterator& operator-=(difference_type n) {
m_index -= n;
return *this;
}
reference operator[](difference_type n) const { return *(*this + n); }
bool operator<(const const_iterator& other) const {
return m_index < other.m_index;
}
bool operator>(const const_iterator& other) const {
return m_index > other.m_index;
}
bool operator<=(const const_iterator& other) const {
return m_index <= other.m_index;
}
bool operator>=(const const_iterator& other) const {
return m_index >= other.m_index;
}
friend const_iterator operator+(difference_type n,
const const_iterator& it) {
return it + n;
}
};
template <class U> [[nodiscard]] Handle insert(U&& value) {
uint32_t id;
if (!m_free_list.empty()) {
id = m_free_list.back();
m_free_list.pop_back();
m_data[id].emplace(std::forward<U>(value));
m_dense_index[id] = m_dense.size();
} else {
id = m_data.size();
m_data.push_back(std::forward<U>(value));
m_generation.push_back(1);
m_dense_index.emplace_back(m_dense.size());
}
m_dense.push_back(id);
return {id, m_generation[id]};
}
template <typename... Args> Handle emplace(Args&&... args) {
return insert(T(std::forward<Args>(args)...));
}
void erase(Handle h) {
if (!exists(h)) {
return;
}
uint32_t id = h.index;
m_free_list.push_back(id);
uint32_t index = m_dense_index[id];
uint32_t last_id = m_dense.back();
m_dense[index] = last_id;
m_dense_index[last_id] = index;
m_dense.pop_back();
m_data[id].reset();
++m_generation[id];
}
T& operator[](Handle h) {
assert(exists(h));
return m_data[h.index].value();
}
const T& operator[](Handle h) const {
assert(exists(h));
return m_data[h.index].value();
}
bool exists(Handle h) const {
return h.index < m_generation.size() &&
m_generation[h.index] == h.generation;
}
void reserve(size_t n) {
m_data.reserve(n);
m_generation.reserve(n);
m_dense.reserve(n);
m_dense_index.reserve(n);
}
size_t size() const { return m_dense.size(); }
bool empty() const { return m_dense.empty(); }
iterator begin() { return iterator(this, 0); }
iterator end() { return iterator(this, m_dense.size()); }
const_iterator begin() const { return const_iterator(this, 0); }
const_iterator end() const { return const_iterator(this, m_dense.size()); }
const_iterator cbegin() const { return begin(); }
const_iterator cend() const { return end(); }
};
} // namespace Cubed

View File

@@ -0,0 +1,2 @@
DisableFormat: true
SortIncludes: false

View File

@@ -0,0 +1,134 @@
#ifndef ENTT_CONFIG_CONFIG_H
#define ENTT_CONFIG_CONFIG_H
#if __has_include(<entt/ext/config.h>)
# include <entt/ext/config.h>
#endif
#include <version>
#include "version.h"
// NOLINTBEGIN(cppcoreguidelines-macro-usage)
#ifdef ENTT_USE_STL
# define ENTT_FORCE_STL
#endif
#if defined(__cpp_exceptions) && !defined(ENTT_NO_EXCEPTION)
# define ENTT_THROW throw
# define ENTT_TRY try
# define ENTT_CATCH catch(...)
#else
# define ENTT_THROW
# define ENTT_TRY if(true)
# define ENTT_CATCH if(false)
#endif
#if defined(__cpp_consteval)
# define ENTT_CONSTEVAL consteval
#else
# define ENTT_CONSTEVAL constexpr
#endif
#ifdef ENTT_USE_ATOMIC
# include "../stl/atomic.hpp"
# define ENTT_MAYBE_ATOMIC(Type) stl::atomic<Type>
#else
# define ENTT_MAYBE_ATOMIC(Type) Type
#endif
#ifndef ENTT_ID_TYPE
# include "../stl/cstdint.hpp"
# define ENTT_ID_TYPE stl::uint32_t
#else
# include "../stl/cstdint.hpp" // provides coverage for types in the std namespace
#endif
#ifndef ENTT_SPARSE_PAGE
# define ENTT_SPARSE_PAGE 4096
#endif
#ifndef ENTT_PACKED_PAGE
# define ENTT_PACKED_PAGE 1024
#endif
#ifdef ENTT_DISABLE_ASSERT
# undef ENTT_ASSERT
# define ENTT_ASSERT(condition, msg) (void(0))
#elif !defined ENTT_ASSERT
# include <cassert>
# define ENTT_ASSERT(condition, msg) assert(((condition) && (msg)))
#endif
#ifdef ENTT_DISABLE_ASSERT
# undef ENTT_ASSERT_CONSTEXPR
# define ENTT_ASSERT_CONSTEXPR(condition, msg) (void(0))
#elif !defined ENTT_ASSERT_CONSTEXPR
# define ENTT_ASSERT_CONSTEXPR(condition, msg) ENTT_ASSERT(condition, msg)
#endif
#define ENTT_FAIL(msg) ENTT_ASSERT(false, msg);
#ifdef ENTT_NO_ETO
# define ENTT_ETO_TYPE(Type) void
#else
# define ENTT_ETO_TYPE(Type) Type
#endif
#ifdef ENTT_NO_MIXIN
# define ENTT_STORAGE(Mixin, ...) __VA_ARGS__
#else
# define ENTT_STORAGE(Mixin, ...) Mixin<__VA_ARGS__>
#endif
#ifdef ENTT_STANDARD_CPP
# define ENTT_NONSTD false
#else
# define ENTT_NONSTD true
# if defined __clang__ || defined __GNUC__
# define ENTT_PRETTY_FUNCTION __PRETTY_FUNCTION__
# define ENTT_PRETTY_FUNCTION_PREFIX '='
# define ENTT_PRETTY_FUNCTION_SUFFIX ']'
# elif defined _MSC_VER
# define ENTT_PRETTY_FUNCTION __FUNCSIG__
# define ENTT_PRETTY_FUNCTION_PREFIX '<'
# define ENTT_PRETTY_FUNCTION_SUFFIX '>'
# endif
#endif
#ifndef ENTT_EXPORT
# if defined _WIN32 || defined __CYGWIN__ || defined _MSC_VER
# define ENTT_EXPORT __declspec(dllexport)
# define ENTT_IMPORT __declspec(dllimport)
# define ENTT_HIDDEN
# elif defined __GNUC__ && __GNUC__ >= 4
# define ENTT_EXPORT __attribute__((visibility("default")))
# define ENTT_IMPORT __attribute__((visibility("default")))
# define ENTT_HIDDEN __attribute__((visibility("hidden")))
# else /* Unsupported compiler */
# define ENTT_EXPORT
# define ENTT_IMPORT
# define ENTT_HIDDEN
# endif
#endif
#ifndef ENTT_API
# if defined ENTT_API_EXPORT
# define ENTT_API ENTT_EXPORT
# elif defined ENTT_API_IMPORT
# define ENTT_API ENTT_IMPORT
# else /* No API */
# define ENTT_API
# endif
#endif
#if defined _MSC_VER
# pragma detect_mismatch("entt.version", ENTT_VERSION)
# pragma detect_mismatch("entt.noexcept", ENTT_XSTR(ENTT_TRY))
# pragma detect_mismatch("entt.id", ENTT_XSTR(ENTT_ID_TYPE))
# pragma detect_mismatch("entt.nonstd", ENTT_XSTR(ENTT_NONSTD))
#endif
// NOLINTEND(cppcoreguidelines-macro-usage)
#endif

View File

@@ -0,0 +1,11 @@
#ifndef ENTT_CONFIG_MACRO_H
#define ENTT_CONFIG_MACRO_H
// NOLINTBEGIN(cppcoreguidelines-macro-usage)
#define ENTT_STR(arg) #arg
#define ENTT_XSTR(arg) ENTT_STR(arg)
// NOLINTEND(cppcoreguidelines-macro-usage)
#endif

View File

@@ -0,0 +1,18 @@
#ifndef ENTT_CONFIG_VERSION_H
#define ENTT_CONFIG_VERSION_H
#include "macro.h"
// NOLINTBEGIN(cppcoreguidelines-macro-*,modernize-macro-*)
#define ENTT_VERSION_MAJOR 4
#define ENTT_VERSION_MINOR 0
#define ENTT_VERSION_PATCH 0
#define ENTT_VERSION \
ENTT_XSTR(ENTT_VERSION_MAJOR) \
"." ENTT_XSTR(ENTT_VERSION_MINOR) "." ENTT_XSTR(ENTT_VERSION_PATCH)
// NOLINTEND(cppcoreguidelines-macro-*,modernize-macro-*)
#endif

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -0,0 +1,38 @@
#ifndef ENTT_CONTAINER_FWD_HPP
#define ENTT_CONTAINER_FWD_HPP
#include "../stl/functional.hpp"
#include "../stl/memory.hpp"
#include "../stl/utility.hpp"
#include "../stl/vector.hpp"
namespace entt {
template<
typename Key,
typename Type,
typename = stl::hash<Key>,
typename = stl::equal_to<>,
typename = stl::allocator<stl::pair<const Key, Type>>>
class dense_map;
template<
typename Type,
typename = stl::hash<Type>,
typename = stl::equal_to<>,
typename = stl::allocator<Type>>
class dense_set;
template<typename...>
class basic_table;
/**
* @brief Alias declaration for the most common use case.
* @tparam Type Element types.
*/
template<typename... Type>
using table = basic_table<stl::vector<Type>...>;
} // namespace entt
#endif

View File

@@ -0,0 +1,434 @@
#ifndef ENTT_CONTAINER_TABLE_HPP
#define ENTT_CONTAINER_TABLE_HPP
#include "../config/config.h"
#include "../core/iterator.hpp"
#include "../stl/concepts.hpp"
#include "../stl/cstddef.hpp"
#include "../stl/iterator.hpp"
#include "../stl/tuple.hpp"
#include "../stl/utility.hpp"
#include "fwd.hpp"
namespace entt {
/*! @cond ENTT_INTERNAL */
namespace internal {
template<typename... It>
class table_iterator {
template<typename...>
friend class table_iterator;
public:
using value_type = decltype(stl::forward_as_tuple(*stl::declval<It>()...));
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 table_iterator() noexcept
: it{} {}
constexpr table_iterator(It... from) noexcept
: it{from...} {}
template<typename... Other>
requires (stl::constructible_from<It, Other> && ...)
constexpr table_iterator(const table_iterator<Other...> &other) noexcept
: table_iterator{stl::get<Other>(other.it)...} {}
constexpr table_iterator &operator++() noexcept {
return (++stl::get<It>(it), ...), *this;
}
constexpr table_iterator operator++(int) noexcept {
const table_iterator orig = *this;
return ++(*this), orig;
}
constexpr table_iterator &operator--() noexcept {
return (--stl::get<It>(it), ...), *this;
}
constexpr table_iterator operator--(int) noexcept {
const table_iterator orig = *this;
return operator--(), orig;
}
constexpr table_iterator &operator+=(const difference_type value) noexcept {
return ((stl::get<It>(it) += value), ...), *this;
}
constexpr table_iterator operator+(const difference_type value) const noexcept {
table_iterator copy = *this;
return (copy += value);
}
constexpr table_iterator &operator-=(const difference_type value) noexcept {
return (*this += -value);
}
constexpr table_iterator operator-(const difference_type value) const noexcept {
return (*this + -value);
}
[[nodiscard]] constexpr reference operator[](const difference_type value) const noexcept {
return stl::forward_as_tuple(stl::get<It>(it)[value]...);
}
[[nodiscard]] constexpr pointer operator->() const noexcept {
return {operator[](0)};
}
[[nodiscard]] constexpr reference operator*() const noexcept {
return operator[](0);
}
template<typename... Other>
[[nodiscard]] constexpr stl::ptrdiff_t operator-(const table_iterator<Other...> &other) const noexcept {
return stl::get<0>(it) - stl::get<0>(other.it);
}
template<typename... Other>
[[nodiscard]] constexpr bool operator==(const table_iterator<Other...> &other) const noexcept {
return stl::get<0>(it) == stl::get<0>(other.it);
}
template<typename... Other>
[[nodiscard]] constexpr auto operator<=>(const table_iterator<Other...> &other) const noexcept {
return stl::get<0>(it) <=> stl::get<0>(other.it);
}
private:
stl::tuple<It...> it;
};
} // namespace internal
/*! @endcond */
/**
* @brief Basic table implementation.
*
* Internal data structures arrange elements to maximize performance. There are
* no guarantees that objects are returned in the insertion order when iterate
* a table. Do not make assumption on the order in any case.
*
* @tparam Container Sequence container row types.
*/
template<typename... Container>
class basic_table {
using container_type = stl::tuple<Container...>;
public:
/*! @brief Unsigned integer type. */
using size_type = stl::size_t;
/*! @brief Signed integer type. */
using difference_type = stl::ptrdiff_t;
/*! @brief Input iterator type. */
using iterator = internal::table_iterator<typename Container::iterator...>;
/*! @brief Constant input iterator type. */
using const_iterator = internal::table_iterator<typename Container::const_iterator...>;
/*! @brief Reverse iterator type. */
using reverse_iterator = internal::table_iterator<typename Container::reverse_iterator...>;
/*! @brief Constant reverse iterator type. */
using const_reverse_iterator = internal::table_iterator<typename Container::const_reverse_iterator...>;
/*! @brief Default constructor. */
basic_table()
: payload{} {
}
/**
* @brief Copy constructs the underlying containers.
* @param container The containers to copy from.
*/
explicit basic_table(const Container &...container) noexcept
: payload{container...} {
ENTT_ASSERT((((stl::get<Container>(payload).size() * sizeof...(Container)) == (stl::get<Container>(payload).size() + ...)) && ...), "Unexpected container size");
}
/**
* @brief Move constructs the underlying containers.
* @param container The containers to move from.
*/
explicit basic_table(Container &&...container) noexcept
: payload{stl::move(container)...} {
ENTT_ASSERT((((stl::get<Container>(payload).size() * sizeof...(Container)) == (stl::get<Container>(payload).size() + ...)) && ...), "Unexpected container size");
}
/*! @brief Default copy constructor, deleted on purpose. */
basic_table(const basic_table &) = delete;
/**
* @brief Move constructor.
* @param other The instance to move from.
*/
basic_table(basic_table &&other) noexcept
: payload{stl::move(other.payload)} {}
/**
* @brief Constructs the underlying containers using a given allocator.
* @param allocator A valid allocator.
*/
explicit basic_table(const auto &allocator)
: payload{Container{allocator}...} {}
/**
* @brief Copy constructs the underlying containers using a given allocator.
* @tparam Allocator Type of allocator.
* @param container The containers to copy from.
* @param allocator A valid allocator.
*/
template<class Allocator>
basic_table(const Container &...container, const Allocator &allocator) noexcept
: payload{Container{container, allocator}...} {
ENTT_ASSERT((((stl::get<Container>(payload).size() * sizeof...(Container)) == (stl::get<Container>(payload).size() + ...)) && ...), "Unexpected container size");
}
/**
* @brief Move constructs the underlying containers using a given allocator.
* @tparam Allocator Type of allocator.
* @param container The containers to move from.
* @param allocator A valid allocator.
*/
template<class Allocator>
basic_table(Container &&...container, const Allocator &allocator) noexcept
: payload{Container{stl::move(container), allocator}...} {
ENTT_ASSERT((((stl::get<Container>(payload).size() * sizeof...(Container)) == (stl::get<Container>(payload).size() + ...)) && ...), "Unexpected container size");
}
/**
* @brief Allocator-extended move constructor.
* @tparam Allocator Type of allocator.
* @param other The instance to move from.
* @param allocator The allocator to use.
*/
template<class Allocator>
basic_table(basic_table &&other, const Allocator &allocator)
: payload{Container{stl::move(stl::get<Container>(other.payload)), allocator}...} {}
/*! @brief Default destructor. */
~basic_table() = default;
/**
* @brief Default copy assignment operator, deleted on purpose.
* @return This container.
*/
basic_table &operator=(const basic_table &) = delete;
/**
* @brief Move assignment operator.
* @param other The instance to move from.
* @return This container.
*/
basic_table &operator=(basic_table &&other) noexcept {
swap(other);
return *this;
}
/**
* @brief Exchanges the contents with those of a given table.
* @param other Table to exchange the content with.
*/
void swap(basic_table &other) noexcept {
using stl::swap;
swap(payload, other.payload);
}
/**
* @brief Increases the capacity of a table.
*
* If the new capacity is greater than the current capacity, new storage is
* allocated, otherwise the method does nothing.
*
* @param cap Desired capacity.
*/
void reserve(const size_type cap) {
(stl::get<Container>(payload).reserve(cap), ...);
}
/**
* @brief Returns the number of rows that a table has currently allocated
* space for.
* @return Capacity of the table.
*/
[[nodiscard]] size_type capacity() const noexcept {
return stl::get<0>(payload).capacity();
}
/*! @brief Requests the removal of unused capacity. */
void shrink_to_fit() {
(stl::get<Container>(payload).shrink_to_fit(), ...);
}
/**
* @brief Returns the number of rows in a table.
* @return Number of rows.
*/
[[nodiscard]] size_type size() const noexcept {
return stl::get<0>(payload).size();
}
/**
* @brief Checks whether a table is empty.
* @return True if the table is empty, false otherwise.
*/
[[nodiscard]] bool empty() const noexcept {
return stl::get<0>(payload).empty();
}
/**
* @brief Returns an iterator to the beginning.
*
* If the table is empty, the returned iterator will be equal to `end()`.
*
* @return An iterator to the first row of the table.
*/
[[nodiscard]] const_iterator cbegin() const noexcept {
return {stl::get<Container>(payload).cbegin()...};
}
/*! @copydoc cbegin */
[[nodiscard]] const_iterator begin() const noexcept {
return cbegin();
}
/*! @copydoc begin */
[[nodiscard]] iterator begin() noexcept {
return {stl::get<Container>(payload).begin()...};
}
/**
* @brief Returns an iterator to the end.
* @return An iterator to the element following the last row of the table.
*/
[[nodiscard]] const_iterator cend() const noexcept {
return {stl::get<Container>(payload).cend()...};
}
/*! @copydoc cend */
[[nodiscard]] const_iterator end() const noexcept {
return cend();
}
/*! @copydoc end */
[[nodiscard]] iterator end() noexcept {
return {stl::get<Container>(payload).end()...};
}
/**
* @brief Returns a reverse iterator to the beginning.
*
* If the table is empty, the returned iterator will be equal to `rend()`.
*
* @return An iterator to the first row of the reversed table.
*/
[[nodiscard]] const_reverse_iterator crbegin() const noexcept {
return {stl::get<Container>(payload).crbegin()...};
}
/*! @copydoc crbegin */
[[nodiscard]] const_reverse_iterator rbegin() const noexcept {
return crbegin();
}
/*! @copydoc rbegin */
[[nodiscard]] reverse_iterator rbegin() noexcept {
return {stl::get<Container>(payload).rbegin()...};
}
/**
* @brief Returns a reverse iterator to the end.
* @return An iterator to the element following the last row of the reversed
* table.
*/
[[nodiscard]] const_reverse_iterator crend() const noexcept {
return {stl::get<Container>(payload).crend()...};
}
/*! @copydoc crend */
[[nodiscard]] const_reverse_iterator rend() const noexcept {
return crend();
}
/*! @copydoc rend */
[[nodiscard]] reverse_iterator rend() noexcept {
return {stl::get<Container>(payload).rend()...};
}
/**
* @brief Appends a row to the end of a table.
* @tparam Args Types of arguments to use to construct the row data.
* @param args Parameters to use to construct the row data.
* @return A reference to the newly created row data.
*/
template<typename... Args>
stl::tuple<typename Container::value_type &...> emplace(Args &&...args) {
if constexpr(sizeof...(Args) == 0u) {
return stl::forward_as_tuple(stl::get<Container>(payload).emplace_back()...);
} else {
return stl::forward_as_tuple(stl::get<Container>(payload).emplace_back(stl::forward<Args>(args))...);
}
}
/**
* @brief Removes a row from a table.
* @param pos An iterator to the row to remove.
* @return An iterator following the removed row.
*/
iterator erase(const_iterator pos) {
const auto diff = pos - begin();
return {stl::get<Container>(payload).erase(stl::get<Container>(payload).begin() + diff)...};
}
/**
* @brief Removes a row from a table.
* @param pos Index of the row to remove.
*/
void erase(const size_type pos) {
ENTT_ASSERT(pos < size(), "Index out of bounds");
erase(begin() + static_cast<difference_type>(pos));
}
/**
* @brief Returns the row data at specified location.
* @param pos The row for which to return the data.
* @return The row data at specified location.
*/
[[nodiscard]] stl::tuple<const typename Container::value_type &...> operator[](const size_type pos) const {
ENTT_ASSERT(pos < size(), "Index out of bounds");
return stl::forward_as_tuple(stl::get<Container>(payload)[pos]...);
}
/*! @copydoc operator[] */
[[nodiscard]] stl::tuple<typename Container::value_type &...> operator[](const size_type pos) {
ENTT_ASSERT(pos < size(), "Index out of bounds");
return stl::forward_as_tuple(stl::get<Container>(payload)[pos]...);
}
/*! @brief Clears a table. */
void clear() {
(stl::get<Container>(payload).clear(), ...);
}
private:
container_type payload;
};
} // namespace entt
/*! @cond ENTT_INTERNAL */
#include <utility>
namespace std {
template<typename... Container, typename Allocator>
struct uses_allocator<entt::basic_table<Container...>, Allocator>
: entt::stl::bool_constant<(entt::stl::uses_allocator_v<Container, Allocator> && ...)> {};
} // namespace std
/*! @endcond */
#endif

View File

@@ -0,0 +1,143 @@
#ifndef ENTT_CORE_ALGORITHM_HPP
#define ENTT_CORE_ALGORITHM_HPP
#include "../stl/algorithm.hpp"
#include "../stl/concepts.hpp"
#include "../stl/cstddef.hpp"
#include "../stl/functional.hpp"
#include "../stl/iterator.hpp"
#include "../stl/utility.hpp"
#include "../stl/vector.hpp"
namespace entt {
/**
* @brief Function object to wrap `stl::sort` in a class type.
*
* Unfortunately, `stl::sort` cannot be passed as template argument to a class
* template or a function template.<br/>
* This class fills the gap by wrapping some flavors of `stl::sort` in a
* function object.
*/
struct std_sort {
/**
* @brief Sorts the elements in a range.
*
* Sorts the elements in a range using the given binary comparison function.
*
* @tparam Compare Type of comparison function object.
* @tparam Args Types of arguments to forward to the sort function.
* @param first An iterator to the first element of the range to sort.
* @param last An iterator past the last element of the range to sort.
* @param compare A valid comparison function object.
* @param args Arguments to forward to the sort function, if any.
*/
template<typename Compare = stl::less<>, typename... Args>
void operator()(stl::random_access_iterator auto first, stl::random_access_iterator auto last, Compare compare = Compare{}, Args &&...args) const {
stl::sort(stl::forward<Args>(args)..., stl::move(first), stl::move(last), stl::move(compare));
}
};
/*! @brief Function object for performing insertion sort. */
struct insertion_sort {
/**
* @brief Sorts the elements in a range.
*
* Sorts the elements in a range using the given binary comparison function.
*
* @tparam Compare Type of comparison function object.
* @param first An iterator to the first element of the range to sort.
* @param last An iterator past the last element of the range to sort.
* @param compare A valid comparison function object.
*/
template<typename Compare = stl::less<>>
void operator()(stl::random_access_iterator auto first, stl::random_access_iterator auto last, Compare compare = Compare{}) const {
if(first < last) {
for(auto it = first + 1; it < last; ++it) {
auto value = stl::move(*it);
auto pre = it;
// NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic)
for(; pre > first && compare(value, *(pre - 1)); --pre) {
*pre = stl::move(*(pre - 1));
}
// NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic)
*pre = stl::move(value);
}
}
}
};
/**
* @brief Function object for performing LSD radix sort.
* @tparam Bit Number of bits processed per pass.
* @tparam N Maximum number of bits to sort.
*/
template<stl::size_t Bit, stl::size_t N>
requires ((N % Bit) == 0) // The maximum number of bits to sort must be a multiple of the number of bits processed per pass
struct radix_sort {
/**
* @brief Sorts the elements in a range.
*
* Sorts the elements in a range using the given _getter_ to access the
* actual data to be sorted.
*
* This implementation is inspired by the online book
* [Physically Based Rendering](http://www.pbr-book.org/3ed-2018/Primitives_and_Intersection_Acceleration/Bounding_Volume_Hierarchies.html#RadixSort).
*
* @tparam It Type of random access iterator.
* @tparam Getter Type of _getter_ function object.
* @param first An iterator to the first element of the range to sort.
* @param last An iterator past the last element of the range to sort.
* @param getter A valid _getter_ function object.
*/
template<stl::random_access_iterator It, typename Getter = stl::identity>
void operator()(It first, It last, Getter getter = Getter{}) const {
if(first < last) {
constexpr auto passes = N / Bit;
using value_type = stl::iterator_traits<It>::value_type;
using difference_type = stl::iterator_traits<It>::difference_type;
stl::vector<value_type> aux(static_cast<stl::size_t>(stl::distance(first, last)));
auto part = [getter = stl::move(getter)](auto from, auto to, auto out, auto start) {
constexpr auto mask = (1 << Bit) - 1;
constexpr auto buckets = 1 << Bit;
// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays, modernize-avoid-c-arrays, misc-const-correctness)
stl::size_t count[buckets]{};
for(auto it = from; it != to; ++it) {
++count[(getter(*it) >> start) & mask];
}
// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays, modernize-avoid-c-arrays)
stl::size_t index[buckets]{};
for(stl::size_t pos{}, end = buckets - 1u; pos < end; ++pos) {
index[pos + 1u] = index[pos] + count[pos];
}
for(auto it = from; it != to; ++it) {
const auto pos = index[(getter(*it) >> start) & mask]++;
out[static_cast<difference_type>(pos)] = stl::move(*it);
}
};
for(stl::size_t pass = 0; pass < (passes & ~1u); pass += 2) {
part(first, last, aux.begin(), pass * Bit);
part(aux.begin(), aux.end(), first, (pass + 1) * Bit);
}
if constexpr(passes & 1) {
part(first, last, aux.begin(), (passes - 1) * Bit);
stl::move(aux.begin(), aux.end(), first);
}
}
}
};
} // namespace entt
#endif

623
include/entt/core/any.hpp Normal file
View File

@@ -0,0 +1,623 @@
#ifndef ENTT_CORE_ANY_HPP
#define ENTT_CORE_ANY_HPP
#include "../config/config.h"
#include "../core/concepts.hpp"
#include "../stl/concepts.hpp"
#include "../stl/cstddef.hpp"
#include "../stl/cstdint.hpp"
#include "../stl/memory.hpp"
#include "../stl/type_traits.hpp"
#include "../stl/utility.hpp"
#include "fwd.hpp"
#include "type_info.hpp"
#include "type_traits.hpp"
#include "utility.hpp"
namespace entt {
/*! @cond ENTT_INTERNAL */
namespace internal {
enum class any_request : stl::uint8_t {
info,
transfer,
assign,
compare,
copy,
move
};
template<stl::size_t Len, stl::size_t Align>
struct basic_any_storage {
static constexpr bool has_buffer = true;
union {
const void *instance{};
// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays, modernize-avoid-c-arrays)
alignas(Align) stl::byte buffer[Len];
};
};
template<stl::size_t Align>
struct basic_any_storage<0u, Align> {
static constexpr bool has_buffer = false;
const void *instance{};
};
template<typename Type, stl::size_t Len, stl::size_t Align>
// NOLINTNEXTLINE(bugprone-sizeof-expression)
struct in_situ: stl::bool_constant<(Len != 0u) && alignof(Type) <= Align && sizeof(Type) <= Len && stl::is_nothrow_move_constructible_v<Type>> {};
template<stl::size_t Len, stl::size_t Align>
struct in_situ<void, Len, Align>: stl::false_type {};
} // namespace internal
/*! @endcond */
/**
* @brief A SBO friendly, type-safe container for single values of any type.
* @tparam Len Size of the buffer reserved for the small buffer optimization.
* @tparam Align Optional alignment requirement.
*/
template<stl::size_t Len, stl::size_t Align>
class basic_any: private internal::basic_any_storage<Len, Align> {
using request = internal::any_request;
using base_type = internal::basic_any_storage<Len, Align>;
using vtable_type = const void *(const request, const basic_any &, const void *);
using deleter_type = void(const basic_any &);
template<typename Type>
static constexpr bool in_situ_v = internal::in_situ<Type, Len, Align>::value;
template<cvref_unqualified Type>
static const void *basic_vtable(const request req, const basic_any &value, const void *other) {
switch(const auto *elem = static_cast<const Type *>(value.data()); req) {
using enum internal::any_request;
case info:
return &type_id<Type>();
case transfer:
if constexpr(stl::is_move_assignable_v<Type>) {
// NOLINTNEXTLINE(bugprone-casting-through-void)
*const_cast<Type *>(elem) = stl::move(*static_cast<Type *>(const_cast<void *>(other)));
return other;
}
[[fallthrough]];
case assign:
if constexpr(stl::is_copy_assignable_v<Type>) {
*const_cast<Type *>(elem) = *static_cast<const Type *>(other);
return other;
}
break;
case compare:
if constexpr(!stl::is_function_v<Type> && !stl::is_array_v<Type> && is_equality_comparable_v<Type>) {
return (*elem == *static_cast<const Type *>(other)) ? other : nullptr;
} else {
return (elem == other) ? other : nullptr;
}
case copy:
if constexpr(stl::is_copy_constructible_v<Type>) {
// NOLINTNEXTLINE(bugprone-casting-through-void)
static_cast<basic_any *>(const_cast<void *>(other))->initialize<Type>(*elem);
}
break;
case move:
ENTT_ASSERT(value.mode == any_policy::embedded, "Unexpected policy");
if constexpr(in_situ_v<Type>) {
// NOLINTNEXTLINE(bugprone-casting-through-void, bugprone-multi-level-implicit-pointer-conversion)
return ::new(&static_cast<basic_any *>(const_cast<void *>(other))->buffer) Type{stl::move(*const_cast<Type *>(elem))};
}
}
return nullptr;
}
template<cvref_unqualified Type>
static void basic_deleter(const basic_any &value) {
ENTT_ASSERT((value.mode == any_policy::dynamic) || ((value.mode == any_policy::embedded) && !stl::is_trivially_destructible_v<Type>), "Unexpected policy");
const auto *elem = static_cast<const Type *>(value.data());
if constexpr(in_situ_v<Type>) {
(value.mode == any_policy::embedded) ? elem->~Type() : (delete elem);
} else if constexpr(stl::is_array_v<Type>) {
delete[] elem;
} else {
delete elem;
}
}
template<typename Type, typename... Args>
void initialize([[maybe_unused]] Args &&...args) {
using plain_type = stl::remove_cvref_t<Type>;
vtable = basic_vtable<plain_type>;
underlying_type = type_hash<plain_type>::value();
if constexpr(stl::is_void_v<Type>) {
deleter = nullptr;
mode = any_policy::empty;
this->instance = nullptr;
} else if constexpr(stl::is_lvalue_reference_v<Type>) {
deleter = nullptr;
mode = stl::is_const_v<stl::remove_reference_t<Type>> ? any_policy::cref : any_policy::ref;
static_assert((stl::is_lvalue_reference_v<Args> && ...) && (sizeof...(Args) == 1u), "Invalid arguments");
// NOLINTNEXTLINE(bugprone-multi-level-implicit-pointer-conversion)
this->instance = (stl::addressof(args), ...);
} else if constexpr(in_situ_v<plain_type>) {
if constexpr(stl::is_trivially_destructible_v<plain_type>) {
deleter = nullptr;
} else {
deleter = &basic_deleter<plain_type>;
}
mode = any_policy::embedded;
if constexpr(stl::is_aggregate_v<plain_type> && (sizeof...(Args) != 0u || !stl::is_default_constructible_v<plain_type>)) {
::new(&this->buffer) plain_type{stl::forward<Args>(args)...};
} else {
// NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
::new(&this->buffer) plain_type(stl::forward<Args>(args)...);
}
} else {
deleter = &basic_deleter<plain_type>;
mode = any_policy::dynamic;
if constexpr(stl::is_aggregate_v<plain_type> && (sizeof...(Args) != 0u || !stl::is_default_constructible_v<plain_type>)) {
this->instance = new plain_type{stl::forward<Args>(args)...};
} else if constexpr(stl::is_array_v<plain_type>) {
static_assert(sizeof...(Args) == 0u, "Invalid arguments");
this->instance = new plain_type[stl::extent_v<plain_type>]();
} else {
this->instance = new plain_type(stl::forward<Args>(args)...);
}
}
}
void invoke_deleter_if_exists() {
if(deleter != nullptr) {
deleter(*this);
}
}
public:
/*! @brief Size of the internal buffer. */
static constexpr auto length = Len;
/*! @brief Alignment requirement. */
static constexpr auto alignment = Align;
/*! @brief Default constructor. */
constexpr basic_any() noexcept
: basic_any{stl::in_place_type<void>} {}
/**
* @brief Constructs a wrapper by directly initializing the new object.
* @tparam Type Type of object to use to initialize the wrapper.
* @tparam Args Types of arguments to use to construct the new instance.
* @param args Parameters to use to construct the instance.
*/
template<typename Type, typename... Args>
explicit basic_any(stl::in_place_type_t<Type>, Args &&...args)
: base_type{} {
initialize<Type>(stl::forward<Args>(args)...);
}
/**
* @brief Constructs a wrapper taking ownership of the passed object.
* @tparam Type Type of object to use to initialize the wrapper.
* @param value A pointer to an object to take ownership of.
*/
template<typename Type>
requires (!stl::is_const_v<Type> && !stl::is_void_v<Type>)
explicit basic_any(stl::in_place_t, Type *value)
: base_type{} {
if(value == nullptr) {
initialize<void>();
} else {
initialize<Type &>(*value);
deleter = &basic_deleter<Type>;
mode = any_policy::dynamic;
}
}
/**
* @brief Constructs a wrapper from a given value.
* @tparam Type Type of object to use to initialize the wrapper.
* @param value An instance of an object to use to initialize the wrapper.
*/
template<typename Type>
requires (!stl::same_as<stl::remove_cvref_t<Type>, basic_any>)
basic_any(Type &&value)
: basic_any{stl::in_place_type<stl::decay_t<Type>>, stl::forward<Type>(value)} {}
/**
* @brief Copy constructor.
* @param other The instance to copy from.
*/
basic_any(const basic_any &other)
: basic_any{} {
other.vtable(request::copy, other, this);
}
/**
* @brief Move constructor.
* @param other The instance to move from.
*/
basic_any(basic_any &&other) noexcept
: base_type{},
vtable{other.vtable},
deleter{other.deleter},
underlying_type{other.underlying_type},
mode{other.mode} {
if(other.mode == any_policy::embedded) {
other.vtable(request::move, other, this);
} else if(other.mode != any_policy::empty) {
this->instance = stl::exchange(other.instance, nullptr);
}
}
/*! @brief Frees the internal buffer, whatever it means. */
~basic_any() {
invoke_deleter_if_exists();
}
/**
* @brief Copy assignment operator.
* @param other The instance to copy from.
* @return This any object.
*/
basic_any &operator=(const basic_any &other) {
if(this != &other) {
invoke_deleter_if_exists();
if(other) {
other.vtable(request::copy, other, this);
} else {
initialize<void>();
}
}
return *this;
}
/**
* @brief Move assignment operator.
* @param other The instance to move from.
* @return This any object.
*/
basic_any &operator=(basic_any &&other) noexcept {
if(this != &other) {
invoke_deleter_if_exists();
if(other.mode == any_policy::embedded) {
other.vtable(request::move, other, this);
} else if(other.mode != any_policy::empty) {
this->instance = stl::exchange(other.instance, nullptr);
}
vtable = other.vtable;
deleter = other.deleter;
underlying_type = other.underlying_type;
mode = other.mode;
}
return *this;
}
/**
* @brief Value assignment operator.
* @tparam Type Type of object to use to initialize the wrapper.
* @param value An instance of an object to use to initialize the wrapper.
* @return This any object.
*/
template<typename Type>
requires (!stl::same_as<stl::remove_cvref_t<Type>, basic_any>)
basic_any &operator=(Type &&value) {
emplace<stl::decay_t<Type>>(stl::forward<Type>(value));
return *this;
}
/**
* @brief Returns false if a wrapper is empty, true otherwise.
* @return False if the wrapper is empty, true otherwise.
*/
[[nodiscard]] bool has_value() const noexcept {
return (mode != any_policy::empty);
}
/**
* @brief Returns false if the wrapper does not contain the expected type,
* true otherwise.
* @param req Expected type.
* @return False if the wrapper does not contain the expected type, true
* otherwise.
*/
[[nodiscard]] bool has_value(const type_info &req) const noexcept {
return (underlying_type == req.hash());
}
/**
* @brief Returns false if the wrapper does not contain the expected type,
* true otherwise.
* @tparam Type Expected type.
* @return False if the wrapper does not contain the expected type, true
* otherwise.
*/
template<cvref_unqualified Type>
[[nodiscard]] bool has_value() const noexcept {
return (underlying_type == type_hash<Type>::value());
}
/**
* @brief Returns the object type info if any, `type_id<void>()` otherwise.
* @return The object type info if any, `type_id<void>()` otherwise.
*/
[[nodiscard]] const type_info &info() const noexcept {
return *static_cast<const type_info *>(vtable(request::info, *this, nullptr));
}
/**
* @brief Returns an opaque pointer to the contained instance.
* @return An opaque pointer the contained instance, if any.
*/
[[nodiscard]] const void *data() const noexcept {
if constexpr(base_type::has_buffer) {
return (mode == any_policy::embedded) ? &this->buffer : this->instance;
} else {
return this->instance;
}
}
/**
* @brief Returns an opaque pointer to the contained instance.
* @param req Expected type.
* @return An opaque pointer the contained instance, if any.
*/
[[nodiscard]] const void *data(const type_info &req) const noexcept {
return has_value(req) ? data() : nullptr;
}
/**
* @brief Returns an opaque pointer to the contained instance.
* @tparam Type Expected type.
* @return An opaque pointer the contained instance, if any.
*/
template<typename Type>
[[nodiscard]] const Type *data() const noexcept {
return has_value<stl::remove_const_t<Type>>() ? static_cast<const Type *>(data()) : nullptr;
}
/**
* @brief Returns an opaque pointer to the contained instance.
* @return An opaque pointer the contained instance, if any.
*/
[[nodiscard]] void *data() noexcept {
return (mode == any_policy::cref) ? nullptr : const_cast<void *>(stl::as_const(*this).data());
}
/**
* @brief Returns an opaque pointer to the contained instance.
* @param req Expected type.
* @return An opaque pointer the contained instance, if any.
*/
[[nodiscard]] void *data(const type_info &req) noexcept {
return (mode == any_policy::cref) ? nullptr : const_cast<void *>(stl::as_const(*this).data(req));
}
/**
* @brief Returns an opaque pointer to the contained instance.
* @tparam Type Expected type.
* @return An opaque pointer the contained instance, if any.
*/
template<typename Type>
[[nodiscard]] Type *data() noexcept {
if constexpr(stl::is_const_v<Type>) {
return stl::as_const(*this).template data<stl::remove_const_t<Type>>();
} else {
return (mode == any_policy::cref) ? nullptr : const_cast<Type *>(stl::as_const(*this).template data<stl::remove_const_t<Type>>());
}
}
/**
* @brief Replaces the contained object by creating a new instance directly.
* @tparam Type Type of object to use to initialize the wrapper.
* @tparam Args Types of arguments to use to construct the new instance.
* @param args Parameters to use to construct the instance.
*/
template<typename Type, typename... Args>
void emplace(Args &&...args) {
invoke_deleter_if_exists();
initialize<Type>(stl::forward<Args>(args)...);
}
/**
* @brief Assigns a value to the contained object without replacing it.
* @param other The value to assign to the contained object.
* @return True in case of success, false otherwise.
*/
bool assign(const basic_any &other) {
if(other && (mode != any_policy::cref) && (underlying_type == other.underlying_type)) {
return (vtable(request::assign, *this, other.data()) != nullptr);
}
return false;
}
/*! @copydoc assign */
// NOLINTNEXTLINE(cppcoreguidelines-rvalue-reference-param-not-moved)
bool assign(basic_any &&other) {
if(other && (mode != any_policy::cref) && (underlying_type == other.underlying_type)) {
return (other.mode == any_policy::cref) ? (vtable(request::assign, *this, stl::as_const(other).data()) != nullptr) : (vtable(request::transfer, *this, other.data()) != nullptr);
}
return false;
}
/*! @brief Destroys contained object */
void reset() {
invoke_deleter_if_exists();
initialize<void>();
}
/**
* @brief Returns false if a wrapper is empty, true otherwise.
* @return False if the wrapper is empty, true otherwise.
*/
[[nodiscard]] explicit operator bool() const noexcept {
return has_value();
}
/**
* @brief Checks if two wrappers differ in their content.
* @param other Wrapper with which to compare.
* @return False if the two objects differ in their content, true otherwise.
*/
[[nodiscard]] bool operator==(const basic_any &other) const noexcept {
if(other && (underlying_type == other.underlying_type)) {
return (vtable(request::compare, *this, other.data()) != nullptr);
}
return (!*this && !other);
}
/**
* @brief Aliasing constructor.
* @return A wrapper that shares a reference to an unmanaged object.
*/
[[nodiscard]] basic_any as_ref() noexcept {
basic_any other = stl::as_const(*this).as_ref();
switch(mode) {
using enum any_policy;
case cref:
case empty:
other.mode = mode;
break;
default:
other.mode = any_policy::ref;
break;
}
return other;
}
/*! @copydoc as_ref */
[[nodiscard]] basic_any as_ref() const noexcept {
basic_any other{};
other.instance = data();
other.vtable = vtable;
other.underlying_type = underlying_type;
other.mode = any_policy::cref;
return other;
}
/**
* @brief Returns true if a wrapper owns its object, false otherwise.
* @return True if the wrapper owns its object, false otherwise.
*/
[[nodiscard]] bool owner() const noexcept {
return (mode == any_policy::dynamic || mode == any_policy::embedded);
}
/**
* @brief Returns the current mode of an any object.
* @return The current mode of the any object.
*/
[[nodiscard]] any_policy policy() const noexcept {
return mode;
}
private:
vtable_type *vtable{};
deleter_type *deleter{};
id_type underlying_type{};
any_policy mode{};
};
/**
* @brief Performs type-safe access to the contained object.
* @tparam Type Type to which conversion is required.
* @tparam Len Size of the buffer reserved for the small buffer optimization.
* @tparam Align Alignment requirement.
* @param data Target any object.
* @return The element converted to the requested type.
*/
template<typename Type, stl::size_t Len, stl::size_t Align>
[[nodiscard]] stl::remove_const_t<Type> any_cast(const basic_any<Len, Align> &data) noexcept {
const auto *const instance = any_cast<stl::remove_reference_t<Type>>(&data);
ENTT_ASSERT(instance, "Invalid instance");
return static_cast<Type>(*instance);
}
/*! @copydoc any_cast */
template<typename Type, stl::size_t Len, stl::size_t Align>
[[nodiscard]] stl::remove_const_t<Type> any_cast(basic_any<Len, Align> &data) noexcept {
// forces const on non-reference types to make them work also with wrappers for const references
auto *const instance = any_cast<stl::remove_reference_t<const Type>>(&data);
ENTT_ASSERT(instance, "Invalid instance");
return static_cast<Type>(*instance);
}
/*! @copydoc any_cast */
template<typename Type, stl::size_t Len, stl::size_t Align>
// NOLINTNEXTLINE(cppcoreguidelines-rvalue-reference-param-not-moved)
[[nodiscard]] stl::remove_const_t<Type> any_cast(basic_any<Len, Align> &&data) noexcept {
if constexpr(stl::is_copy_constructible_v<stl::remove_cvref_t<Type>>) {
if(auto *const instance = any_cast<stl::remove_reference_t<Type>>(&data); instance) {
return static_cast<Type>(stl::move(*instance));
}
return any_cast<Type>(data);
} else {
auto *const instance = any_cast<stl::remove_reference_t<Type>>(&data);
ENTT_ASSERT(instance, "Invalid instance");
return static_cast<Type>(stl::move(*instance));
}
}
/*! @copydoc any_cast */
template<typename Type, stl::size_t Len, stl::size_t Align>
[[nodiscard]] const Type *any_cast(const basic_any<Len, Align> *data) noexcept {
return data->template data<stl::remove_const_t<Type>>();
}
/*! @copydoc any_cast */
template<typename Type, stl::size_t Len, stl::size_t Align>
[[nodiscard]] Type *any_cast(basic_any<Len, Align> *data) noexcept {
if constexpr(stl::is_const_v<Type>) {
// last attempt to make wrappers for const references return their values
return any_cast<Type>(&stl::as_const(*data));
} else {
return data->template data<Type>();
}
}
/**
* @brief Constructs a wrapper from a given type, passing it all arguments.
* @tparam Type Type of object to use to initialize the wrapper.
* @tparam Len Size of the buffer reserved for the small buffer optimization.
* @tparam Align Optional alignment requirement.
* @tparam Args Types of arguments to use to construct the new instance.
* @param args Parameters to use to construct the instance.
* @return A properly initialized wrapper for an object of the given type.
*/
template<typename Type, stl::size_t Len = basic_any<>::length, stl::size_t Align = basic_any<Len>::alignment, typename... Args>
[[nodiscard]] basic_any<Len, Align> make_any(Args &&...args) {
return basic_any<Len, Align>{stl::in_place_type<Type>, stl::forward<Args>(args)...};
}
/**
* @brief Forwards its argument and avoids copies for lvalue references.
* @tparam Len Size of the buffer reserved for the small buffer optimization.
* @tparam Align Optional alignment requirement.
* @tparam Type Type of argument to use to construct the new instance.
* @param value Parameter to use to construct the instance.
* @return A properly initialized and not necessarily owning wrapper.
*/
template<stl::size_t Len = basic_any<>::length, stl::size_t Align = basic_any<Len>::alignment, typename Type>
[[nodiscard]] basic_any<Len, Align> forward_as_any(Type &&value) {
return basic_any<Len, Align>{stl::in_place_type<Type &&>, stl::forward<Type>(value)};
}
} // namespace entt
#endif

26
include/entt/core/bit.hpp Normal file
View File

@@ -0,0 +1,26 @@
#ifndef ENTT_CORE_BIT_HPP
#define ENTT_CORE_BIT_HPP
#include "../config/config.h"
#include "../stl/bit.hpp"
#include "../stl/concepts.hpp"
#include "../stl/cstddef.hpp"
namespace entt {
/**
* @brief Fast module utility function (powers of two only).
* @tparam Type Unsigned integer type.
* @param value A value of unsigned integer type.
* @param mod _Modulus_, it must be a power of two.
* @return The common remainder.
*/
template<stl::unsigned_integral Type>
[[nodiscard]] constexpr Type fast_mod(const Type value, const stl::size_t mod) noexcept {
ENTT_ASSERT_CONSTEXPR(stl::has_single_bit(mod), "Value must be a power of two");
return static_cast<Type>(value & (mod - 1u));
}
} // namespace entt
#endif

View File

@@ -0,0 +1,266 @@
#ifndef ENTT_CORE_COMPRESSED_PAIR_HPP
#define ENTT_CORE_COMPRESSED_PAIR_HPP
#include "../stl/concepts.hpp"
#include "../stl/cstddef.hpp"
#include "../stl/tuple.hpp"
#include "../stl/type_traits.hpp"
#include "../stl/utility.hpp"
#include "fwd.hpp"
#include "type_traits.hpp"
namespace entt {
/*! @cond ENTT_INTERNAL */
namespace internal {
template<typename Type, stl::size_t>
struct compressed_pair_element {
using reference = Type &;
using const_reference = const Type &;
// NOLINTNEXTLINE(modernize-use-equals-default)
constexpr compressed_pair_element() noexcept(stl::is_nothrow_default_constructible_v<Type>)
requires stl::default_initializable<Type> {}
template<typename Arg>
constexpr compressed_pair_element(Arg &&arg) noexcept(stl::is_nothrow_constructible_v<Type, Arg>)
requires (!stl::same_as<stl::remove_cvref_t<Arg>, compressed_pair_element>)
: value{stl::forward<Arg>(arg)} {}
template<typename... Args, stl::size_t... Index>
constexpr compressed_pair_element(stl::tuple<Args...> args, stl::index_sequence<Index...>) noexcept(stl::is_nothrow_constructible_v<Type, Args...>)
: value{stl::forward<Args>(stl::get<Index>(args))...} {}
[[nodiscard]] constexpr reference get() noexcept {
return value;
}
[[nodiscard]] constexpr const_reference get() const noexcept {
return value;
}
private:
Type value{};
};
template<typename Type, stl::size_t Tag>
requires is_ebco_eligible_v<Type>
struct compressed_pair_element<Type, Tag>: Type {
using reference = Type &;
using const_reference = const Type &;
using base_type = Type;
constexpr compressed_pair_element() noexcept(stl::is_nothrow_default_constructible_v<base_type>)
requires stl::default_initializable<Type>
: base_type{} {}
template<typename Arg>
constexpr compressed_pair_element(Arg &&arg) noexcept(stl::is_nothrow_constructible_v<base_type, Arg>)
requires (!stl::same_as<stl::remove_cvref_t<Arg>, compressed_pair_element>)
: base_type{stl::forward<Arg>(arg)} {}
template<typename... Args, stl::size_t... Index>
constexpr compressed_pair_element(stl::tuple<Args...> args, stl::index_sequence<Index...>) noexcept(stl::is_nothrow_constructible_v<base_type, Args...>)
: base_type{stl::forward<Args>(stl::get<Index>(args))...} {}
[[nodiscard]] constexpr reference get() noexcept {
return *this;
}
[[nodiscard]] constexpr const_reference get() const noexcept {
return *this;
}
};
} // namespace internal
/*! @endcond */
/**
* @brief A compressed pair.
*
* A pair that exploits the _Empty Base Class Optimization_ (or _EBCO_) to
* reduce its final size to a minimum.
*
* @tparam First The type of the first element that the pair stores.
* @tparam Second The type of the second element that the pair stores.
*/
template<typename First, typename Second>
class compressed_pair final
: internal::compressed_pair_element<First, 0u>,
internal::compressed_pair_element<Second, 1u> {
using first_base = internal::compressed_pair_element<First, 0u>;
using second_base = internal::compressed_pair_element<Second, 1u>;
public:
/*! @brief The type of the first element that the pair stores. */
using first_type = First;
/*! @brief The type of the second element that the pair stores. */
using second_type = Second;
/**
* @brief Default constructor, conditionally enabled.
*
* This constructor is only available when the types that the pair stores
* are both at least default constructible.
*/
constexpr compressed_pair() noexcept(stl::is_nothrow_default_constructible_v<first_base> && stl::is_nothrow_default_constructible_v<second_base>)
requires stl::default_initializable<first_type> && stl::default_initializable<second_type>
: first_base{},
second_base{} {}
/**
* @brief Copy constructor.
* @param other The instance to copy from.
*/
constexpr compressed_pair(const compressed_pair &other) = default;
/**
* @brief Move constructor.
* @param other The instance to move from.
*/
constexpr compressed_pair(compressed_pair &&other) noexcept = default;
/**
* @brief Constructs a pair from its values.
* @tparam Arg Type of value to use to initialize the first element.
* @tparam Other Type of value to use to initialize the second element.
* @param arg Value to use to initialize the first element.
* @param other Value to use to initialize the second element.
*/
template<typename Arg, typename Other>
constexpr compressed_pair(Arg &&arg, Other &&other) noexcept(stl::is_nothrow_constructible_v<first_base, Arg> && stl::is_nothrow_constructible_v<second_base, Other>)
: first_base{stl::forward<Arg>(arg)},
second_base{stl::forward<Other>(other)} {}
/**
* @brief Constructs a pair by forwarding the arguments to its parts.
* @tparam Args Types of arguments to use to initialize the first element.
* @tparam Other Types of arguments to use to initialize the second element.
* @param args Arguments to use to initialize the first element.
* @param other Arguments to use to initialize the second element.
*/
template<typename... Args, typename... Other>
constexpr compressed_pair(stl::piecewise_construct_t, stl::tuple<Args...> args, stl::tuple<Other...> other) noexcept(stl::is_nothrow_constructible_v<first_base, Args...> && stl::is_nothrow_constructible_v<second_base, Other...>)
: first_base{stl::move(args), stl::index_sequence_for<Args...>{}},
second_base{stl::move(other), stl::index_sequence_for<Other...>{}} {}
/*! @brief Default destructor. */
~compressed_pair() = default;
/**
* @brief Copy assignment operator.
* @param other The instance to copy from.
* @return This compressed pair object.
*/
constexpr compressed_pair &operator=(const compressed_pair &other) = default;
/**
* @brief Move assignment operator.
* @param other The instance to move from.
* @return This compressed pair object.
*/
constexpr compressed_pair &operator=(compressed_pair &&other) noexcept = default;
/**
* @brief Returns the first element that a pair stores.
* @return The first element that a pair stores.
*/
[[nodiscard]] constexpr first_type &first() noexcept {
return static_cast<first_base &>(*this).get();
}
/*! @copydoc first */
[[nodiscard]] constexpr const first_type &first() const noexcept {
return static_cast<const first_base &>(*this).get();
}
/**
* @brief Returns the second element that a pair stores.
* @return The second element that a pair stores.
*/
[[nodiscard]] constexpr second_type &second() noexcept {
return static_cast<second_base &>(*this).get();
}
/*! @copydoc second */
[[nodiscard]] constexpr const second_type &second() const noexcept {
return static_cast<const second_base &>(*this).get();
}
/**
* @brief Swaps two compressed pair objects.
* @param other The compressed pair to swap with.
*/
constexpr void swap(compressed_pair &other) noexcept {
using stl::swap;
swap(first(), other.first());
swap(second(), other.second());
}
/**
* @brief Extracts an element from the compressed pair.
* @tparam Index An integer value that is either 0 or 1.
* @return Returns a reference to the first element if `Index` is 0 and a
* reference to the second element if `Index` is 1.
*/
template<stl::size_t Index>
requires (Index <= 1u)
[[nodiscard]] constexpr decltype(auto) get() noexcept {
if constexpr(Index == 0u) {
return first();
} else {
return second();
}
}
/*! @copydoc get */
template<stl::size_t Index>
requires (Index <= 1u)
[[nodiscard]] constexpr decltype(auto) get() const noexcept {
if constexpr(Index == 0u) {
return first();
} else {
return second();
}
}
};
/**
* @brief Deduction guide.
* @tparam Type Type of value to use to initialize the first element.
* @tparam Other Type of value to use to initialize the second element.
*/
template<typename Type, typename Other>
compressed_pair(Type &&, Other &&) -> compressed_pair<stl::decay_t<Type>, stl::decay_t<Other>>;
/**
* @brief Swaps two compressed pair objects.
* @tparam First The type of the first element that the pairs store.
* @tparam Second The type of the second element that the pairs store.
* @param lhs A valid compressed pair object.
* @param rhs A valid compressed pair object.
*/
template<typename First, typename Second>
constexpr void swap(compressed_pair<First, Second> &lhs, compressed_pair<First, Second> &rhs) noexcept {
lhs.swap(rhs);
}
} // namespace entt
/*! @cond ENTT_INTERNAL */
#include <utility>
namespace std {
template<typename First, typename Second>
struct tuple_size<entt::compressed_pair<First, Second>>: integral_constant<entt::stl::size_t, 2u> {};
template<entt::stl::size_t Index, typename First, typename Second>
requires (Index <= 1u)
struct tuple_element<Index, entt::compressed_pair<First, Second>>: conditional<Index == 0u, First, Second> {};
} // namespace std
/*! @endcond */
#endif

View File

@@ -0,0 +1,17 @@
#ifndef ENTT_CORE_CONCEPTS_HPP
#define ENTT_CORE_CONCEPTS_HPP
#include "../stl/type_traits.hpp"
namespace entt {
/**
* @brief Specifies that a type is not a cv-qualified reference.
* @tparam Type Type to check.
*/
template<typename Type>
concept cvref_unqualified = stl::is_same_v<stl::remove_cvref_t<Type>, Type>;
} // namespace entt
#endif

102
include/entt/core/enum.hpp Normal file
View File

@@ -0,0 +1,102 @@
#ifndef ENTT_CORE_ENUM_HPP
#define ENTT_CORE_ENUM_HPP
#include "../stl/concepts.hpp"
#include "../stl/type_traits.hpp"
namespace entt {
/**
* @brief Enable bitmask support for enum classes.
* @tparam Type The enum type for which to enable bitmask support.
*/
template<typename Type>
struct enum_as_bitmask: stl::false_type {};
/*! @copydoc enum_as_bitmask */
template<typename Type>
requires requires {
requires stl::is_enum_v<Type>;
{ Type::_entt_enum_as_bitmask } -> stl::same_as<Type>;
}
struct enum_as_bitmask<Type>: stl::true_type {};
/**
* @brief Helper variable template.
* @tparam Type The enum class type for which to enable bitmask support.
*/
template<typename Type>
inline constexpr bool enum_as_bitmask_v = enum_as_bitmask<Type>::value;
/**
* @brief Specifies that an enum class supports bitmask operations.
* @tparam Type Enum class type.
*/
template<typename Type>
// check again that it is an enum to deal with incorrect specializations
concept enum_bitmask = stl::is_enum_v<Type> && enum_as_bitmask_v<Type>;
} // namespace entt
/**
* @brief Operator available for enums for which bitmask support is enabled.
* @tparam Type Enum class type.
* @param lhs The first value to use.
* @param rhs The second value to use.
* @return The result of invoking the operator on the underlying types of the
* two values provided.
*/
template<entt::enum_bitmask Type>
[[nodiscard]] constexpr Type operator|(const Type lhs, const Type rhs) noexcept {
return static_cast<Type>(static_cast<entt::stl::underlying_type_t<Type>>(lhs) | static_cast<entt::stl::underlying_type_t<Type>>(rhs));
}
/*! @copydoc operator| */
template<entt::enum_bitmask Type>
[[nodiscard]] constexpr Type operator&(const Type lhs, const Type rhs) noexcept {
return static_cast<Type>(static_cast<entt::stl::underlying_type_t<Type>>(lhs) & static_cast<entt::stl::underlying_type_t<Type>>(rhs));
}
/*! @copydoc operator| */
template<entt::enum_bitmask Type>
[[nodiscard]] constexpr Type operator^(const Type lhs, const Type rhs) noexcept {
return static_cast<Type>(static_cast<entt::stl::underlying_type_t<Type>>(lhs) ^ static_cast<entt::stl::underlying_type_t<Type>>(rhs));
}
/**
* @brief Operator available for enums for which bitmask support is enabled.
* @tparam Type Enum class type.
* @param value The value to use.
* @return The result of invoking the operator on the underlying types of the
* value provided.
*/
template<entt::enum_bitmask Type>
[[nodiscard]] constexpr Type operator~(const Type value) noexcept {
return static_cast<Type>(~static_cast<entt::stl::underlying_type_t<Type>>(value));
}
/*! @copydoc operator~ */
template<entt::enum_bitmask Type>
[[nodiscard]] constexpr bool operator!(const Type value) noexcept {
return !static_cast<entt::stl::underlying_type_t<Type>>(value);
}
/*! @copydoc operator| */
template<entt::enum_bitmask Type>
constexpr Type &operator|=(Type &lhs, const Type rhs) noexcept {
return (lhs = (lhs | rhs));
}
/*! @copydoc operator| */
template<entt::enum_bitmask Type>
constexpr Type &operator&=(Type &lhs, const Type rhs) noexcept {
return (lhs = (lhs & rhs));
}
/*! @copydoc operator| */
template<entt::enum_bitmask Type>
constexpr Type &operator^=(Type &lhs, const Type rhs) noexcept {
return (lhs = (lhs ^ rhs));
}
#endif

View File

@@ -0,0 +1,35 @@
#ifndef ENTT_CORE_FAMILY_HPP
#define ENTT_CORE_FAMILY_HPP
#include "../config/config.h"
#include "fwd.hpp"
namespace entt {
/**
* @brief Dynamic identifier generator.
*
* Utility class template that can be used to assign unique identifiers to types
* at runtime. Use different specializations to create separate sets of
* identifiers.
*/
template<typename...>
class family {
static auto identifier() noexcept {
static ENTT_MAYBE_ATOMIC(id_type) value{};
return value++;
}
public:
/*! @brief Unsigned integer type. */
using value_type = id_type;
/*! @brief Statically generated unique identifier for the given type. */
template<typename... Type>
// at the time I'm writing, clang crashes during compilation if auto is used instead of value_type
inline static const value_type value = identifier();
};
} // namespace entt
#endif

51
include/entt/core/fwd.hpp Normal file
View File

@@ -0,0 +1,51 @@
#ifndef ENTT_CORE_FWD_HPP
#define ENTT_CORE_FWD_HPP
#include "../config/config.h"
#include "../stl/cstddef.hpp"
#include "../stl/cstdint.hpp"
namespace entt {
/*! @brief Possible modes of an any object. */
enum class any_policy : stl::uint8_t {
/*! @brief Default mode, no element available. */
empty,
/*! @brief Owning mode, dynamically allocated element. */
dynamic,
/*! @brief Owning mode, embedded element. */
embedded,
/*! @brief Aliasing mode, non-const reference. */
ref,
/*! @brief Const aliasing mode, const reference. */
cref
};
// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays, modernize-avoid-c-arrays)
template<stl::size_t Len = sizeof(double[2]), stl::size_t = alignof(double[2])>
class basic_any;
/*! @brief Alias declaration for type identifiers. */
using id_type = ENTT_ID_TYPE;
/*! @brief Alias declaration for the most common use case. */
using any = basic_any<>;
template<typename, typename>
class compressed_pair;
template<typename>
class basic_hashed_string;
/*! @brief Aliases for common character types. */
using hashed_string = basic_hashed_string<char>;
/*! @brief Aliases for common character types. */
using hashed_wstring = basic_hashed_string<wchar_t>;
// NOLINTNEXTLINE(bugprone-forward-declaration-namespace)
struct type_info;
} // namespace entt
#endif

View File

@@ -0,0 +1,260 @@
#ifndef ENTT_CORE_HASHED_STRING_HPP
#define ENTT_CORE_HASHED_STRING_HPP
#include "../stl/cstddef.hpp"
#include "../stl/cstdint.hpp"
#include "fwd.hpp"
namespace entt {
/*! @cond ENTT_INTERNAL */
namespace internal {
template<typename = id_type>
struct fnv_1a_params;
template<>
struct fnv_1a_params<stl::uint32_t> {
static constexpr auto offset = 2166136261;
static constexpr auto prime = 16777619;
};
template<>
struct fnv_1a_params<stl::uint64_t> {
static constexpr auto offset = 14695981039346656037ull;
static constexpr auto prime = 1099511628211ull;
};
template<typename Char>
struct basic_hashed_string {
using value_type = Char;
using size_type = stl::size_t;
using hash_type = id_type;
const value_type *repr{};
hash_type hash{fnv_1a_params<>::offset};
size_type length{};
};
} // namespace internal
/*! @endcond */
/**
* @brief Zero overhead unique identifier.
*
* A hashed string is a compile-time tool that allows users to use
* human-readable identifiers in the codebase while using their numeric
* counterparts at runtime.<br/>
* Because of that, a hashed string can also be used in constant expressions if
* required.
*
* @warning
* This class doesn't take ownership of user-supplied strings nor does it make a
* copy of them.
*
* @tparam Char Character type.
*/
template<typename Char>
class basic_hashed_string: internal::basic_hashed_string<Char> {
using base_type = internal::basic_hashed_string<Char>;
using params = internal::fnv_1a_params<>;
struct const_wrapper {
// non-explicit constructor on purpose
constexpr const_wrapper(const base_type::value_type *str) noexcept
: repr{str} {}
const base_type::value_type *repr;
};
public:
/*! @brief Character type. */
using value_type = base_type::value_type;
/*! @brief Unsigned integer type. */
using size_type = base_type::size_type;
/*! @brief Unsigned integer type. */
using hash_type = base_type::hash_type;
/**
* @brief Returns directly the numeric representation of a string view.
* @param str Human-readable identifier.
* @param len Length of the string to hash.
* @return The numeric representation of the string.
*/
[[nodiscard]] static constexpr hash_type value(const value_type *str, const size_type len) noexcept {
return basic_hashed_string{str, len};
}
/**
* @brief Returns directly the numeric representation of a string.
* @tparam N Number of characters of the identifier.
* @param str Human-readable identifier.
* @return The numeric representation of the string.
*/
template<stl::size_t N>
// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays, modernize-avoid-c-arrays)
[[nodiscard]] static ENTT_CONSTEVAL hash_type value(const value_type (&str)[N]) noexcept {
return basic_hashed_string{str};
}
/**
* @brief Returns directly the numeric representation of a string.
* @param wrapper Helps achieving the purpose by relying on overloading.
* @return The numeric representation of the string.
*/
[[nodiscard]] static constexpr hash_type value(const_wrapper wrapper) noexcept {
return basic_hashed_string{wrapper};
}
/*! @brief Constructs an empty hashed string. */
constexpr basic_hashed_string() noexcept
: basic_hashed_string{nullptr, 0u} {}
/**
* @brief Constructs a hashed string from a string view.
* @param str Human-readable identifier.
* @param len Length of the string to hash.
*/
constexpr basic_hashed_string(const value_type *str, const size_type len) noexcept
// NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
: base_type{str} {
// NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic)
for(; base_type::length < len; ++base_type::length) {
base_type::hash = (base_type::hash ^ static_cast<id_type>(str[base_type::length])) * params::prime;
}
// NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic)
}
/**
* @brief Constructs a hashed string from an array of const characters.
* @tparam N Number of characters of the identifier.
* @param str Human-readable identifier.
*/
template<stl::size_t N>
// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays, modernize-avoid-c-arrays)
ENTT_CONSTEVAL basic_hashed_string(const value_type (&str)[N]) noexcept
// NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay)
: base_type{str} {
for(; str[base_type::length]; ++base_type::length) {
base_type::hash = (base_type::hash ^ static_cast<id_type>(str[base_type::length])) * params::prime;
}
}
/**
* @brief Explicit constructor on purpose to avoid constructing a hashed
* string directly from a `const value_type *`.
*
* @warning
* The lifetime of the string is not extended nor is it copied.
*
* @param wrapper Helps achieving the purpose by relying on overloading.
*/
explicit constexpr basic_hashed_string(const_wrapper wrapper) noexcept
: base_type{wrapper.repr} {
// NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic)
for(; wrapper.repr[base_type::length]; ++base_type::length) {
base_type::hash = (base_type::hash ^ static_cast<id_type>(wrapper.repr[base_type::length])) * params::prime;
}
// NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic)
}
/**
* @brief Returns the size of a hashed string.
* @return The size of the hashed string.
*/
[[nodiscard]] constexpr size_type size() const noexcept {
return base_type::length;
}
/**
* @brief Returns the human-readable representation of a hashed string.
* @return The string used to initialize the hashed string.
*/
[[nodiscard]] constexpr const value_type *data() const noexcept {
return base_type::repr;
}
/**
* @brief Returns the numeric representation of a hashed string.
* @return The numeric representation of the hashed string.
*/
[[nodiscard]] constexpr hash_type value() const noexcept {
return base_type::hash;
}
/*! @copydoc data */
[[nodiscard]] explicit constexpr operator const value_type *() const noexcept {
return data();
}
/**
* @brief Returns the numeric representation of a hashed string.
* @return The numeric representation of the hashed string.
*/
[[nodiscard]] constexpr operator hash_type() const noexcept {
return value();
}
/**
* @brief Compares two hashed strings.
* @param other A valid hashed string.
* @return True if the two hashed strings are identical, false otherwise.
*/
[[nodiscard]] constexpr bool operator==(const basic_hashed_string &other) const noexcept {
return value() == other.value();
}
/**
* @brief Lexicographically compares two hashed strings.
* @param other A valid hashed string.
* @return The relative order between the two hashed strings.
*/
[[nodiscard]] constexpr auto operator<=>(const basic_hashed_string &other) const noexcept {
return value() <=> other.value();
}
};
/**
* @brief Deduction guide.
* @tparam Char Character type.
* @param str Human-readable identifier.
* @param len Length of the string to hash.
*/
template<typename Char>
basic_hashed_string(const Char *str, stl::size_t len) -> basic_hashed_string<Char>;
/**
* @brief Deduction guide.
* @tparam Char Character type.
* @tparam N Number of characters of the identifier.
* @param str Human-readable identifier.
*/
template<typename Char, stl::size_t N>
// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays, modernize-avoid-c-arrays)
basic_hashed_string(const Char (&str)[N]) -> basic_hashed_string<Char>;
inline namespace literals {
/**
* @brief User defined literal for hashed strings.
* @param str The literal without its suffix.
* @return A properly initialized hashed string.
*/
[[nodiscard]] ENTT_CONSTEVAL hashed_string operator""_hs(const char *str, stl::size_t) noexcept {
return hashed_string{str};
}
/**
* @brief User defined literal for hashed wstrings.
* @param str The literal without its suffix.
* @return A properly initialized hashed wstring.
*/
[[nodiscard]] ENTT_CONSTEVAL hashed_wstring operator""_hws(const wchar_t *str, stl::size_t) noexcept {
return hashed_wstring{str};
}
} // namespace literals
} // namespace entt
#endif

View File

@@ -0,0 +1,35 @@
#ifndef ENTT_CORE_IDENT_HPP
#define ENTT_CORE_IDENT_HPP
#include "../stl/cstddef.hpp"
#include "../stl/type_traits.hpp"
#include "../stl/utility.hpp"
#include "fwd.hpp"
#include "type_traits.hpp"
namespace entt {
/**
* @brief Type integral identifiers.
* @tparam Type List of types for which to generate identifiers.
*/
template<typename... Type>
class ident {
template<typename Curr, stl::size_t... Index>
[[nodiscard]] static ENTT_CONSTEVAL id_type get(stl::index_sequence<Index...>) noexcept {
return (0 + ... + (stl::is_same_v<Curr, type_list_element_t<Index, type_list<stl::decay_t<Type>...>>> ? id_type{Index} : id_type{}));
}
public:
/*! @brief Unsigned integer type. */
using value_type = id_type;
/*! @brief Statically generated unique identifier for the given type. */
template<typename Curr>
requires (stl::is_same_v<stl::remove_cvref_t<Curr>, Type> || ...)
static constexpr value_type value = get<stl::remove_cvref_t<Curr>>(stl::index_sequence_for<Type...>{});
};
} // namespace entt
#endif

View File

@@ -0,0 +1,181 @@
#ifndef ENTT_CORE_ITERATOR_HPP
#define ENTT_CORE_ITERATOR_HPP
#include "../stl/concepts.hpp"
#include "../stl/cstddef.hpp"
#include "../stl/iterator.hpp"
#include "../stl/memory.hpp"
#include "../stl/type_traits.hpp"
#include "../stl/utility.hpp"
namespace entt {
/**
* @brief Helper type to use as pointer with input iterators.
* @tparam Type of wrapped value.
*/
template<typename Type>
struct input_iterator_pointer final {
/*! @brief Value type. */
using value_type = Type;
/*! @brief Pointer type. */
using pointer = Type *;
/*! @brief Reference type. */
using reference = Type &;
/**
* @brief Constructs a proxy object by move.
* @param val Value to use to initialize the proxy object.
*/
constexpr input_iterator_pointer(value_type &&val) noexcept(stl::is_nothrow_move_constructible_v<value_type>)
: value{stl::move(val)} {}
/**
* @brief Access operator for accessing wrapped values.
* @return A pointer to the wrapped value.
*/
[[nodiscard]] constexpr pointer operator->() noexcept {
return stl::addressof(value);
}
/**
* @brief Dereference operator for accessing wrapped values.
* @return A reference to the wrapped value.
*/
[[nodiscard]] constexpr reference operator*() noexcept {
return value;
}
private:
Type value;
};
/**
* @brief Plain iota iterator (waiting for C++20).
* @tparam Type Value type.
*/
template<stl::integral Type>
struct iota_iterator final {
/*! @brief Value type, likely an integral one. */
using value_type = Type;
/*! @brief Invalid pointer type. */
using pointer = void;
/*! @brief Non-reference type, same as value type. */
using reference = value_type;
/*! @brief Difference type. */
using difference_type = stl::ptrdiff_t;
/*! @brief Iterator category. */
using iterator_category = stl::input_iterator_tag;
/*! @brief Default constructor. */
constexpr iota_iterator() noexcept
: current{} {}
/**
* @brief Constructs an iota iterator from a given value.
* @param init The initial value assigned to the iota iterator.
*/
constexpr iota_iterator(const value_type init) noexcept
: current{init} {}
/**
* @brief Pre-increment operator.
* @return This iota iterator.
*/
constexpr iota_iterator &operator++() noexcept {
return ++current, *this;
}
/**
* @brief Post-increment operator.
* @return This iota iterator.
*/
constexpr iota_iterator operator++(int) noexcept {
const iota_iterator orig = *this;
return ++(*this), orig;
}
/**
* @brief Dereference operator.
* @return The underlying value.
*/
[[nodiscard]] constexpr reference operator*() const noexcept {
return current;
}
/**
* @brief Comparison operator.
* @param other A properly initialized iota iterator.
* @return True if the two iterators are identical, false otherwise.
*/
[[nodiscard]] constexpr bool operator==(const iota_iterator &other) const noexcept {
return current == other.current;
}
private:
value_type current;
};
/**
* @brief Utility class to create an iterable object from a pair of iterators.
* @tparam It Type of iterator.
* @tparam Sentinel Type of sentinel.
*/
template<stl::input_or_output_iterator It, stl::sentinel_for<It> Sentinel = It>
struct iterable_adaptor final {
/*! @brief Value type. */
using value_type = stl::iterator_traits<It>::value_type;
/*! @brief Iterator type. */
using iterator = It;
/*! @brief Sentinel type. */
using sentinel = Sentinel;
/*! @brief Default constructor. */
constexpr iterable_adaptor() noexcept(stl::is_nothrow_default_constructible_v<iterator> && stl::is_nothrow_default_constructible_v<sentinel>)
: first{},
last{} {}
/**
* @brief Creates an iterable object from a pair of iterators.
* @param from Begin iterator.
* @param to End iterator.
*/
constexpr iterable_adaptor(iterator from, sentinel to) noexcept(stl::is_nothrow_move_constructible_v<iterator> && stl::is_nothrow_move_constructible_v<sentinel>)
: first{stl::move(from)},
last{stl::move(to)} {}
/**
* @brief Returns an iterator to the beginning.
* @return An iterator to the first element of the range.
*/
[[nodiscard]] constexpr iterator begin() const noexcept {
return first;
}
/**
* @brief Returns an iterator to the end.
* @return An iterator to the element following the last element of the
* range.
*/
[[nodiscard]] constexpr sentinel end() const noexcept {
return last;
}
/*! @copydoc begin */
[[nodiscard]] constexpr iterator cbegin() const noexcept {
return begin();
}
/*! @copydoc end */
[[nodiscard]] constexpr sentinel cend() const noexcept {
return end();
}
private:
It first;
Sentinel last;
};
} // namespace entt
#endif

View File

@@ -0,0 +1,225 @@
#ifndef ENTT_CORE_MEMORY_HPP
#define ENTT_CORE_MEMORY_HPP
#include "../config/config.h"
#include "../stl/cstddef.hpp"
#include "../stl/memory.hpp"
#include "../stl/tuple.hpp"
#include "../stl/type_traits.hpp"
#include "../stl/utility.hpp"
namespace entt {
/**
* @brief Utility function to design allocation-aware containers.
* @tparam Allocator Type of allocator.
* @param lhs A valid allocator.
* @param rhs Another valid allocator.
*/
template<typename Allocator>
constexpr void propagate_on_container_copy_assignment([[maybe_unused]] Allocator &lhs, [[maybe_unused]] Allocator &rhs) noexcept {
if constexpr(stl::allocator_traits<Allocator>::propagate_on_container_copy_assignment::value) {
lhs = rhs;
}
}
/**
* @brief Utility function to design allocation-aware containers.
* @tparam Allocator Type of allocator.
* @param lhs A valid allocator.
* @param rhs Another valid allocator.
*/
template<typename Allocator>
constexpr void propagate_on_container_move_assignment([[maybe_unused]] Allocator &lhs, [[maybe_unused]] Allocator &rhs) noexcept {
if constexpr(stl::allocator_traits<Allocator>::propagate_on_container_move_assignment::value) {
lhs = stl::move(rhs);
}
}
/**
* @brief Utility function to design allocation-aware containers.
* @tparam Allocator Type of allocator.
* @param lhs A valid allocator.
* @param rhs Another valid allocator.
*/
template<typename Allocator>
constexpr void propagate_on_container_swap([[maybe_unused]] Allocator &lhs, [[maybe_unused]] Allocator &rhs) noexcept {
if constexpr(stl::allocator_traits<Allocator>::propagate_on_container_swap::value) {
using stl::swap;
swap(lhs, rhs);
} else {
ENTT_ASSERT_CONSTEXPR(lhs == rhs, "Cannot swap the containers");
}
}
/**
* @brief Deleter for allocator-aware unique pointers (waiting for C++20).
* @tparam Allocator Type of allocator used to manage memory and elements.
*/
template<typename Allocator>
struct allocation_deleter: private Allocator {
/*! @brief Allocator type. */
using allocator_type = Allocator;
/*! @brief Pointer type. */
using pointer = stl::allocator_traits<Allocator>::pointer;
/**
* @brief Inherited constructors.
* @param alloc The allocator to use.
*/
constexpr allocation_deleter(const allocator_type &alloc) noexcept(stl::is_nothrow_copy_constructible_v<allocator_type>)
: Allocator{alloc} {}
/**
* @brief Destroys the pointed object and deallocates its memory.
* @param ptr A valid pointer to an object of the given type.
*/
constexpr void operator()(pointer ptr) noexcept(stl::is_nothrow_destructible_v<typename allocator_type::value_type>) {
using alloc_traits = stl::allocator_traits<Allocator>;
alloc_traits::destroy(*this, stl::to_address(ptr));
alloc_traits::deallocate(*this, ptr, 1u);
}
};
/**
* @brief Allows `stl::unique_ptr` to use allocators (waiting for C++20).
* @tparam Type Type of object to allocate for and to construct.
* @tparam Allocator Type of allocator used to manage memory and elements.
* @tparam Args Types of arguments to use to construct the object.
* @param allocator The allocator to use.
* @param args Parameters to use to construct the object.
* @return A properly initialized unique pointer with a custom deleter.
*/
template<typename Type, typename Allocator, typename... Args>
constexpr auto allocate_unique(Allocator &allocator, Args &&...args) {
static_assert(!stl::is_array_v<Type>, "Array types are not supported");
using alloc_traits = stl::allocator_traits<Allocator>::template rebind_traits<Type>;
using allocator_type = alloc_traits::allocator_type;
allocator_type alloc{allocator};
auto ptr = alloc_traits::allocate(alloc, 1u);
ENTT_TRY {
alloc_traits::construct(alloc, stl::to_address(ptr), stl::forward<Args>(args)...);
}
ENTT_CATCH {
alloc_traits::deallocate(alloc, ptr, 1u);
ENTT_THROW;
}
return stl::unique_ptr<Type, allocation_deleter<allocator_type>>{ptr, alloc};
}
/*! @cond ENTT_INTERNAL */
namespace internal {
template<typename Type>
struct uses_allocator_construction {
template<typename Allocator, typename... Params>
static constexpr auto args([[maybe_unused]] const Allocator &allocator, Params &&...params) noexcept {
if constexpr(!stl::uses_allocator_v<Type, Allocator> && stl::is_constructible_v<Type, Params...>) {
return stl::forward_as_tuple(stl::forward<Params>(params)...);
} else {
static_assert(stl::uses_allocator_v<Type, Allocator>, "Ill-formed request");
if constexpr(stl::is_constructible_v<Type, stl::allocator_arg_t, const Allocator &, Params...>) {
return stl::tuple<stl::allocator_arg_t, const Allocator &, Params &&...>{stl::allocator_arg, allocator, stl::forward<Params>(params)...};
} else {
static_assert(stl::is_constructible_v<Type, Params..., const Allocator &>, "Ill-formed request");
return stl::forward_as_tuple(stl::forward<Params>(params)..., allocator);
}
}
}
};
template<typename Type, typename Other>
struct uses_allocator_construction<stl::pair<Type, Other>> {
using type = stl::pair<Type, Other>;
template<typename First, typename Second>
static constexpr auto args(const auto &allocator, stl::piecewise_construct_t, First &&first, Second &&second) noexcept {
return stl::make_tuple(
stl::piecewise_construct,
stl::apply([&allocator](auto &&...curr) { return uses_allocator_construction<Type>::args(allocator, stl::forward<decltype(curr)>(curr)...); }, stl::forward<First>(first)),
stl::apply([&allocator](auto &&...curr) { return uses_allocator_construction<Other>::args(allocator, stl::forward<decltype(curr)>(curr)...); }, stl::forward<Second>(second)));
}
static constexpr auto args(const auto &allocator) noexcept {
return uses_allocator_construction<type>::args(allocator, stl::piecewise_construct, stl::tuple<>{}, stl::tuple<>{});
}
template<typename First, typename Second>
static constexpr auto args(const auto &allocator, First &&first, Second &&second) noexcept {
return uses_allocator_construction<type>::args(allocator, stl::piecewise_construct, stl::forward_as_tuple(stl::forward<First>(first)), stl::forward_as_tuple(stl::forward<Second>(second)));
}
template<typename First, typename Second>
static constexpr auto args(const auto &allocator, const stl::pair<First, Second> &value) noexcept {
return uses_allocator_construction<type>::args(allocator, stl::piecewise_construct, stl::forward_as_tuple(value.first), stl::forward_as_tuple(value.second));
}
template<typename First, typename Second>
static constexpr auto args(const auto &allocator, stl::pair<First, Second> &&value) noexcept {
return uses_allocator_construction<type>::args(allocator, stl::piecewise_construct, stl::forward_as_tuple(stl::move(value.first)), stl::forward_as_tuple(stl::move(value.second)));
}
};
} // namespace internal
/*! @endcond */
/**
* @brief Uses-allocator construction utility (waiting for C++20).
*
* Primarily intended for internal use. Prepares the argument list needed to
* create an object of a given type by means of uses-allocator construction.
*
* @tparam Type Type to return arguments for.
* @tparam Args Types of arguments to use to construct the object.
* @param allocator The allocator to use.
* @param args Parameters to use to construct the object.
* @return The arguments needed to create an object of the given type.
*/
template<typename Type, typename... Args>
constexpr auto uses_allocator_construction_args(const auto &allocator, Args &&...args) noexcept {
return internal::uses_allocator_construction<Type>::args(allocator, stl::forward<Args>(args)...);
}
/**
* @brief Uses-allocator construction utility (waiting for C++20).
*
* Primarily intended for internal use. Creates an object of a given type by
* means of uses-allocator construction.
*
* @tparam Type Type of object to create.
* @tparam Args Types of arguments to use to construct the object.
* @param allocator The allocator to use.
* @param args Parameters to use to construct the object.
* @return A newly created object of the given type.
*/
template<typename Type, typename... Args>
constexpr Type make_obj_using_allocator(const auto &allocator, Args &&...args) {
return stl::make_from_tuple<Type>(internal::uses_allocator_construction<Type>::args(allocator, stl::forward<Args>(args)...));
}
/**
* @brief Uses-allocator construction utility (waiting for C++20).
*
* Primarily intended for internal use. Creates an object of a given type by
* means of uses-allocator construction at an uninitialized memory location.
*
* @tparam Type Type of object to create.
* @tparam Args Types of arguments to use to construct the object.
* @param value Memory location in which to place the object.
* @param allocator The allocator to use.
* @param args Parameters to use to construct the object.
* @return A pointer to the newly created object of the given type.
*/
template<typename Type, typename... Args>
constexpr Type *uninitialized_construct_using_allocator(Type *value, const auto &allocator, Args &&...args) {
return stl::apply([value](auto &&...curr) { return ::new(value) Type(stl::forward<decltype(curr)>(curr)...); }, internal::uses_allocator_construction<Type>::args(allocator, stl::forward<Args>(args)...));
}
} // namespace entt
#endif

View File

@@ -0,0 +1,60 @@
#ifndef ENTT_CORE_MONOSTATE_HPP
#define ENTT_CORE_MONOSTATE_HPP
#include "../config/config.h"
#include "fwd.hpp"
namespace entt {
/**
* @brief Minimal implementation of the monostate pattern.
*
* A minimal, yet complete configuration system built on top of the monostate
* pattern. Thread safe by design, it works only with basic types like `int`s or
* `bool`s.<br/>
* Multiple types and therefore more than one value can be associated with a
* single key. Because of this, users must pay attention to use the same type
* both during an assignment and when they try to read back their data.
* Otherwise, they can incur in unexpected results.
*/
template<id_type>
struct monostate {
/**
* @brief Assigns a value of a specific type to a given key.
* @tparam Type Type of the value to assign.
* @param val User data to assign to the given key.
* @return This monostate object.
*/
template<typename Type>
monostate &operator=(Type val) noexcept {
value<Type> = val;
return *this;
}
/**
* @brief Gets a value of a specific type for a given key.
* @tparam Type Type of the value to get.
* @return Stored value, if any.
*/
template<typename Type>
operator Type() const noexcept {
return value<Type>;
}
private:
template<typename Type>
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
inline static ENTT_MAYBE_ATOMIC(Type) value{};
};
/**
* @brief Helper variable template.
* @tparam Value Value used to differentiate between different variables.
*/
template<id_type Value>
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
inline monostate<Value> monostate_v{};
} // namespace entt
#endif

View File

@@ -0,0 +1,22 @@
#ifndef ENTT_CORE_RANGES_HPP
#define ENTT_CORE_RANGES_HPP
#include <version>
#if defined(__cpp_lib_ranges)
# include <ranges>
# include "iterator.hpp"
namespace std::ranges {
template<class... Args>
inline constexpr bool enable_borrowed_range<entt::iterable_adaptor<Args...>>{true};
template<class... Args>
inline constexpr bool enable_view<entt::iterable_adaptor<Args...>>{true};
} // namespace std::ranges
#endif
#endif

View File

@@ -0,0 +1,90 @@
#ifndef ENTT_CORE_TUPLE_HPP
#define ENTT_CORE_TUPLE_HPP
#include "../stl/tuple.hpp"
#include "../stl/type_traits.hpp"
#include "../stl/utility.hpp"
namespace entt {
/**
* @brief Provides the member constant `value` equal to true if a given type is
* a tuple, false otherwise.
* @tparam Type The type to test.
*/
template<typename Type>
struct is_tuple: stl::false_type {};
/**
* @copybrief is_tuple
* @tparam Args Tuple template arguments.
*/
template<typename... Args>
struct is_tuple<stl::tuple<Args...>>: stl::true_type {};
/**
* @brief Helper variable template.
* @tparam Type The type to test.
*/
template<typename Type>
inline constexpr bool is_tuple_v = is_tuple<Type>::value;
/**
* @brief Utility function to unwrap tuples of a single element.
* @tparam Type Tuple type of any sizes.
* @param value A tuple object of the given type.
* @return The tuple itself if it contains more than one element, the first
* element otherwise.
*/
template<typename Type>
constexpr decltype(auto) unwrap_tuple(Type &&value) noexcept {
if constexpr(stl::tuple_size_v<stl::remove_reference_t<Type>> == 1u) {
return stl::get<0>(stl::forward<Type>(value));
} else {
return stl::forward<Type>(value);
}
}
/**
* @brief Utility class to forward-and-apply tuple objects.
* @tparam Func Type of underlying invocable object.
*/
template<typename Func>
struct forward_apply: private Func {
/**
* @brief Constructs a forward-and-apply object.
* @tparam Args Types of arguments to use to construct the new instance.
* @param args Parameters to use to construct the instance.
*/
template<typename... Args>
constexpr forward_apply(Args &&...args) noexcept(stl::is_nothrow_constructible_v<Func, Args...>)
: Func{stl::forward<Args>(args)...} {}
/**
* @brief Forwards and applies the arguments with the underlying function.
* @tparam Type Tuple-like type to forward to the underlying function.
* @param args Parameters to forward to the underlying function.
* @return Return value of the underlying function, if any.
*/
template<typename Type>
constexpr decltype(auto) operator()(Type &&args) noexcept(noexcept(stl::apply(stl::declval<Func &>(), args))) {
return stl::apply(static_cast<Func &>(*this), stl::forward<Type>(args));
}
/*! @copydoc operator()() */
template<typename Type>
constexpr decltype(auto) operator()(Type &&args) const noexcept(noexcept(stl::apply(stl::declval<const Func &>(), args))) {
return stl::apply(static_cast<const Func &>(*this), stl::forward<Type>(args));
}
};
/**
* @brief Deduction guide.
* @tparam Func Type of underlying invocable object.
*/
template<typename Func>
forward_apply(Func) -> forward_apply<stl::remove_cvref_t<Func>>;
} // namespace entt
#endif

View File

@@ -0,0 +1,232 @@
#ifndef ENTT_CORE_TYPE_INFO_HPP
#define ENTT_CORE_TYPE_INFO_HPP
#include <compare>
#include "../config/config.h"
#include "../stl/string_view.hpp"
#include "../stl/type_traits.hpp"
#include "../stl/utility.hpp"
#include "fwd.hpp"
#include "hashed_string.hpp"
namespace entt {
/*! @cond ENTT_INTERNAL */
namespace internal {
struct ENTT_API type_index final {
[[nodiscard]] static id_type next() noexcept {
static ENTT_MAYBE_ATOMIC(id_type) value{};
return value++;
}
};
template<typename Type>
[[nodiscard]] constexpr const char *pretty_function() noexcept {
#if defined ENTT_PRETTY_FUNCTION
return static_cast<const char *>(ENTT_PRETTY_FUNCTION);
#else
return "";
#endif
}
template<typename Type>
[[nodiscard]] constexpr auto stripped_type_name() noexcept {
#if defined ENTT_PRETTY_FUNCTION
const stl::string_view full_name{pretty_function<Type>()};
auto first = full_name.find_first_not_of(' ', full_name.find_first_of(ENTT_PRETTY_FUNCTION_PREFIX) + 1);
auto value = full_name.substr(first, full_name.find_last_of(ENTT_PRETTY_FUNCTION_SUFFIX) - first);
return value;
#else
return stl::string_view{};
#endif
}
template<typename Type, auto = stripped_type_name<Type>().find_first_of('.')>
[[nodiscard]] ENTT_CONSTEVAL stl::string_view type_name(int) noexcept {
constexpr auto value = stripped_type_name<Type>();
return value;
}
template<typename Type>
[[nodiscard]] stl::string_view type_name(char) noexcept {
static const auto value = stripped_type_name<Type>();
return value;
}
template<typename Type, auto = stripped_type_name<Type>().find_first_of('.')>
[[nodiscard]] ENTT_CONSTEVAL id_type type_hash(int) noexcept {
constexpr auto stripped = stripped_type_name<Type>();
constexpr auto value = hashed_string::value(stripped.data(), stripped.size());
return value;
}
template<typename Type>
[[nodiscard]] id_type type_hash(char) noexcept {
static const auto value = [](const auto stripped) {
return hashed_string::value(stripped.data(), stripped.size());
}(stripped_type_name<Type>());
return value;
}
} // namespace internal
/*! @endcond */
/**
* @brief Type sequential identifier.
* @tparam Type Type for which to generate a sequential identifier.
*/
template<typename Type>
struct ENTT_API type_index final {
/**
* @brief Returns the sequential identifier of a given type.
* @return The sequential identifier of a given type.
*/
[[nodiscard]] static id_type value() noexcept {
static const id_type value = internal::type_index::next();
return value;
}
/*! @copydoc value */
[[nodiscard]] constexpr operator id_type() const noexcept {
return value();
}
};
/**
* @brief Type hash.
* @tparam Type Type for which to generate a hash value.
*/
template<typename Type>
struct type_hash final {
/**
* @brief Returns the numeric representation of a given type.
* @return The numeric representation of the given type.
*/
#if defined ENTT_PRETTY_FUNCTION
[[nodiscard]] static constexpr id_type value() noexcept {
return internal::type_hash<Type>(0);
#else
[[nodiscard]] static constexpr id_type value() noexcept {
return type_index<Type>::value();
#endif
}
/*! @copydoc value */
[[nodiscard]] constexpr operator id_type() const noexcept {
return value();
}
};
/**
* @brief Type name.
* @tparam Type Type for which to generate a name.
*/
template<typename Type>
struct type_name final {
/**
* @brief Returns the name of a given type.
* @return The name of the given type.
*/
[[nodiscard]] static constexpr stl::string_view value() noexcept {
return internal::type_name<Type>(0);
}
/*! @copydoc value */
[[nodiscard]] constexpr operator stl::string_view() const noexcept {
return value();
}
};
/*! @brief Implementation specific information about a type. */
struct type_info final {
/**
* @brief Constructs a type info object for a given type.
* @tparam Type Type for which to construct a type info object.
*/
template<typename Type>
// NOLINTBEGIN(modernize-use-transparent-functors)
constexpr type_info(stl::in_place_type_t<Type>) noexcept
: seq{type_index<stl::remove_cvref_t<Type>>::value()},
identifier{type_hash<stl::remove_cvref_t<Type>>::value()},
alias{type_name<stl::remove_cvref_t<Type>>::value()} {}
// NOLINTEND(modernize-use-transparent-functors)
/**
* @brief Type index.
* @return Type index.
*/
[[nodiscard]] constexpr id_type index() const noexcept {
return seq;
}
/**
* @brief Type hash.
* @return Type hash.
*/
[[nodiscard]] constexpr id_type hash() const noexcept {
return identifier;
}
/**
* @brief Type name.
* @return Type name.
*/
[[nodiscard]] constexpr stl::string_view name() const noexcept {
return alias;
}
/**
* @brief Compares two type info objects.
* @param other A type info object.
* @return True if the two type info objects are identical, false otherwise.
*/
[[nodiscard]] constexpr bool operator==(const type_info &other) const noexcept {
return identifier == other.identifier;
}
/**
* @brief Lexicographically compares two type info objects.
* @param other A type info object.
* @return The relative order between the two type info objects.
*/
[[nodiscard]] constexpr auto operator<=>(const type_info &other) const noexcept {
return seq <=> other.seq;
}
private:
id_type seq;
id_type identifier;
stl::string_view alias;
};
/**
* @brief Returns the type info object associated to a given type.
*
* The returned element refers to an object with static storage duration.<br/>
* The type doesn't need to be a complete type. If the type is a reference, the
* result refers to the referenced type. In all cases, top-level cv-qualifiers
* are ignored.
*
* @tparam Type Type for which to generate a type info object.
* @return A reference to a properly initialized type info object.
*/
template<typename Type>
[[nodiscard]] const type_info &type_id() noexcept {
if constexpr(stl::is_same_v<Type, stl::remove_cvref_t<Type>>) {
static const type_info instance{stl::in_place_type<Type>};
return instance;
} else {
return type_id<stl::remove_cvref_t<Type>>();
}
}
/*! @copydoc type_id */
template<typename Type>
[[nodiscard]] const type_info &type_id(const Type &) noexcept {
return type_id<stl::remove_cvref_t<Type>>();
}
} // namespace entt
#endif

View File

@@ -0,0 +1,909 @@
#ifndef ENTT_CORE_TYPE_TRAITS_HPP
#define ENTT_CORE_TYPE_TRAITS_HPP
#include "../config/config.h"
#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 "fwd.hpp"
namespace entt {
/**
* @brief Utility class to disambiguate overloaded functions.
* @tparam N Number of choices available.
*/
template<stl::size_t N>
struct choice_t
// unfortunately, doxygen cannot parse such a construct
: /*! @cond ENTT_INTERNAL */ choice_t<N - 1> /*! @endcond */
{};
/*! @copybrief choice_t */
template<>
struct choice_t<0> {};
/**
* @brief Variable template for the choice trick.
* @tparam N Number of choices available.
*/
template<stl::size_t N>
inline constexpr choice_t<N> choice{};
/**
* @brief A type-only `sizeof` wrapper that returns 0 where `sizeof` complains.
* @tparam Type The type of which to return the size.
*/
template<typename Type>
struct size_of: stl::integral_constant<stl::size_t, 0u> {};
/*! @copydoc size_of */
template<typename Type>
requires requires { sizeof(Type); }
struct size_of<Type>
// NOLINTNEXTLINE(bugprone-sizeof-expression)
: stl::integral_constant<stl::size_t, sizeof(Type)> {};
/**
* @brief Helper variable template.
* @tparam Type The type of which to return the size.
*/
template<typename Type>
inline constexpr stl::size_t size_of_v = size_of<Type>::value;
/**
* @brief Using declaration to be used to _repeat_ the same type a number of
* times equal to the size of a given parameter pack.
* @tparam Type A type to repeat.
*/
template<typename Type, typename>
using unpack_as_type = Type;
/**
* @brief Helper variable template to be used to _repeat_ the same value a
* number of times equal to the size of a given parameter pack.
* @tparam Value A value to repeat.
*/
template<auto Value, typename>
inline constexpr auto unpack_as_value = Value;
/**
* @brief Wraps a static constant.
* @tparam Value A static constant.
*/
template<auto Value>
using integral_constant = stl::integral_constant<decltype(Value), Value>;
/**
* @brief Alias template to facilitate the creation of named values.
* @tparam Value A constant value at least convertible to `id_type`.
*/
template<id_type Value>
using tag = integral_constant<Value>;
/**
* @brief A class to use to push around lists of types, nothing more.
* @tparam Type Types provided by the type list.
*/
template<typename... Type>
struct type_list {
/*! @brief Type list type. */
using type = type_list;
/*! @brief Compile-time number of elements in the type list. */
static constexpr auto size = sizeof...(Type);
};
/*! @brief Primary template isn't defined on purpose. */
template<stl::size_t, typename>
struct type_list_element;
/**
* @brief Provides compile-time indexed access to the types of a type list.
* @tparam Index Index of the type to return.
* @tparam First First type provided by the type list.
* @tparam Other Other types provided by the type list.
*/
template<stl::size_t Index, typename First, typename... Other>
struct type_list_element<Index, type_list<First, Other...>>
: type_list_element<Index - 1u, type_list<Other...>> {};
/**
* @brief Provides compile-time indexed access to the types of a type list.
* @tparam First First type provided by the type list.
* @tparam Other Other types provided by the type list.
*/
template<typename First, typename... Other>
struct type_list_element<0u, type_list<First, Other...>> {
/*! @brief Searched type. */
using type = First;
};
/**
* @brief Helper type.
* @tparam Index Index of the type to return.
* @tparam List Type list to search into.
*/
template<stl::size_t Index, typename List>
using type_list_element_t = type_list_element<Index, List>::type;
/*! @brief Primary template isn't defined on purpose. */
template<typename, typename>
struct type_list_index;
/**
* @brief Provides compile-time type access to the types of a type list.
* @tparam Type Type to look for and for which to return the index.
* @tparam First First type provided by the type list.
* @tparam Other Other types provided by the type list.
*/
template<typename Type, typename First, typename... Other>
struct type_list_index<Type, type_list<First, Other...>> {
/*! @brief Unsigned integer type. */
using value_type = stl::size_t;
/*! @brief Compile-time position of the given type in the sublist. */
static constexpr value_type value = 1u + type_list_index<Type, type_list<Other...>>::value;
};
/**
* @brief Provides compile-time type access to the types of a type list.
* @tparam Type Type to look for and for which to return the index.
* @tparam Other Other types provided by the type list.
*/
template<typename Type, typename... Other>
requires (type_list_index<Type, type_list<Other...>>::value == sizeof...(Other))
struct type_list_index<Type, type_list<Type, Other...>> {
/*! @brief Unsigned integer type. */
using value_type = stl::size_t;
/*! @brief Compile-time position of the given type in the sublist. */
static constexpr value_type value = 0u;
};
/**
* @brief Provides compile-time type access to the types of a type list.
* @tparam Type Type to look for and for which to return the index.
*/
template<typename Type>
struct type_list_index<Type, type_list<>> {
/*! @brief Unsigned integer type. */
using value_type = stl::size_t;
/*! @brief Compile-time position of the given type in the sublist. */
static constexpr value_type value = 0u;
};
/**
* @brief Helper variable template.
* @tparam List Type list.
* @tparam Type Type to look for and for which to return the index.
*/
template<typename Type, typename List>
inline constexpr stl::size_t type_list_index_v = type_list_index<Type, List>::value;
/**
* @brief Concatenates multiple type lists.
* @tparam Type Types provided by the first type list.
* @tparam Other Types provided by the second type list.
* @return A type list composed by the types of both the type lists.
*/
template<typename... Type, typename... Other>
ENTT_CONSTEVAL type_list<Type..., Other...> operator+(type_list<Type...>, type_list<Other...>) {
return {};
}
/*! @brief Primary template isn't defined on purpose. */
template<typename...>
struct type_list_cat;
/*! @brief Concatenates multiple type lists. */
template<>
struct type_list_cat<> {
/*! @brief A type list composed by the types of all the type lists. */
using type = type_list<>;
};
/**
* @brief Concatenates multiple type lists.
* @tparam Type Types provided by the first type list.
* @tparam Other Types provided by the second type list.
* @tparam List Other type lists, if any.
*/
template<typename... Type, typename... Other, typename... List>
struct type_list_cat<type_list<Type...>, type_list<Other...>, List...> {
/*! @brief A type list composed by the types of all the type lists. */
using type = type_list_cat<type_list<Type..., Other...>, List...>::type;
};
/**
* @brief Concatenates multiple type lists.
* @tparam Type Types provided by the type list.
*/
template<typename... Type>
struct type_list_cat<type_list<Type...>> {
/*! @brief A type list composed by the types of all the type lists. */
using type = type_list<Type...>;
};
/**
* @brief Helper type.
* @tparam List Type lists to concatenate.
*/
template<typename... List>
using type_list_cat_t = type_list_cat<List...>::type;
/*! @cond ENTT_INTERNAL */
namespace internal {
template<typename...>
struct type_list_unique;
template<typename First, typename... Other, typename... Type>
struct type_list_unique<type_list<First, Other...>, Type...>
: stl::conditional_t<(stl::is_same_v<First, Type> || ...), type_list_unique<type_list<Other...>, Type...>, type_list_unique<type_list<Other...>, Type..., First>> {};
template<typename... Type>
struct type_list_unique<type_list<>, Type...> {
using type = type_list<Type...>;
};
} // namespace internal
/*! @endcond */
/**
* @brief Removes duplicates types from a type list.
* @tparam List Type list.
*/
template<typename List>
struct type_list_unique {
/*! @brief A type list without duplicate types. */
using type = internal::type_list_unique<List>::type;
};
/**
* @brief Helper type.
* @tparam List Type list.
*/
template<typename List>
using type_list_unique_t = type_list_unique<List>::type;
/**
* @brief Provides the member constant `value` equal to true if a type list
* contains a given type, false otherwise.
* @tparam List Type list.
* @tparam Type Type to look for.
*/
template<typename List, typename Type>
struct type_list_contains;
/**
* @copybrief type_list_contains
* @tparam Type Types provided by the type list.
* @tparam Other Type to look for.
*/
template<typename... Type, typename Other>
struct type_list_contains<type_list<Type...>, Other>
: stl::bool_constant<(stl::is_same_v<Type, Other> || ...)> {};
/**
* @brief Helper variable template.
* @tparam List Type list.
* @tparam Type Type to look for.
*/
template<typename List, typename Type>
inline constexpr bool type_list_contains_v = type_list_contains<List, Type>::value;
/*! @brief Primary template isn't defined on purpose. */
template<typename...>
struct type_list_diff;
/**
* @brief Computes the difference between two type lists.
* @tparam Type Types provided by the first type list.
* @tparam Other Types provided by the second type list.
*/
template<typename... Type, typename... Other>
struct type_list_diff<type_list<Type...>, type_list<Other...>> {
/*! @brief A type list that is the difference between the two type lists. */
using type = type_list_cat_t<stl::conditional_t<type_list_contains_v<type_list<Other...>, Type>, type_list<>, type_list<Type>>...>;
};
/**
* @brief Helper type.
* @tparam List Type lists between which to compute the difference.
*/
template<typename... List>
using type_list_diff_t = type_list_diff<List...>::type;
/*! @brief Primary template isn't defined on purpose. */
template<typename, template<typename...> class>
struct type_list_transform;
/**
* @brief Applies a given _function_ to a type list and generates a new list.
* @tparam Type Types provided by the type list.
* @tparam Op Unary operation as template class with a type member named `type`.
*/
template<typename... Type, template<typename...> class Op>
struct type_list_transform<type_list<Type...>, Op> {
/*! @brief Resulting type list after applying the transform function. */
// NOLINTNEXTLINE(modernize-type-traits)
using type = type_list<typename Op<Type>::type...>;
};
/**
* @brief Helper type.
* @tparam List Type list.
* @tparam Op Unary operation as template class with a type member named `type`.
*/
template<typename List, template<typename...> class Op>
using type_list_transform_t = type_list_transform<List, Op>::type;
/**
* @brief A class to use to push around lists of constant values, nothing more.
* @tparam Value Values provided by the value list.
*/
template<auto... Value>
struct value_list {
/*! @brief Value list type. */
using type = value_list;
/*! @brief Compile-time number of elements in the value list. */
static constexpr auto size = sizeof...(Value);
};
/*! @brief Primary template isn't defined on purpose. */
template<stl::size_t, typename>
struct value_list_element;
/**
* @brief Provides compile-time indexed access to the values of a value list.
* @tparam Index Index of the value to return.
* @tparam Value First value provided by the value list.
* @tparam Other Other values provided by the value list.
*/
template<stl::size_t Index, auto Value, auto... Other>
struct value_list_element<Index, value_list<Value, Other...>>
: value_list_element<Index - 1u, value_list<Other...>> {};
/**
* @brief Provides compile-time indexed access to the types of a type list.
* @tparam Value First value provided by the value list.
* @tparam Other Other values provided by the value list.
*/
template<auto Value, auto... Other>
struct value_list_element<0u, value_list<Value, Other...>> {
/*! @brief Searched type. */
using type = decltype(Value);
/*! @brief Searched value. */
static constexpr auto value = Value;
};
/**
* @brief Helper type.
* @tparam Index Index of the type to return.
* @tparam List Value list to search into.
*/
template<stl::size_t Index, typename List>
using value_list_element_t = value_list_element<Index, List>::type;
/**
* @brief Helper type.
* @tparam Index Index of the value to return.
* @tparam List Value list to search into.
*/
template<stl::size_t Index, typename List>
inline constexpr auto value_list_element_v = value_list_element<Index, List>::value;
/*! @brief Primary template isn't defined on purpose. */
template<auto, typename>
struct value_list_index;
/**
* @brief Provides compile-time type access to the values of a value list.
* @tparam Value Value to look for and for which to return the index.
* @tparam First First value provided by the value list.
* @tparam Other Other values provided by the value list.
*/
template<auto Value, auto First, auto... Other>
struct value_list_index<Value, value_list<First, Other...>> {
/*! @brief Unsigned integer type. */
using value_type = stl::size_t;
/*! @brief Compile-time position of the given value in the sublist. */
static constexpr value_type value = 1u + value_list_index<Value, value_list<Other...>>::value;
};
/**
* @brief Provides compile-time type access to the values of a value list.
* @tparam Value Value to look for and for which to return the index.
* @tparam Other Other values provided by the value list.
*/
template<auto Value, auto... Other>
requires (value_list_index<Value, value_list<Other...>>::value == sizeof...(Other))
struct value_list_index<Value, value_list<Value, Other...>> {
/*! @brief Unsigned integer type. */
using value_type = stl::size_t;
/*! @brief Compile-time position of the given value in the sublist. */
static constexpr value_type value = 0u;
};
/**
* @brief Provides compile-time type access to the values of a value list.
* @tparam Value Value to look for and for which to return the index.
*/
template<auto Value>
struct value_list_index<Value, value_list<>> {
/*! @brief Unsigned integer type. */
using value_type = stl::size_t;
/*! @brief Compile-time position of the given type in the sublist. */
static constexpr value_type value = 0u;
};
/**
* @brief Helper variable template.
* @tparam List Value list.
* @tparam Value Value to look for and for which to return the index.
*/
template<auto Value, typename List>
inline constexpr stl::size_t value_list_index_v = value_list_index<Value, List>::value;
/**
* @brief Concatenates multiple value lists.
* @tparam Value Values provided by the first value list.
* @tparam Other Values provided by the second value list.
* @return A value list composed by the values of both the value lists.
*/
template<auto... Value, auto... Other>
ENTT_CONSTEVAL value_list<Value..., Other...> operator+(value_list<Value...>, value_list<Other...>) {
return {};
}
/*! @brief Primary template isn't defined on purpose. */
template<typename...>
struct value_list_cat;
/*! @brief Concatenates multiple value lists. */
template<>
struct value_list_cat<> {
/*! @brief A value list composed by the values of all the value lists. */
using type = value_list<>;
};
/**
* @brief Concatenates multiple value lists.
* @tparam Value Values provided by the first value list.
* @tparam Other Values provided by the second value list.
* @tparam List Other value lists, if any.
*/
template<auto... Value, auto... Other, typename... List>
struct value_list_cat<value_list<Value...>, value_list<Other...>, List...> {
/*! @brief A value list composed by the values of all the value lists. */
using type = value_list_cat<value_list<Value..., Other...>, List...>::type;
};
/**
* @brief Concatenates multiple value lists.
* @tparam Value Values provided by the value list.
*/
template<auto... Value>
struct value_list_cat<value_list<Value...>> {
/*! @brief A value list composed by the values of all the value lists. */
using type = value_list<Value...>;
};
/**
* @brief Helper type.
* @tparam List Value lists to concatenate.
*/
template<typename... List>
using value_list_cat_t = value_list_cat<List...>::type;
/*! @brief Primary template isn't defined on purpose. */
template<typename>
struct value_list_unique;
/**
* @brief Removes duplicates values from a value list.
* @tparam Value One of the values provided by the given value list.
* @tparam Other The other values provided by the given value list.
*/
template<auto Value, auto... Other>
struct value_list_unique<value_list<Value, Other...>> {
/*! @brief A value list without duplicate types. */
using type = stl::conditional_t<
((Value == Other) || ...),
typename value_list_unique<value_list<Other...>>::type,
value_list_cat_t<value_list<Value>, typename value_list_unique<value_list<Other...>>::type>>;
};
/*! @brief Removes duplicates values from a value list. */
template<>
struct value_list_unique<value_list<>> {
/*! @brief A value list without duplicate types. */
using type = value_list<>;
};
/**
* @brief Helper type.
* @tparam Type A value list.
*/
template<typename Type>
using value_list_unique_t = value_list_unique<Type>::type;
/**
* @brief Provides the member constant `value` equal to true if a value list
* contains a given value, false otherwise.
* @tparam List Value list.
* @tparam Value Value to look for.
*/
template<typename List, auto Value>
struct value_list_contains;
/**
* @copybrief value_list_contains
* @tparam Value Values provided by the value list.
* @tparam Other Value to look for.
*/
template<auto... Value, auto Other>
struct value_list_contains<value_list<Value...>, Other>
: stl::bool_constant<((Value == Other) || ...)> {};
/**
* @brief Helper variable template.
* @tparam List Value list.
* @tparam Value Value to look for.
*/
template<typename List, auto Value>
inline constexpr bool value_list_contains_v = value_list_contains<List, Value>::value;
/*! @brief Primary template isn't defined on purpose. */
template<typename...>
struct value_list_diff;
/**
* @brief Computes the difference between two value lists.
* @tparam Value Values provided by the first value list.
* @tparam Other Values provided by the second value list.
*/
template<auto... Value, auto... Other>
struct value_list_diff<value_list<Value...>, value_list<Other...>> {
/*! @brief A value list that is the difference between the two lists. */
using type = value_list_cat_t<stl::conditional_t<value_list_contains_v<value_list<Other...>, Value>, value_list<>, value_list<Value>>...>;
};
/**
* @brief Helper type.
* @tparam List Value lists between which to compute the difference.
*/
template<typename... List>
using value_list_diff_t = value_list_diff<List...>::type;
/*! @brief Same as stl::is_invocable, but with tuples. */
template<typename, typename>
struct is_applicable: stl::false_type {};
/**
* @copybrief is_applicable
* @tparam Func A valid function type.
* @tparam Tuple Tuple-like type.
* @tparam Args The list of arguments to use to probe the function type.
*/
template<typename Func, template<typename...> class Tuple, typename... Args>
struct is_applicable<Func, Tuple<Args...>>: stl::is_invocable<Func, Args...> {};
/**
* @copybrief is_applicable
* @tparam Func A valid function type.
* @tparam Tuple Tuple-like type.
* @tparam Args The list of arguments to use to probe the function type.
*/
template<typename Func, template<typename...> class Tuple, typename... Args>
struct is_applicable<Func, const Tuple<Args...>>: stl::is_invocable<Func, Args...> {};
/**
* @brief Helper variable template.
* @tparam Func A valid function type.
* @tparam Args The list of arguments to use to probe the function type.
*/
template<typename Func, typename Args>
inline constexpr bool is_applicable_v = is_applicable<Func, Args>::value;
/*! @brief Same as stl::is_invocable_r, but with tuples for arguments. */
template<typename, typename, typename>
struct is_applicable_r: stl::false_type {};
/**
* @copybrief is_applicable_r
* @tparam Ret The type to which the return type of the function should be
* convertible.
* @tparam Func A valid function type.
* @tparam Args The list of arguments to use to probe the function type.
*/
template<typename Ret, typename Func, typename... Args>
struct is_applicable_r<Ret, Func, stl::tuple<Args...>>: stl::is_invocable_r<Ret, Func, Args...> {};
/**
* @brief Helper variable template.
* @tparam Ret The type to which the return type of the function should be
* convertible.
* @tparam Func A valid function type.
* @tparam Args The list of arguments to use to probe the function type.
*/
template<typename Ret, typename Func, typename Args>
inline constexpr bool is_applicable_r_v = is_applicable_r<Ret, Func, Args>::value;
/**
* @brief Provides the member constant `value` equal to true if a given type is
* complete, false otherwise.
* @tparam Type The type to test.
*/
template<typename Type>
struct is_complete: stl::false_type {};
/*! @copydoc is_complete */
template<typename Type>
requires requires { sizeof(Type); }
struct is_complete<Type>: stl::true_type {};
/**
* @brief Helper variable template.
* @tparam Type The type to test.
*/
template<typename Type>
inline constexpr bool is_complete_v = is_complete<Type>::value;
/**
* @brief Provides the member constant `value` equal to true if a given type is
* an iterator, false otherwise.
* @tparam Type The type to test.
*/
template<typename Type>
struct is_iterator: stl::false_type {};
/*! @cond ENTT_INTERNAL */
namespace internal {
template<typename>
struct has_iterator_category: stl::false_type {};
template<typename Type>
requires requires { typename stl::iterator_traits<Type>::iterator_category; }
struct has_iterator_category<Type>: stl::true_type {};
} // namespace internal
/*! @endcond */
/*! @copydoc is_iterator */
template<typename Type>
requires (!stl::is_void_v<stl::remove_const_t<stl::remove_pointer_t<Type>>>)
struct is_iterator<Type>: internal::has_iterator_category<Type> {};
/**
* @brief Helper variable template.
* @tparam Type The type to test.
*/
template<typename Type>
inline constexpr bool is_iterator_v = is_iterator<Type>::value;
/**
* @brief Provides the member constant `value` equal to true if a given type is
* both an empty and non-final class, false otherwise.
* @tparam Type The type to test
*/
template<typename Type>
struct is_ebco_eligible: stl::bool_constant<stl::is_empty_v<Type> && !stl::is_final_v<Type>> {};
/**
* @brief Helper variable template.
* @tparam Type The type to test.
*/
template<typename Type>
inline constexpr bool is_ebco_eligible_v = is_ebco_eligible<Type>::value;
/**
* @brief Provides the member constant `value` equal to true if
* `Type::is_transparent` is valid and denotes a type, false otherwise.
* @tparam Type The type to test.
*/
template<typename Type>
struct is_transparent: stl::false_type {};
/*! @copydoc is_transparent */
template<typename Type>
requires requires { typename Type::is_transparent; }
struct is_transparent<Type>: stl::true_type {};
/**
* @brief Helper variable template.
* @tparam Type The type to test.
*/
template<typename Type>
inline constexpr bool is_transparent_v = is_transparent<Type>::value;
/*! @cond ENTT_INTERNAL */
namespace internal {
template<typename>
struct has_tuple_size_value: stl::false_type {};
template<typename Type>
requires is_complete_v<stl::tuple_size<const Type>>
struct has_tuple_size_value<Type>: stl::true_type {};
template<typename>
struct has_value_type: stl::false_type {};
template<typename Type>
requires requires { typename Type::value_type; }
struct has_value_type<Type>: stl::true_type {};
template<typename>
[[nodiscard]] ENTT_CONSTEVAL bool dispatch_is_equality_comparable();
template<typename Type, stl::size_t... Index>
[[nodiscard]] ENTT_CONSTEVAL bool unpack_maybe_equality_comparable(stl::index_sequence<Index...>) {
return (dispatch_is_equality_comparable<stl::tuple_element_t<Index, Type>>() && ...);
}
template<typename>
[[nodiscard]] ENTT_CONSTEVAL bool maybe_equality_comparable(char) {
return false;
}
template<typename Type>
[[nodiscard]] ENTT_CONSTEVAL auto maybe_equality_comparable(int) -> decltype(stl::declval<Type>() == stl::declval<Type>()) {
return true;
}
template<typename Type>
[[nodiscard]] ENTT_CONSTEVAL bool dispatch_is_equality_comparable() {
// NOLINTBEGIN(modernize-use-transparent-functors)
if constexpr(stl::is_array_v<Type>) {
return false;
} else if constexpr(is_complete_v<stl::tuple_size<stl::remove_const_t<Type>>>) {
if constexpr(has_tuple_size_value<Type>::value) {
return maybe_equality_comparable<Type>(0) && unpack_maybe_equality_comparable<Type>(stl::make_index_sequence<stl::tuple_size<Type>::value>{});
} else {
return maybe_equality_comparable<Type>(0);
}
} else if constexpr(has_value_type<Type>::value) {
if constexpr(is_iterator_v<Type> || stl::is_same_v<typename Type::value_type, Type> || dispatch_is_equality_comparable<typename Type::value_type>()) {
return maybe_equality_comparable<Type>(0);
} else {
return false;
}
} else {
return maybe_equality_comparable<Type>(0);
}
// NOLINTEND(modernize-use-transparent-functors)
}
} // namespace internal
/*! @endcond */
/**
* @brief Provides the member constant `value` equal to true if a given type is
* equality comparable, false otherwise.
* @tparam Type The type to test.
*/
template<typename Type>
struct is_equality_comparable: stl::bool_constant<internal::dispatch_is_equality_comparable<Type>()> {};
/*! @copydoc is_equality_comparable */
template<typename Type>
struct is_equality_comparable<const Type>: is_equality_comparable<Type> {};
/**
* @brief Helper variable template.
* @tparam Type The type to test.
*/
template<typename Type>
inline constexpr bool is_equality_comparable_v = is_equality_comparable<Type>::value;
/**
* @brief Transcribes the constness of a type to another type.
* @tparam To The type to which to transcribe the constness.
* @tparam From The type from which to transcribe the constness.
*/
template<typename To, typename From>
struct constness_as {
/*! @brief The type resulting from the transcription of the constness. */
using type = stl::remove_const_t<To>;
};
/*! @copydoc constness_as */
template<typename To, typename From>
struct constness_as<To, const From> {
/*! @brief The type resulting from the transcription of the constness. */
using type = const To;
};
/**
* @brief Alias template to facilitate the transcription of the constness.
* @tparam To The type to which to transcribe the constness.
* @tparam From The type from which to transcribe the constness.
*/
template<typename To, typename From>
using constness_as_t = constness_as<To, From>::type;
/*! @brief Primary template isn't defined on purpose. */
template<typename>
class member_class;
/**
* @brief Extracts the class of a non-static member object or function.
* @tparam Member A pointer to a non-static member object or function.
*/
template<typename Member>
requires stl::is_member_pointer_v<Member>
class member_class<Member> {
template<typename Class, typename Ret, typename... Args>
static Class *clazz(Ret (Class::*)(Args...));
template<typename Class, typename Ret, typename... Args>
static Class *clazz(Ret (Class::*)(Args...) const);
template<typename Class, typename Type>
static Class *clazz(Type Class::*);
public:
/*! @brief The class of the given non-static member object or function. */
using type = stl::remove_pointer_t<decltype(clazz(stl::declval<Member>()))>;
};
/**
* @brief Helper type.
* @tparam Member A pointer to a non-static member object or function.
*/
template<typename Member>
using member_class_t = member_class<Member>::type;
/**
* @brief Extracts the n-th argument of a _callable_ type.
* @tparam Index The index of the argument to extract.
* @tparam Candidate A valid _callable_ type.
*/
template<stl::size_t Index, typename Candidate>
class nth_argument {
template<typename Ret, typename... Args>
static ENTT_CONSTEVAL type_list<Args...> pick_up(Ret (*)(Args...));
template<typename Ret, typename Class, typename... Args>
static ENTT_CONSTEVAL type_list<Args...> pick_up(Ret (Class ::*)(Args...));
template<typename Ret, typename Class, typename... Args>
static ENTT_CONSTEVAL type_list<Args...> pick_up(Ret (Class ::*)(Args...) const);
template<typename Type, typename Class>
static ENTT_CONSTEVAL type_list<Type> pick_up(Type Class ::*);
template<typename Type>
static ENTT_CONSTEVAL decltype(pick_up(&Type::operator())) pick_up(Type &&);
public:
/*! @brief N-th argument of the _callable_ type. */
using type = type_list_element_t<Index, decltype(pick_up(stl::declval<Candidate>()))>;
};
/**
* @brief Helper type.
* @tparam Index The index of the argument to extract.
* @tparam Candidate A valid function, member function or data member type.
*/
template<stl::size_t Index, typename Candidate>
using nth_argument_t = nth_argument<Index, Candidate>::type;
} // namespace entt
template<typename... Type>
struct entt::stl::tuple_size<entt::type_list<Type...>>: entt::stl::integral_constant<entt::stl::size_t, entt::type_list<Type...>::size> {};
template<entt::stl::size_t Index, typename... Type>
struct entt::stl::tuple_element<Index, entt::type_list<Type...>>: entt::type_list_element<Index, entt::type_list<Type...>> {};
template<auto... Value>
struct entt::stl::tuple_size<entt::value_list<Value...>>: entt::stl::integral_constant<entt::stl::size_t, entt::value_list<Value...>::size> {};
template<entt::stl::size_t Index, auto... Value>
struct entt::stl::tuple_element<Index, entt::value_list<Value...>>: entt::value_list_element<Index, entt::value_list<Value...>> {};
#endif

View File

@@ -0,0 +1,84 @@
#ifndef ENTT_CORE_UTILITY_HPP
#define ENTT_CORE_UTILITY_HPP
#include "../stl/type_traits.hpp"
#include "../stl/utility.hpp"
namespace entt {
/**
* @brief Constant utility to disambiguate overloaded members of a class.
* @tparam Type Type of the desired overload.
* @tparam Class Type of class to which the member belongs.
* @param member A valid pointer to a member.
* @return Pointer to the member.
*/
template<typename Type, typename Class>
[[nodiscard]] constexpr auto overload(Type Class::*member) noexcept {
return member;
}
/**
* @brief Constant utility to disambiguate overloaded functions.
* @tparam Func Function type of the desired overload.
* @param func A valid pointer to a function.
* @return Pointer to the function.
*/
template<typename Func>
[[nodiscard]] constexpr auto overload(Func *func) noexcept {
return func;
}
/**
* @brief Helper type for visitors.
* @tparam Func Types of function objects.
*/
template<typename... Func>
struct overloaded: Func... {
using Func::operator()...;
};
/**
* @brief Deduction guide.
* @tparam Func Types of function objects.
*/
template<typename... Func>
overloaded(Func...) -> overloaded<Func...>;
/**
* @brief Basic implementation of a y-combinator.
* @tparam Func Type of a potentially recursive function.
*/
template<typename Func>
struct y_combinator {
/**
* @brief Constructs a y-combinator from a given function.
* @param recursive A potentially recursive function.
*/
constexpr y_combinator(Func recursive) noexcept(stl::is_nothrow_move_constructible_v<Func>)
: func{stl::move(recursive)} {}
/**
* @brief Invokes a y-combinator and therefore its underlying function.
* @tparam Args Types of arguments to use to invoke the underlying function.
* @param args Parameters to use to invoke the underlying function.
* @return Return value of the underlying function, if any.
*/
template<typename... Args>
constexpr decltype(auto) operator()(Args &&...args) const noexcept(stl::is_nothrow_invocable_v<Func, const y_combinator &, Args...>) {
return func(*this, stl::forward<Args>(args)...);
}
/*! @copydoc operator()() */
template<typename... Args>
constexpr decltype(auto) operator()(Args &&...args) noexcept(stl::is_nothrow_invocable_v<Func, y_combinator &, Args...>) {
return func(*this, stl::forward<Args>(args)...);
}
private:
Func func;
};
} // namespace entt
#endif

View File

@@ -0,0 +1,59 @@
#ifndef ENTT_ENTITY_COMPONENT_HPP
#define ENTT_ENTITY_COMPONENT_HPP
#include "../config/config.h"
#include "../core/concepts.hpp"
#include "../stl/concepts.hpp"
#include "../stl/cstddef.hpp"
#include "../stl/type_traits.hpp"
#include "fwd.hpp"
namespace entt {
/*! @cond ENTT_INTERNAL */
namespace internal {
template<typename Type>
struct in_place_delete: stl::bool_constant<!(stl::is_move_constructible_v<Type> && stl::is_move_assignable_v<Type>)> {};
template<>
struct in_place_delete<void>: stl::false_type {};
template<typename Type>
requires Type::in_place_delete
struct in_place_delete<Type>: stl::true_type {};
template<typename Type>
struct page_size: stl::integral_constant<stl::size_t, !stl::is_empty_v<ENTT_ETO_TYPE(Type)> * ENTT_PACKED_PAGE> {};
template<>
struct page_size<void>: stl::integral_constant<stl::size_t, 0u> {};
template<typename Type>
requires stl::is_convertible_v<decltype(Type::page_size), stl::size_t>
struct page_size<Type>: stl::integral_constant<stl::size_t, Type::page_size> {};
} // namespace internal
/*! @endcond */
/**
* @brief Common way to access various properties of components.
* @tparam Type Element type.
* @tparam Entity A valid entity type.
*/
template<cvref_unqualified Type, typename Entity>
struct component_traits {
/*! @brief Element type. */
using element_type = Type;
/*! @brief Underlying entity identifier. */
using entity_type = Entity;
/*! @brief Pointer stability, default is `false`. */
static constexpr bool in_place_delete = internal::in_place_delete<Type>::value;
/*! @brief Page size, default is `ENTT_PACKED_PAGE` for non-empty types. */
static constexpr stl::size_t page_size = internal::page_size<Type>::value;
};
} // namespace entt
#endif

View File

@@ -0,0 +1,312 @@
#ifndef ENTT_ENTITY_ENTITY_HPP
#define ENTT_ENTITY_ENTITY_HPP
#include "../config/config.h"
#include "../core/bit.hpp"
#include "../stl/bit.hpp"
#include "../stl/concepts.hpp"
#include "../stl/cstddef.hpp"
#include "../stl/cstdint.hpp"
#include "../stl/type_traits.hpp"
#include "fwd.hpp"
namespace entt {
/*! @cond ENTT_INTERNAL */
namespace internal {
template<typename>
struct entt_traits;
template<typename Type>
requires requires {
requires stl::is_enum_v<Type>;
typename internal::entt_traits<stl::underlying_type_t<Type>>::value_type;
}
struct entt_traits<Type>: entt_traits<stl::underlying_type_t<Type>> {
using value_type = Type;
};
template<typename Type>
requires requires { typename Type::entity_type; }
struct entt_traits<Type>
: entt_traits<typename Type::entity_type> {
using value_type = Type;
};
template<>
struct entt_traits<stl::uint32_t> {
using value_type = stl::uint32_t;
using entity_type = stl::uint32_t;
using version_type = stl::uint16_t;
static constexpr entity_type entity_mask = 0xFFFFF;
static constexpr entity_type version_mask = 0xFFF;
};
template<>
struct entt_traits<stl::uint64_t> {
using value_type = stl::uint64_t;
using entity_type = stl::uint64_t;
using version_type = stl::uint32_t;
static constexpr entity_type entity_mask = 0xFFFFFFFF;
static constexpr entity_type version_mask = 0xFFFFFFFF;
};
} // namespace internal
/*! @endcond */
/**
* @brief Specifies that a type is an entity-like type.
* @tparam Type Type to check.
*/
template<typename Type>
concept entity_like = requires {
typename internal::entt_traits<Type>::value_type;
};
/**
* @brief Common basic entity traits implementation.
* @tparam Traits Actual entity traits to use.
*/
template<typename Traits>
class basic_entt_traits {
static constexpr auto length = stl::popcount(Traits::entity_mask);
public:
/*! @brief Value type. */
using value_type = Traits::value_type;
/*! @brief Underlying entity type. */
using entity_type = Traits::entity_type;
/*! @brief Underlying version type. */
using version_type = Traits::version_type;
/*! @brief Entity mask size. */
static constexpr entity_type entity_mask = Traits::entity_mask;
/*! @brief Version mask size */
static constexpr entity_type version_mask = Traits::version_mask;
/**
* @brief Converts an entity to its underlying type.
* @param value The value to convert.
* @return The integral representation of the given value.
*/
[[nodiscard]] static constexpr entity_type to_integral(const value_type value) noexcept {
return static_cast<entity_type>(value);
}
/**
* @brief Returns the entity part once converted to the underlying type.
* @param value The value to convert.
* @return The integral representation of the entity part.
*/
[[nodiscard]] static constexpr entity_type to_entity(const value_type value) noexcept {
static_assert(Traits::entity_mask && ((Traits::entity_mask & (Traits::entity_mask + 1)) == 0), "Invalid entity mask");
return (to_integral(value) & entity_mask);
}
/**
* @brief Returns the version part once converted to the underlying type.
* @param value The value to convert.
* @return The integral representation of the version part.
*/
[[nodiscard]] static constexpr version_type to_version(const value_type value) noexcept {
if constexpr(Traits::version_mask == 0u) {
return version_type{};
} else {
static_assert((Traits::version_mask & (Traits::version_mask + 1)) == 0, "Invalid version mask");
return (static_cast<version_type>(to_integral(value) >> length) & version_mask);
}
}
/**
* @brief Returns the successor of a given identifier.
* @param value The identifier of which to return the successor.
* @return The successor of the given identifier.
*/
[[nodiscard]] static constexpr value_type next(const value_type value) noexcept {
const auto vers = to_version(value) + 1;
return construct(to_integral(value), static_cast<version_type>(vers + (vers == version_mask)));
}
/**
* @brief Constructs an identifier from its parts.
*
* If the version part is not provided, a tombstone is returned.<br/>
* If the entity part is not provided, a null identifier is returned.
*
* @param entity The entity part of the identifier.
* @param version The version part of the identifier.
* @return A properly constructed identifier.
*/
[[nodiscard]] static constexpr value_type construct(const entity_type entity, const version_type version) noexcept {
if constexpr(Traits::version_mask == 0u) {
return value_type{entity & entity_mask};
} else {
return value_type{(entity & entity_mask) | (static_cast<entity_type>(version & version_mask) << length)};
}
}
/**
* @brief Combines two identifiers in a single one.
*
* The returned identifier is a copy of the first element except for its
* version, which is taken from the second element.
*
* @param lhs The identifier from which to take the entity part.
* @param rhs The identifier from which to take the version part.
* @return A properly constructed identifier.
*/
[[nodiscard]] static constexpr value_type combine(const entity_type lhs, const entity_type rhs) noexcept {
if constexpr(Traits::version_mask == 0u) {
return value_type{lhs & entity_mask};
} else {
return value_type{(lhs & entity_mask) | (rhs & (version_mask << length))};
}
}
};
/**
* @brief Entity traits.
* @tparam Type Type of identifier.
*/
template<entity_like Type>
struct entt_traits: basic_entt_traits<internal::entt_traits<Type>> {
/*! @brief Base type. */
using base_type = basic_entt_traits<internal::entt_traits<Type>>;
/*! @brief Page size, default is `ENTT_SPARSE_PAGE`. */
static constexpr stl::size_t page_size = ENTT_SPARSE_PAGE;
};
/**
* @brief Converts an entity to its underlying type.
* @tparam Entity The value type.
* @param value The value to convert.
* @return The integral representation of the given value.
*/
template<typename Entity>
[[nodiscard]] constexpr entt_traits<Entity>::entity_type to_integral(const Entity value) noexcept {
return entt_traits<Entity>::to_integral(value);
}
/**
* @brief Returns the entity part once converted to the underlying type.
* @tparam Entity The value type.
* @param value The value to convert.
* @return The integral representation of the entity part.
*/
template<typename Entity>
[[nodiscard]] constexpr entt_traits<Entity>::entity_type to_entity(const Entity value) noexcept {
return entt_traits<Entity>::to_entity(value);
}
/**
* @brief Returns the version part once converted to the underlying type.
* @tparam Entity The value type.
* @param value The value to convert.
* @return The integral representation of the version part.
*/
template<typename Entity>
[[nodiscard]] constexpr entt_traits<Entity>::version_type to_version(const Entity value) noexcept {
return entt_traits<Entity>::to_version(value);
}
/*! @brief Null object for all identifiers. */
struct null_t {
/**
* @brief Converts the null object to identifiers of any type.
* @tparam Entity Type of identifier.
* @return The null representation for the given type.
*/
template<entity_like Entity>
[[nodiscard]] constexpr operator Entity() const noexcept {
using traits_type = entt_traits<Entity>;
return traits_type::construct(traits_type::entity_mask, traits_type::version_mask);
}
/**
* @brief Compares two null objects.
* @param other A null object.
* @return True in all cases.
*/
[[nodiscard]] constexpr bool operator==([[maybe_unused]] const null_t other) const noexcept {
return true;
}
/**
* @brief Compares a null object and an identifier of any type.
* @tparam Entity Type of identifier.
* @param entity Identifier with which to compare.
* @return False if the two elements differ, true otherwise.
*/
template<entity_like Entity>
[[nodiscard]] constexpr bool operator==(const Entity entity) const noexcept {
using traits_type = entt_traits<Entity>;
return traits_type::to_entity(entity) == traits_type::to_entity(*this);
}
};
/*! @brief Tombstone object for all identifiers. */
struct tombstone_t {
/**
* @brief Converts the tombstone object to identifiers of any type.
* @tparam Entity Type of identifier.
* @return The tombstone representation for the given type.
*/
template<entity_like Entity>
[[nodiscard]] constexpr operator Entity() const noexcept {
using traits_type = entt_traits<Entity>;
return traits_type::construct(traits_type::entity_mask, traits_type::version_mask);
}
/**
* @brief Compares two tombstone objects.
* @param other A tombstone object.
* @return True in all cases.
*/
[[nodiscard]] constexpr bool operator==([[maybe_unused]] const tombstone_t other) const noexcept {
return true;
}
/**
* @brief Compares a tombstone object and an identifier of any type.
* @tparam Entity Type of identifier.
* @param entity Identifier with which to compare.
* @return False if the two elements differ, true otherwise.
*/
template<entity_like Entity>
[[nodiscard]] constexpr bool operator==(const Entity entity) const noexcept {
using traits_type = entt_traits<Entity>;
if constexpr(traits_type::version_mask == 0u) {
return false;
} else {
return (traits_type::to_version(entity) == traits_type::to_version(*this));
}
}
};
/**
* @brief Compile-time constant for null entities.
*
* There exist implicit conversions from this variable to identifiers of any
* allowed type. Similarly, there exist comparison operators between the null
* entity and any other identifier.
*/
inline constexpr null_t null{};
/**
* @brief Compile-time constant for tombstone entities.
*
* There exist implicit conversions from this variable to identifiers of any
* allowed type. Similarly, there exist comparison operators between the
* tombstone entity and any other identifier.
*/
inline constexpr tombstone_t tombstone{};
} // namespace entt
#endif

291
include/entt/entity/fwd.hpp Normal file
View File

@@ -0,0 +1,291 @@
#ifndef ENTT_ENTITY_FWD_HPP
#define ENTT_ENTITY_FWD_HPP
#include "../config/config.h"
#include "../core/concepts.hpp"
#include "../core/fwd.hpp"
#include "../core/type_traits.hpp"
#include "../stl/cstdint.hpp"
#include "../stl/memory.hpp"
#include "../stl/type_traits.hpp"
namespace entt {
/*! @brief Default entity identifier. */
enum class entity : id_type {};
/*! @brief Storage deletion policy. */
enum class deletion_policy : stl::uint8_t {
/*! @brief Swap-and-pop deletion policy. */
swap_and_pop = 0u,
/*! @brief In-place deletion policy. */
in_place = 1u,
/*! @brief Swap-only deletion policy. */
swap_only = 2u,
/*! @brief Unspecified deletion policy. */
unspecified = swap_and_pop
};
template<cvref_unqualified Type, typename Entity = entity>
struct component_traits;
template<typename Entity = entity, typename = stl::allocator<Entity>>
class basic_sparse_set;
template<typename Type, typename = entity, typename = stl::allocator<Type>>
class basic_storage;
template<typename, typename>
class basic_sigh_mixin;
template<typename, typename>
class basic_reactive_mixin;
template<typename Entity = entity, typename = stl::allocator<Entity>>
class basic_registry;
template<typename, typename>
class basic_view;
template<typename Type, typename = stl::allocator<Type *>>
class basic_runtime_view;
template<typename, typename, typename>
class basic_group;
template<typename>
class basic_organizer;
template<typename, typename...>
class basic_handle;
template<typename>
class basic_snapshot;
template<typename>
class basic_snapshot_loader;
template<typename>
class basic_continuous_loader;
/*! @brief Alias declaration for the most common use case. */
using sparse_set = basic_sparse_set<>;
/**
* @brief Alias declaration for the most common use case.
* @tparam Type Element type.
*/
template<typename Type>
using storage = basic_storage<Type>;
/**
* @brief Alias declaration for the most common use case.
* @tparam Type Underlying storage type.
*/
template<typename Type>
using sigh_mixin = basic_sigh_mixin<Type, basic_registry<typename Type::entity_type, typename Type::base_type::allocator_type>>;
/**
* @brief Alias declaration for the most common use case.
* @tparam Type Underlying storage type.
*/
template<typename Type>
using reactive_mixin = basic_reactive_mixin<Type, basic_registry<typename Type::entity_type, typename Type::base_type::allocator_type>>;
/*! @brief Alias declaration for the most common use case. */
using registry = basic_registry<>;
/*! @brief Alias declaration for the most common use case. */
using organizer = basic_organizer<registry>;
/*! @brief Alias declaration for the most common use case. */
using handle = basic_handle<registry>;
/*! @brief Alias declaration for the most common use case. */
using const_handle = basic_handle<const registry>;
/**
* @brief Alias declaration for the most common use case.
* @tparam Args Other template parameters.
*/
template<typename... Args>
using handle_view = basic_handle<registry, Args...>;
/**
* @brief Alias declaration for the most common use case.
* @tparam Args Other template parameters.
*/
template<typename... Args>
using const_handle_view = basic_handle<const registry, Args...>;
/*! @brief Alias declaration for the most common use case. */
using snapshot = basic_snapshot<registry>;
/*! @brief Alias declaration for the most common use case. */
using snapshot_loader = basic_snapshot_loader<registry>;
/*! @brief Alias declaration for the most common use case. */
using continuous_loader = basic_continuous_loader<registry>;
/*! @brief Alias declaration for the most common use case. */
using runtime_view = basic_runtime_view<sparse_set>;
/*! @brief Alias declaration for the most common use case. */
using const_runtime_view = basic_runtime_view<const sparse_set>;
/**
* @brief Alias for exclusion lists.
* @tparam Type List of types.
*/
template<typename... Type>
struct exclude_t final: type_list<Type...> {
/*! @brief Default constructor. */
explicit ENTT_CONSTEVAL exclude_t() = default;
};
/**
* @brief Variable template for exclusion lists.
* @tparam Type List of types.
*/
template<typename... Type>
inline constexpr exclude_t<Type...> exclude{};
/**
* @brief Alias for lists of observed elements.
* @tparam Type List of types.
*/
template<typename... Type>
struct get_t final: type_list<Type...> {
/*! @brief Default constructor. */
explicit ENTT_CONSTEVAL get_t() = default;
};
/**
* @brief Variable template for lists of observed elements.
* @tparam Type List of types.
*/
template<typename... Type>
inline constexpr get_t<Type...> get{};
/**
* @brief Alias for lists of owned elements.
* @tparam Type List of types.
*/
template<typename... Type>
struct owned_t final: type_list<Type...> {
/*! @brief Default constructor. */
explicit ENTT_CONSTEVAL owned_t() = default;
};
/**
* @brief Variable template for lists of owned elements.
* @tparam Type List of types.
*/
template<typename... Type>
inline constexpr owned_t<Type...> owned{};
/**
* @brief Applies a given _function_ to a get list and generate a new list.
* @tparam Type Types provided by the get list.
* @tparam Op Unary operation as template class with a type member named `type`.
*/
template<typename... Type, template<typename...> class Op>
struct type_list_transform<get_t<Type...>, Op> {
/*! @brief Resulting get list after applying the transform function. */
using type = get_t<typename Op<Type>::type...>;
};
/**
* @brief Applies a given _function_ to an exclude list and generate a new list.
* @tparam Type Types provided by the exclude list.
* @tparam Op Unary operation as template class with a type member named `type`.
*/
template<typename... Type, template<typename...> class Op>
struct type_list_transform<exclude_t<Type...>, Op> {
/*! @brief Resulting exclude list after applying the transform function. */
using type = exclude_t<typename Op<Type>::type...>;
};
/**
* @brief Applies a given _function_ to an owned list and generate a new list.
* @tparam Type Types provided by the owned list.
* @tparam Op Unary operation as template class with a type member named `type`.
*/
template<typename... Type, template<typename...> class Op>
struct type_list_transform<owned_t<Type...>, Op> {
/*! @brief Resulting owned list after applying the transform function. */
using type = owned_t<typename Op<Type>::type...>;
};
/**
* @brief Provides a common way to define storage types.
* @tparam Type Storage value type.
* @tparam Entity A valid entity type.
* @tparam Allocator Type of allocator used to manage memory and elements.
*/
template<typename Type, typename Entity = entity, typename Allocator = stl::allocator<Type>>
struct storage_type {
/*! @brief Type-to-storage conversion result. */
using type = ENTT_STORAGE(sigh_mixin, basic_storage<Type, Entity, Allocator>);
};
/*! @brief Empty value type for reactive storage types. */
struct reactive final {};
/**
* @ brief Partial specialization for reactive storage types.
* @tparam Entity A valid entity type.
* @tparam Allocator Type of allocator used to manage memory and elements.
*/
template<typename Entity, typename Allocator>
struct storage_type<reactive, Entity, Allocator> {
/*! @brief Type-to-storage conversion result. */
using type = ENTT_STORAGE(reactive_mixin, basic_storage<reactive, Entity, Allocator>);
};
/**
* @brief Helper type.
* @tparam Args Arguments to forward.
*/
template<typename... Args>
using storage_type_t = storage_type<Args...>::type;
/**
* Type-to-storage conversion utility that preserves constness.
* @tparam Type Storage value type, eventually const.
* @tparam Entity A valid entity type.
* @tparam Allocator Type of allocator used to manage memory and elements.
*/
template<typename Type, typename Entity = entity, typename Allocator = stl::allocator<stl::remove_const_t<Type>>>
struct storage_for {
/*! @brief Type-to-storage conversion result. */
using type = constness_as_t<storage_type_t<stl::remove_const_t<Type>, Entity, Allocator>, Type>;
};
/**
* @brief Helper type.
* @tparam Args Arguments to forward.
*/
template<typename... Args>
using storage_for_t = storage_for<Args...>::type;
/**
* @brief Alias declaration for the most common use case.
* @tparam Get Types of storage iterated by the view.
* @tparam Exclude Types of storage used to filter the view.
*/
template<typename Get, typename Exclude = exclude_t<>>
using view = basic_view<type_list_transform_t<Get, storage_for>, type_list_transform_t<Exclude, storage_for>>;
/**
* @brief Alias declaration for the most common use case.
* @tparam Owned Types of storage _owned_ by the group.
* @tparam Get Types of storage _observed_ by the group.
* @tparam Exclude Types of storage used to filter the group.
*/
template<typename Owned, typename Get = get_t<>, typename Exclude = exclude_t<>>
using group = basic_group<type_list_transform_t<Owned, storage_for>, type_list_transform_t<Get, storage_for>, type_list_transform_t<Exclude, storage_for>>;
} // namespace entt
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,368 @@
#ifndef ENTT_ENTITY_HANDLE_HPP
#define ENTT_ENTITY_HANDLE_HPP
#include "../config/config.h"
#include "../core/iterator.hpp"
#include "../core/type_traits.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 It>
class handle_storage_iterator final {
template<typename>
friend class handle_storage_iterator;
using underlying_type = stl::remove_reference_t<typename It::value_type::second_type>;
using entity_type = underlying_type::entity_type;
public:
using value_type = stl::iterator_traits<It>::value_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::forward_iterator_tag;
constexpr handle_storage_iterator() noexcept
: entt{null},
it{},
last{} {}
constexpr handle_storage_iterator(entity_type value, It from, It to) noexcept
: entt{value},
it{from},
last{to} {
while(it != last && !it->second.contains(entt)) {
++it;
}
}
constexpr handle_storage_iterator &operator++() noexcept {
for(++it; it != last && !it->second.contains(entt); ++it) {}
return *this;
}
constexpr handle_storage_iterator operator++(int) noexcept {
const handle_storage_iterator orig = *this;
return ++(*this), orig;
}
[[nodiscard]] constexpr reference operator*() const noexcept {
return *it;
}
[[nodiscard]] constexpr pointer operator->() const noexcept {
return operator*();
}
template<typename Other>
[[nodiscard]] constexpr bool operator==(const handle_storage_iterator<Other> &other) const noexcept {
return it == other.it;
}
private:
entity_type entt;
It it;
It last;
};
} // namespace internal
/*! @endcond */
/**
* @brief Non-owning handle to an entity.
*
* Tiny wrapper around a registry and an entity.
*
* @tparam Registry Basic registry type.
* @tparam Scope Types to which to restrict the scope of a handle.
*/
template<typename Registry, typename... Scope>
class basic_handle {
using traits_type = entt_traits<typename Registry::entity_type>;
[[nodiscard]] auto &owner_or_assert() const noexcept {
ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry");
return static_cast<Registry &>(*owner);
}
public:
/*! @brief Type of registry accepted by the handle. */
using registry_type = Registry;
/*! @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 Iterable handle type. */
using iterable = iterable_adaptor<internal::handle_storage_iterator<typename decltype(stl::declval<registry_type>().storage())::iterator>>;
/*! @brief Constructs an invalid handle. */
basic_handle() noexcept
: owner{},
entt{null} {}
/**
* @brief Constructs a handle from a given registry and entity.
* @param ref An instance of the registry class.
* @param value A valid identifier.
*/
basic_handle(registry_type &ref, entity_type value) noexcept
: owner{&ref},
entt{value} {}
/**
* @brief Returns an iterable object to use to _visit_ a handle.
*
* The iterable object returns a pair that contains the name and a reference
* to the current storage.<br/>
* Returned storage are those that contain the entity associated with the
* handle.
*
* @return An iterable object to use to _visit_ the handle.
*/
[[nodiscard]] iterable storage() const noexcept {
auto underlying = owner_or_assert().storage();
return iterable{{entt, underlying.begin(), underlying.end()}, {entt, underlying.end(), underlying.end()}};
}
/*! @copydoc valid */
[[nodiscard]] explicit operator bool() const noexcept {
return owner && owner->valid(entt);
}
/**
* @brief Checks if a handle refers to a valid registry and entity.
* @return True if the handle refers to a valid registry and entity, false
* otherwise.
*/
[[nodiscard]] bool valid() const {
return static_cast<bool>(*this);
}
/**
* @brief Returns a pointer to the underlying registry, if any.
* @return A pointer to the underlying registry, if any.
*/
[[nodiscard]] registry_type *registry() const noexcept {
return owner;
}
/**
* @brief Returns the entity associated with a handle.
* @return The entity associated with the handle.
*/
[[nodiscard]] entity_type entity() const noexcept {
return entt;
}
/*! @copydoc entity */
[[nodiscard]] operator entity_type() const noexcept {
return entity();
}
/*! @brief Destroys the entity associated with a handle. */
void destroy() {
owner_or_assert().destroy(stl::exchange(entt, null));
}
/**
* @brief Destroys the entity associated with a handle.
* @param version A desired version upon destruction.
*/
void destroy(const version_type version) {
owner_or_assert().destroy(stl::exchange(entt, null), version);
}
/**
* @brief Assigns the given element to a handle.
* @tparam Type Type of element to create.
* @tparam Args Types of arguments to use to construct the element.
* @param args Parameters to use to initialize the element.
* @return A reference to the newly created element.
*/
template<typename Type, typename... Args>
// NOLINTNEXTLINE(modernize-use-nodiscard)
decltype(auto) emplace(Args &&...args) const {
static_assert(((sizeof...(Scope) == 0) || ... || stl::is_same_v<Type, Scope>), "Invalid type");
return owner_or_assert().template emplace<Type>(entt, stl::forward<Args>(args)...);
}
/**
* @brief Assigns or replaces the given element for a handle.
* @tparam Type Type of element to assign or replace.
* @tparam Args Types of arguments to use to construct the element.
* @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(Args &&...args) const {
static_assert(((sizeof...(Scope) == 0) || ... || stl::is_same_v<Type, Scope>), "Invalid type");
return owner_or_assert().template emplace_or_replace<Type>(entt, stl::forward<Args>(args)...);
}
/**
* @brief Patches the given element for a handle.
* @tparam Type Type of element to patch.
* @tparam Func Types of the function objects to invoke.
* @param func Valid function objects.
* @return A reference to the patched element.
*/
template<typename Type, typename... Func>
decltype(auto) patch(Func &&...func) const {
static_assert(((sizeof...(Scope) == 0) || ... || stl::is_same_v<Type, Scope>), "Invalid type");
return owner_or_assert().template patch<Type>(entt, stl::forward<Func>(func)...);
}
/**
* @brief Replaces the given element for a handle.
* @tparam Type Type of element to replace.
* @tparam Args Types of arguments to use to construct the element.
* @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(Args &&...args) const {
static_assert(((sizeof...(Scope) == 0) || ... || stl::is_same_v<Type, Scope>), "Invalid type");
return owner_or_assert().template replace<Type>(entt, stl::forward<Args>(args)...);
}
/**
* @brief Removes the given elements from a handle.
* @tparam Type Types of elements to remove.
* @return The number of elements actually removed.
*/
template<typename... Type>
// NOLINTNEXTLINE(modernize-use-nodiscard)
size_type remove() const {
static_assert(sizeof...(Scope) == 0 || (type_list_contains_v<type_list<Scope...>, Type> && ...), "Invalid type");
return owner_or_assert().template remove<Type...>(entt);
}
/**
* @brief Erases the given elements from a handle.
* @tparam Type Types of elements to erase.
*/
template<typename... Type>
void erase() const {
static_assert(sizeof...(Scope) == 0 || (type_list_contains_v<type_list<Scope...>, Type> && ...), "Invalid type");
owner_or_assert().template erase<Type...>(entt);
}
/**
* @brief Checks if a handle has all the given elements.
* @tparam Type Elements for which to perform the check.
* @return True if the handle has all the elements, false otherwise.
*/
template<typename... Type>
[[nodiscard]] decltype(auto) all_of() const {
return owner_or_assert().template all_of<Type...>(entt);
}
/**
* @brief Checks if a handle has at least one of the given elements.
* @tparam Type Elements for which to perform the check.
* @return True if the handle has at least one of the given elements,
* false otherwise.
*/
template<typename... Type>
[[nodiscard]] decltype(auto) any_of() const {
return owner_or_assert().template any_of<Type...>(entt);
}
/**
* @brief Returns references to the given elements for a handle.
* @tparam Type Types of elements to get.
* @return References to the elements owned by the handle.
*/
template<typename... Type>
[[nodiscard]] decltype(auto) get() const {
static_assert(sizeof...(Scope) == 0 || (type_list_contains_v<type_list<Scope...>, Type> && ...), "Invalid type");
return owner_or_assert().template get<Type...>(entt);
}
/**
* @brief Returns a reference to the given element for a handle.
* @tparam Type Type of element to get.
* @tparam Args Types of arguments to use to construct the element.
* @param args Parameters to use to initialize the element.
* @return Reference to the element owned by the handle.
*/
template<typename Type, typename... Args>
[[nodiscard]] decltype(auto) get_or_emplace(Args &&...args) const {
static_assert(((sizeof...(Scope) == 0) || ... || stl::is_same_v<Type, Scope>), "Invalid type");
return owner_or_assert().template get_or_emplace<Type>(entt, stl::forward<Args>(args)...);
}
/**
* @brief Returns pointers to the given elements for a handle.
* @tparam Type Types of elements to get.
* @return Pointers to the elements owned by the handle.
*/
template<typename... Type>
[[nodiscard]] auto try_get() const {
static_assert(sizeof...(Scope) == 0 || (type_list_contains_v<type_list<Scope...>, Type> && ...), "Invalid type");
return owner_or_assert().template try_get<Type...>(entt);
}
/**
* @brief Checks if a handle has elements assigned.
* @return True if the handle has no elements assigned, false otherwise.
*/
[[nodiscard]] bool orphan() const {
return owner_or_assert().orphan(entt);
}
/**
* @brief Compares two handles.
* @tparam Other Scope of the other handle.
* @param other A valid handle.
* @return True if both handles refer to the same registry and the same
* entity, false otherwise.
*/
template<typename... Other>
[[nodiscard]] bool operator==(const basic_handle<Other...> &other) const noexcept {
return owner == other.registry() && entt == other.entity();
}
/**
* @brief Compares a handle with the null object.
* @param other A null object yet to be converted.
* @return False if the two elements differ, true otherwise.
*/
[[nodiscard]] constexpr bool operator==(const null_t other) const noexcept {
return (entt == other);
}
/**
* @brief Returns a const handle from a non-const one.
* @tparam Other A valid entity type.
* @tparam Args Scope of the handle to construct.
* @return A const handle referring to the same registry and the same
* entity.
*/
template<typename Other, typename... Args>
operator basic_handle<Other, Args...>() const noexcept {
static_assert(stl::is_same_v<Other, Registry> || stl::is_same_v<stl::remove_const_t<Other>, Registry>, "Invalid conversion between different handles");
static_assert((sizeof...(Scope) == 0 || ((sizeof...(Args) != 0 && sizeof...(Args) <= sizeof...(Scope)) && ... && (type_list_contains_v<type_list<Scope...>, Args>))), "Invalid conversion between different handles");
return owner ? basic_handle<Other, Args...>{*owner, entt} : basic_handle<Other, Args...>{};
}
private:
registry_type *owner;
entity_type entt;
};
} // namespace entt
#endif

View File

@@ -0,0 +1,256 @@
#ifndef ENTT_ENTITY_HELPER_HPP
#define ENTT_ENTITY_HELPER_HPP
#include "../core/fwd.hpp"
#include "../core/type_traits.hpp"
#include "../stl/memory.hpp"
#include "../stl/type_traits.hpp"
#include "../stl/utility.hpp"
#include "component.hpp"
#include "fwd.hpp"
#include "group.hpp"
#include "storage.hpp"
#include "view.hpp"
namespace entt {
/**
* @brief Converts a registry to a view.
* @tparam Registry Basic registry type.
*/
template<typename Registry>
class as_view {
template<typename... Get, typename... Exclude>
[[nodiscard]] auto dispatch(get_t<Get...>, exclude_t<Exclude...>) const {
return reg->template view<constness_as_t<typename Get::element_type, Get>...>(exclude_t<constness_as_t<typename Exclude::element_type, Exclude>...>{});
}
public:
/*! @brief Type of registry to convert. */
using registry_type = Registry;
/*! @brief Underlying entity identifier. */
using entity_type = registry_type::entity_type;
/**
* @brief Constructs a converter for a given registry.
* @param source A valid reference to a registry.
*/
as_view(registry_type &source) noexcept
: reg{&source} {}
/**
* @brief Conversion function from a registry to a view.
* @tparam Get Type of storage used to construct the view.
* @tparam Exclude Types of storage used to filter the view.
* @return A newly created view.
*/
template<typename Get, typename Exclude>
operator basic_view<Get, Exclude>() const {
return dispatch(Get{}, Exclude{});
}
private:
registry_type *reg;
};
/**
* @brief Converts a registry to a group.
* @tparam Registry Basic registry type.
*/
template<typename Registry>
class as_group {
template<typename... Owned, typename... Get, typename... Exclude>
[[nodiscard]] auto dispatch(owned_t<Owned...>, get_t<Get...>, exclude_t<Exclude...>) const {
if constexpr(stl::is_const_v<registry_type>) {
return reg->template group_if_exists<typename Owned::element_type...>(get_t<typename Get::element_type...>{}, exclude_t<typename Exclude::element_type...>{});
} else {
return reg->template group<constness_as_t<typename Owned::element_type, Owned>...>(get_t<constness_as_t<typename Get::element_type, Get>...>{}, exclude_t<constness_as_t<typename Exclude::element_type, Exclude>...>{});
}
}
public:
/*! @brief Type of registry to convert. */
using registry_type = Registry;
/*! @brief Underlying entity identifier. */
using entity_type = registry_type::entity_type;
/**
* @brief Constructs a converter for a given registry.
* @param source A valid reference to a registry.
*/
as_group(registry_type &source) noexcept
: reg{&source} {}
/**
* @brief Conversion function from a registry to a group.
* @tparam Owned Types of _owned_ by the group.
* @tparam Get Types of storage _observed_ by the group.
* @tparam Exclude Types of storage used to filter the group.
* @return A newly created group.
*/
template<typename Owned, typename Get, typename Exclude>
operator basic_group<Owned, Get, Exclude>() const {
return dispatch(Owned{}, Get{}, Exclude{});
}
private:
registry_type *reg;
};
/**
* @brief Helper to create a listener that directly invokes a member function.
* @tparam Member Member function to invoke on an element of the given type.
* @tparam Registry Basic registry type.
* @param reg A registry that contains the given entity and its elements.
* @param entt Entity from which to get the element.
*/
template<auto Member, typename Registry = stl::decay_t<nth_argument_t<0u, decltype(Member)>>>
void invoke(Registry &reg, const typename Registry::entity_type entt) {
static_assert(stl::is_member_function_pointer_v<decltype(Member)>, "Invalid pointer to non-static member function");
(reg.template get<member_class_t<decltype(Member)>>(entt).*Member)(reg, entt);
}
/**
* @brief Returns the entity associated with a given element.
*
* @warning
* Currently, this function only works correctly with the default storage as it
* makes assumptions about how the elements are laid out.
*
* @tparam Args Storage type template parameters.
* @param storage A storage that contains the given element.
* @param instance A valid element instance.
* @return The entity associated with the given element.
*/
template<typename... Args>
basic_storage<Args...>::entity_type to_entity(const basic_storage<Args...> &storage, const typename basic_storage<Args...>::value_type &instance) {
using traits_type = component_traits<typename basic_storage<Args...>::value_type, typename basic_storage<Args...>::entity_type>;
static_assert(traits_type::page_size != 0u, "Unexpected page size");
const auto *page = storage.raw();
// NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic)
for(stl::size_t pos{}, count = storage.size(); pos < count; pos += traits_type::page_size, ++page) {
if(const auto dist = (stl::addressof(instance) - *page); dist >= 0 && dist < static_cast<decltype(dist)>(traits_type::page_size)) {
return *(static_cast<const basic_storage<Args...>::base_type &>(storage).rbegin() + static_cast<decltype(dist)>(pos) + dist);
}
}
// NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic)
return null;
}
/*! @brief Primary template isn't defined on purpose. */
template<typename...>
struct sigh_helper;
/**
* @brief Signal connection helper for registries.
* @tparam Registry Basic registry type.
*/
template<typename Registry>
struct sigh_helper<Registry> {
/*! @brief Registry type. */
using registry_type = Registry;
/**
* @brief Constructs a helper for a given registry.
* @param ref A valid reference to a registry.
*/
sigh_helper(registry_type &ref)
: bucket{&ref} {}
/**
* @brief Binds a properly initialized helper to a given signal type.
* @tparam Type Type of signal to bind the helper to.
* @param id Optional name for the underlying storage to use.
* @return A helper for a given registry and signal type.
*/
template<typename Type>
auto with(const id_type id = type_hash<Type>::value()) noexcept {
return sigh_helper<registry_type, Type>{*bucket, id};
}
/**
* @brief Returns a reference to the underlying registry.
* @return A reference to the underlying registry.
*/
[[nodiscard]] registry_type &registry() noexcept {
return *bucket;
}
private:
registry_type *bucket;
};
/**
* @brief Signal connection helper for registries.
* @tparam Registry Basic registry type.
* @tparam Type Type of signal to connect listeners to.
*/
template<typename Registry, typename Type>
struct sigh_helper<Registry, Type> final: sigh_helper<Registry> {
/*! @brief Registry type. */
using registry_type = Registry;
/**
* @brief Constructs a helper for a given registry.
* @param ref A valid reference to a registry.
* @param id Optional name for the underlying storage to use.
*/
sigh_helper(registry_type &ref, const id_type id = type_hash<Type>::value())
: sigh_helper<Registry>{ref},
name{id} {}
/**
* @brief Forwards the call to `on_construct` on the underlying storage.
* @tparam Candidate Function or member to connect.
* @tparam Args Type of class or type of payload, if any.
* @param args A valid object that fits the purpose, if any.
* @return This helper.
*/
template<auto Candidate, typename... Args>
auto on_construct(Args &&...args) {
this->registry().template on_construct<Type>(name).template connect<Candidate>(stl::forward<Args>(args)...);
return *this;
}
/**
* @brief Forwards the call to `on_update` on the underlying storage.
* @tparam Candidate Function or member to connect.
* @tparam Args Type of class or type of payload, if any.
* @param args A valid object that fits the purpose, if any.
* @return This helper.
*/
template<auto Candidate, typename... Args>
auto on_update(Args &&...args) {
this->registry().template on_update<Type>(name).template connect<Candidate>(stl::forward<Args>(args)...);
return *this;
}
/**
* @brief Forwards the call to `on_destroy` on the underlying storage.
* @tparam Candidate Function or member to connect.
* @tparam Args Type of class or type of payload, if any.
* @param args A valid object that fits the purpose, if any.
* @return This helper.
*/
template<auto Candidate, typename... Args>
auto on_destroy(Args &&...args) {
this->registry().template on_destroy<Type>(name).template connect<Candidate>(stl::forward<Args>(args)...);
return *this;
}
private:
id_type name;
};
/**
* @brief Deduction guide.
* @tparam Registry Basic registry type.
*/
template<typename Registry>
sigh_helper(Registry &) -> sigh_helper<Registry>;
} // namespace entt
#endif

View File

@@ -0,0 +1,593 @@
#ifndef ENTT_ENTITY_MIXIN_HPP
#define ENTT_ENTITY_MIXIN_HPP
#include "../config/config.h"
#include "../core/any.hpp"
#include "../core/type_info.hpp"
#include "../signal/sigh.hpp"
#include "../stl/concepts.hpp"
#include "../stl/iterator.hpp"
#include "../stl/type_traits.hpp"
#include "../stl/utility.hpp"
#include "../stl/vector.hpp"
#include "entity.hpp"
#include "fwd.hpp"
namespace entt {
/*! @cond ENTT_INTERNAL */
namespace internal {
template<typename, typename>
struct has_on_construct final: stl::false_type {};
template<typename Type, typename Registry>
requires stl::invocable<decltype(&Type::on_construct), Registry &, typename Registry::entity_type>
struct has_on_construct<Type, Registry>: stl::true_type {};
template<typename, typename>
struct has_on_update final: stl::false_type {};
template<typename Type, typename Registry>
requires stl::invocable<decltype(&Type::on_update), Registry &, typename Registry::entity_type>
struct has_on_update<Type, Registry>: stl::true_type {};
template<typename, typename>
struct has_on_destroy final: stl::false_type {};
template<typename Type, typename Registry>
requires stl::invocable<decltype(&Type::on_destroy), Registry &, typename Registry::entity_type>
struct has_on_destroy<Type, Registry>: stl::true_type {};
} // namespace internal
/*! @endcond */
/**
* @brief Mixin type used to add signal support to storage types.
*
* The function type of a listener is equivalent to:
*
* @code{.cpp}
* void(basic_registry<entity_type> &, entity_type);
* @endcode
*
* This applies to all signals made available.
*
* @tparam Type Underlying storage type.
* @tparam Registry Basic registry type.
*/
template<typename Type, typename Registry>
class basic_sigh_mixin final: public Type {
using underlying_type = Type;
using owner_type = Registry;
using basic_registry_type = basic_registry<typename owner_type::entity_type, typename owner_type::allocator_type>;
using sigh_type = sigh<void(owner_type &, const typename underlying_type::entity_type), typename underlying_type::allocator_type>;
using underlying_iterator = underlying_type::base_type::basic_iterator;
static_assert(stl::is_base_of_v<basic_registry_type, owner_type>, "Invalid registry type");
[[nodiscard]] auto &owner_or_assert() const noexcept {
ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry");
return static_cast<owner_type &>(*owner);
}
private:
void pop(underlying_iterator first, underlying_iterator last) final {
if(auto &reg = owner_or_assert(); destruction.empty()) {
underlying_type::pop(first, last);
} else {
for(; first != last; ++first) {
const auto entt = *first;
destruction.publish(reg, entt);
const auto it = underlying_type::find(entt);
underlying_type::pop(it, it + 1u);
}
}
}
void pop_all() final {
if(auto &reg = owner_or_assert(); !destruction.empty()) {
if constexpr(stl::is_same_v<typename underlying_type::element_type, entity_type>) {
for(typename underlying_type::size_type pos{}, last = underlying_type::free_list(); pos < last; ++pos) {
destruction.publish(reg, underlying_type::base_type::operator[](pos));
}
} else {
for(auto entt: static_cast<underlying_type::base_type &>(*this)) {
if constexpr(underlying_type::storage_policy == deletion_policy::in_place) {
if(entt != tombstone) {
destruction.publish(reg, entt);
}
} else {
destruction.publish(reg, entt);
}
}
}
}
underlying_type::pop_all();
}
underlying_iterator try_emplace(const underlying_type::entity_type entt, const bool force_back, const void *value) final {
const auto it = underlying_type::try_emplace(entt, force_back, value);
if(auto &reg = owner_or_assert(); it != underlying_type::base_type::end()) {
construction.publish(reg, *it);
}
return it;
}
void bind_any(any value) noexcept final {
owner = any_cast<basic_registry_type>(&value);
if constexpr(!stl::is_same_v<registry_type, basic_registry_type>) {
if(owner == nullptr) {
owner = any_cast<registry_type>(&value);
}
}
underlying_type::bind_any(stl::move(value));
}
public:
/*! @brief Allocator type. */
using allocator_type = underlying_type::allocator_type;
/*! @brief Underlying entity identifier. */
using entity_type = underlying_type::entity_type;
/*! @brief Expected registry type. */
using registry_type = owner_type;
/*! @brief Default constructor. */
basic_sigh_mixin()
: basic_sigh_mixin{allocator_type{}} {}
/**
* @brief Constructs an empty storage with a given allocator.
* @param allocator The allocator to use.
*/
explicit basic_sigh_mixin(const allocator_type &allocator)
: underlying_type{allocator},
owner{},
construction{allocator},
destruction{allocator},
update{allocator} {
if constexpr(internal::has_on_construct<typename underlying_type::element_type, Registry>::value) {
sink{construction}.template connect<&underlying_type::element_type::on_construct>();
}
if constexpr(internal::has_on_update<typename underlying_type::element_type, Registry>::value) {
sink{update}.template connect<&underlying_type::element_type::on_update>();
}
if constexpr(internal::has_on_destroy<typename underlying_type::element_type, Registry>::value) {
sink{destruction}.template connect<&underlying_type::element_type::on_destroy>();
}
}
/*! @brief Default copy constructor, deleted on purpose. */
basic_sigh_mixin(const basic_sigh_mixin &) = delete;
/**
* @brief Move constructor.
* @param other The instance to move from.
*/
basic_sigh_mixin(basic_sigh_mixin &&other) noexcept
: underlying_type{static_cast<underlying_type &&>(other)},
owner{other.owner},
construction{stl::move(other.construction)},
destruction{stl::move(other.destruction)},
update{stl::move(other.update)} {}
/**
* @brief Allocator-extended move constructor.
* @param other The instance to move from.
* @param allocator The allocator to use.
*/
basic_sigh_mixin(basic_sigh_mixin &&other, const allocator_type &allocator)
: underlying_type{static_cast<underlying_type &&>(other), allocator},
owner{other.owner},
construction{stl::move(other.construction), allocator},
destruction{stl::move(other.destruction), allocator},
update{stl::move(other.update), allocator} {}
/*! @brief Default destructor. */
~basic_sigh_mixin() override = default;
/**
* @brief Default copy assignment operator, deleted on purpose.
* @return This mixin.
*/
basic_sigh_mixin &operator=(const basic_sigh_mixin &) = delete;
/**
* @brief Move assignment operator.
* @param other The instance to move from.
* @return This mixin.
*/
basic_sigh_mixin &operator=(basic_sigh_mixin &&other) noexcept {
swap(other);
return *this;
}
/**
* @brief Exchanges the contents with those of a given storage.
* @param other Storage to exchange the content with.
*/
void swap(basic_sigh_mixin &other) noexcept {
using stl::swap;
swap(owner, other.owner);
swap(construction, other.construction);
swap(destruction, other.destruction);
swap(update, other.update);
underlying_type::swap(other);
}
/**
* @brief Returns a sink object.
*
* The sink returned by this function can be used to receive notifications
* whenever a new instance is created and assigned to an entity.<br/>
* Listeners are invoked after the object has been assigned to the entity.
*
* @sa sink
*
* @return A temporary sink object.
*/
[[nodiscard]] auto on_construct() noexcept {
return sink{construction};
}
/**
* @brief Returns a sink object.
*
* The sink returned by this function can be used to receive notifications
* whenever an instance is explicitly updated.<br/>
* Listeners are invoked after the object has been updated.
*
* @sa sink
*
* @return A temporary sink object.
*/
[[nodiscard]] auto on_update() noexcept {
return sink{update};
}
/**
* @brief Returns a sink object.
*
* The sink returned by this function can be used to receive notifications
* whenever an instance is removed from an entity and thus destroyed.<br/>
* Listeners are invoked before the object has been removed from the entity.
*
* @sa sink
*
* @return A temporary sink object.
*/
[[nodiscard]] auto on_destroy() noexcept {
return sink{destruction};
}
/**
* @brief Checks if a mixin refers to a valid registry.
* @return True if the mixin refers to a valid registry, false otherwise.
*/
[[nodiscard]] explicit operator bool() const noexcept {
return (owner != nullptr);
}
/**
* @brief Returns a pointer to the underlying registry, if any.
* @return A pointer to the underlying registry, if any.
*/
[[nodiscard]] const registry_type &registry() const noexcept {
return owner_or_assert();
}
/*! @copydoc registry */
[[nodiscard]] registry_type &registry() noexcept {
return owner_or_assert();
}
/**
* @brief Creates a new identifier or recycles a destroyed one.
* @return A valid identifier.
*/
auto generate() {
const auto entt = underlying_type::generate();
construction.publish(owner_or_assert(), entt);
return entt;
}
/**
* @brief Creates a new identifier or recycles a destroyed one.
* @param hint Required identifier.
* @return A valid identifier.
*/
entity_type generate(const entity_type hint) {
const auto entt = underlying_type::generate(hint);
construction.publish(owner_or_assert(), entt);
return entt;
}
/**
* @brief Assigns each element in a range an identifier.
* @tparam It Type of output iterator.
* @param first An iterator to the first element of the range to generate.
* @param last An iterator past the last element of the range to generate.
*/
template<stl::output_iterator<entity_type> It>
void generate(It first, It last) {
underlying_type::generate(first, last);
if(auto &reg = owner_or_assert(); !construction.empty()) {
for(; first != last; ++first) {
construction.publish(reg, *first);
}
}
}
/**
* @brief Assigns an entity to a storage and constructs its object.
* @tparam Args Types of arguments to forward to the underlying storage.
* @param entt A valid identifier.
* @param args Parameters to forward to the underlying storage.
* @return A reference to the newly created object.
*/
template<typename... Args>
decltype(auto) emplace(const entity_type entt, Args &&...args) {
underlying_type::emplace(entt, stl::forward<Args>(args)...);
construction.publish(owner_or_assert(), entt);
return this->get(entt);
}
/**
* @brief Updates the instance assigned to a given entity in-place.
* @tparam Func Types of the function objects to invoke.
* @param entt A valid identifier.
* @param func Valid function objects.
* @return A reference to the patched instance.
*/
template<typename... Func>
decltype(auto) patch(const entity_type entt, Func &&...func) {
underlying_type::patch(entt, stl::forward<Func>(func)...);
update.publish(owner_or_assert(), entt);
return this->get(entt);
}
/**
* @brief Assigns one or more entities to a storage and constructs their
* objects from a given instance.
* @tparam Args Types of arguments to forward to the underlying storage.
* @param first An iterator to the first element of the range of entities.
* @param last An iterator past the last element of the range of entities.
* @param args Parameters to use to forward to the underlying storage.
*/
template<typename... Args>
void insert(stl::input_iterator auto first, stl::input_iterator auto last, Args &&...args) {
auto from = underlying_type::size();
underlying_type::insert(first, last, stl::forward<Args>(args)...);
if(auto &reg = owner_or_assert(); !construction.empty()) {
// fine as long as insert passes force_back true to try_emplace
for(const auto to = underlying_type::size(); from != to; ++from) {
construction.publish(reg, underlying_type::operator[](from));
}
}
}
private:
basic_registry_type *owner;
sigh_type construction;
sigh_type destruction;
sigh_type update;
};
/**
* @brief Mixin type used to add _reactive_ support to storage types.
* @tparam Type Underlying storage type.
* @tparam Registry Basic registry type.
*/
template<typename Type, typename Registry>
class basic_reactive_mixin final: public Type {
using underlying_type = Type;
using owner_type = Registry;
using alloc_traits = stl::allocator_traits<typename underlying_type::allocator_type>;
using basic_registry_type = basic_registry<typename owner_type::entity_type, typename owner_type::allocator_type>;
using container_type = stl::vector<connection, typename alloc_traits::template rebind_alloc<connection>>;
static_assert(stl::is_base_of_v<basic_registry_type, owner_type>, "Invalid registry type");
[[nodiscard]] auto &owner_or_assert() const noexcept {
ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry");
return static_cast<owner_type &>(*owner);
}
void emplace_element(const Registry &, underlying_type::entity_type entity) {
if(!underlying_type::contains(entity)) {
underlying_type::emplace(entity);
}
}
private:
void bind_any(any value) noexcept final {
owner = any_cast<basic_registry_type>(&value);
if constexpr(!stl::is_same_v<registry_type, basic_registry_type>) {
if(owner == nullptr) {
owner = any_cast<registry_type>(&value);
}
}
underlying_type::bind_any(stl::move(value));
}
public:
/*! @brief Allocator type. */
using allocator_type = underlying_type::allocator_type;
/*! @brief Underlying entity identifier. */
using entity_type = underlying_type::entity_type;
/*! @brief Expected registry type. */
using registry_type = owner_type;
/*! @brief Default constructor. */
basic_reactive_mixin()
: basic_reactive_mixin{allocator_type{}} {}
/**
* @brief Constructs an empty storage with a given allocator.
* @param allocator The allocator to use.
*/
explicit basic_reactive_mixin(const allocator_type &allocator)
: underlying_type{allocator},
owner{},
conn{allocator} {
}
/*! @brief Default copy constructor, deleted on purpose. */
basic_reactive_mixin(const basic_reactive_mixin &) = delete;
/**
* @brief Move constructor.
* @param other The instance to move from.
*/
basic_reactive_mixin(basic_reactive_mixin &&other) noexcept
: underlying_type{static_cast<underlying_type &&>(other)},
owner{other.owner},
conn{stl::move(other.conn)} {
}
/**
* @brief Allocator-extended move constructor.
* @param other The instance to move from.
* @param allocator The allocator to use.
*/
basic_reactive_mixin(basic_reactive_mixin &&other, const allocator_type &allocator)
: underlying_type{static_cast<underlying_type &&>(other), allocator},
owner{other.owner},
conn{stl::move(other.conn), allocator} {
}
/*! @brief Default destructor. */
~basic_reactive_mixin() override = default;
/**
* @brief Default copy assignment operator, deleted on purpose.
* @return This mixin.
*/
basic_reactive_mixin &operator=(const basic_reactive_mixin &) = delete;
/**
* @brief Move assignment operator.
* @param other The instance to move from.
* @return This mixin.
*/
basic_reactive_mixin &operator=(basic_reactive_mixin &&other) noexcept {
underlying_type::swap(other);
return *this;
}
/**
* @brief Makes storage _react_ to creation of objects of the given type.
* @tparam Clazz Type of element to _react_ to.
* @tparam Candidate Function to use to _react_ to the event.
* @param id Optional name used to map the storage within the registry.
* @return This mixin.
*/
template<typename Clazz, auto Candidate = &basic_reactive_mixin::emplace_element>
basic_reactive_mixin &on_construct(const id_type id = type_hash<Clazz>::value()) {
auto curr = owner_or_assert().template storage<Clazz>(id).on_construct().template connect<Candidate>(*this);
conn.push_back(stl::move(curr));
return *this;
}
/**
* @brief Makes storage _react_ to update of objects of the given type.
* @tparam Clazz Type of element to _react_ to.
* @tparam Candidate Function to use to _react_ to the event.
* @param id Optional name used to map the storage within the registry.
* @return This mixin.
*/
template<typename Clazz, auto Candidate = &basic_reactive_mixin::emplace_element>
basic_reactive_mixin &on_update(const id_type id = type_hash<Clazz>::value()) {
auto curr = owner_or_assert().template storage<Clazz>(id).on_update().template connect<Candidate>(*this);
conn.push_back(stl::move(curr));
return *this;
}
/**
* @brief Makes storage _react_ to destruction of objects of the given type.
* @tparam Clazz Type of element to _react_ to.
* @tparam Candidate Function to use to _react_ to the event.
* @param id Optional name used to map the storage within the registry.
* @return This mixin.
*/
template<typename Clazz, auto Candidate = &basic_reactive_mixin::emplace_element>
basic_reactive_mixin &on_destroy(const id_type id = type_hash<Clazz>::value()) {
auto curr = owner_or_assert().template storage<Clazz>(id).on_destroy().template connect<Candidate>(*this);
conn.push_back(stl::move(curr));
return *this;
}
/**
* @brief Checks if a mixin refers to a valid registry.
* @return True if the mixin refers to a valid registry, false otherwise.
*/
[[nodiscard]] explicit operator bool() const noexcept {
return (owner != nullptr);
}
/**
* @brief Returns a pointer to the underlying registry, if any.
* @return A pointer to the underlying registry, if any.
*/
[[nodiscard]] const registry_type &registry() const noexcept {
return owner_or_assert();
}
/*! @copydoc registry */
[[nodiscard]] registry_type &registry() noexcept {
return owner_or_assert();
}
/**
* @brief Returns a view that is filtered by the underlying storage.
* @tparam Get 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... Get, typename... Exclude>
[[nodiscard]] basic_view<get_t<const basic_reactive_mixin, typename basic_registry_type::template storage_for_type<const Get>...>, exclude_t<typename basic_registry_type::template storage_for_type<const Exclude>...>>
view(exclude_t<Exclude...> = exclude_t{}) const {
const owner_type &parent = owner_or_assert();
basic_view<get_t<const basic_reactive_mixin, typename basic_registry_type::template storage_for_type<const Get>...>, exclude_t<typename basic_registry_type::template storage_for_type<const Exclude>...>> elem{};
[&elem](const auto *...curr) { ((curr ? elem.storage(*curr) : void()), ...); }(parent.template storage<stl::remove_const_t<Exclude>>()..., parent.template storage<stl::remove_const_t<Get>>()..., this);
return elem;
}
/*! @copydoc view */
template<typename... Get, typename... Exclude>
[[nodiscard]] basic_view<get_t<const basic_reactive_mixin, typename basic_registry_type::template storage_for_type<Get>...>, exclude_t<typename basic_registry_type::template storage_for_type<Exclude>...>>
view(exclude_t<Exclude...> = exclude_t{}) {
stl::conditional_t<((stl::is_const_v<Get> && ...) && (stl::is_const_v<Exclude> && ...)), const owner_type, owner_type> &parent = owner_or_assert();
return {*this, parent.template storage<stl::remove_const_t<Get>>()..., parent.template storage<stl::remove_const_t<Exclude>>()...};
}
/*! @brief Releases all connections to the underlying registry, if any. */
void reset() {
for(auto &&curr: conn) {
curr.release();
}
conn.clear();
}
private:
basic_registry_type *owner;
container_type conn;
};
} // namespace entt
#endif

View File

@@ -0,0 +1,437 @@
#ifndef ENTT_ENTITY_ORGANIZER_HPP
#define ENTT_ENTITY_ORGANIZER_HPP
#include "../core/type_info.hpp"
#include "../core/type_traits.hpp"
#include "../core/utility.hpp"
#include "../graph/adjacency_matrix.hpp"
#include "../graph/flow.hpp"
#include "../stl/cstddef.hpp"
#include "../stl/type_traits.hpp"
#include "../stl/utility.hpp"
#include "../stl/vector.hpp"
#include "fwd.hpp"
#include "helper.hpp"
namespace entt {
/*! @cond ENTT_INTERNAL */
namespace internal {
template<typename>
struct is_view: stl::false_type {};
template<typename... Args>
struct is_view<basic_view<Args...>>: stl::true_type {};
template<typename Type>
inline constexpr bool is_view_v = is_view<Type>::value;
template<typename>
struct is_group: stl::false_type {};
template<typename... Args>
struct is_group<basic_group<Args...>>: stl::true_type {};
template<typename Type>
inline constexpr bool is_group_v = is_group<Type>::value;
template<typename Type, typename Override>
struct unpack_type {
using ro = stl::conditional_t<
type_list_contains_v<Override, const Type> || (stl::is_const_v<Type> && !type_list_contains_v<Override, stl::remove_const_t<Type>>),
type_list<stl::remove_const_t<Type>>,
type_list<>>;
using rw = stl::conditional_t<
type_list_contains_v<Override, stl::remove_const_t<Type>> || (!stl::is_const_v<Type> && !type_list_contains_v<Override, const Type>),
type_list<Type>,
type_list<>>;
};
template<typename... Args, typename... Override>
struct unpack_type<basic_registry<Args...>, type_list<Override...>> {
using ro = type_list<>;
using rw = type_list<>;
};
template<typename... Args, typename... Override>
struct unpack_type<const basic_registry<Args...>, type_list<Override...>>
: unpack_type<basic_registry<Args...>, type_list<Override...>> {};
template<typename... Get, typename... Exclude, typename... Override>
struct unpack_type<basic_view<get_t<Get...>, exclude_t<Exclude...>>, type_list<Override...>> {
using ro = type_list_cat_t<type_list<typename Exclude::element_type...>, typename unpack_type<constness_as_t<typename Get::element_type, Get>, type_list<Override...>>::ro...>;
using rw = type_list_cat_t<typename unpack_type<constness_as_t<typename Get::element_type, Get>, type_list<Override...>>::rw...>;
};
template<typename... Get, typename... Exclude, typename... Override>
struct unpack_type<const basic_view<get_t<Get...>, exclude_t<Exclude...>>, type_list<Override...>>
: unpack_type<basic_view<get_t<Get...>, exclude_t<Exclude...>>, type_list<Override...>> {};
template<typename... Owned, typename... Get, typename... Exclude, typename... Override>
struct unpack_type<basic_group<owned_t<Owned...>, get_t<Get...>, exclude_t<Exclude...>>, type_list<Override...>> {
using ro = type_list_cat_t<type_list<typename Exclude::element_type...>, typename unpack_type<constness_as_t<typename Get::element_type, Get>, type_list<Override...>>::ro..., typename unpack_type<constness_as_t<typename Owned::element_type, Owned>, type_list<Override...>>::ro...>;
using rw = type_list_cat_t<typename unpack_type<constness_as_t<typename Get::element_type, Get>, type_list<Override...>>::rw..., typename unpack_type<constness_as_t<typename Owned::element_type, Owned>, type_list<Override...>>::rw...>;
};
template<typename... Owned, typename... Get, typename... Exclude, typename... Override>
struct unpack_type<const basic_group<owned_t<Owned...>, get_t<Get...>, exclude_t<Exclude...>>, type_list<Override...>>
: unpack_type<basic_group<owned_t<Owned...>, get_t<Get...>, exclude_t<Exclude...>>, type_list<Override...>> {};
template<typename, typename, typename>
struct resource_traits;
template<typename Registry, typename... Args, typename... Req>
struct resource_traits<Registry, type_list<Args...>, type_list<Req...>> {
using args = type_list<stl::remove_const_t<Args>...>;
using ro = type_list_cat_t<typename unpack_type<Args, type_list<Req...>>::ro..., typename unpack_type<Req, type_list<>>::ro...>;
using rw = type_list_cat_t<typename unpack_type<Args, type_list<Req...>>::rw..., typename unpack_type<Req, type_list<>>::rw...>;
static constexpr auto sync_point = (stl::is_same_v<Args, Registry> || ...);
};
template<typename Registry, typename... Req, typename Ret, typename... Args>
resource_traits<Registry, type_list<stl::remove_reference_t<Args>...>, type_list<Req...>> free_function_to_resource_traits(Ret (*)(Args...));
template<typename Registry, typename... Req, typename Ret, typename Type, typename... Args>
resource_traits<Registry, type_list<stl::remove_reference_t<Args>...>, type_list<Req...>> constrained_function_to_resource_traits(Ret (*)(Type &, Args...));
template<typename Registry, typename... Req, typename Ret, typename Class, typename... Args>
resource_traits<Registry, type_list<stl::remove_reference_t<Args>...>, type_list<Req...>> constrained_function_to_resource_traits(Ret (Class::*)(Args...));
template<typename Registry, typename... Req, typename Ret, typename Class, typename... Args>
resource_traits<Registry, type_list<stl::remove_reference_t<Args>...>, type_list<Req...>> constrained_function_to_resource_traits(Ret (Class::*)(Args...) const);
} // namespace internal
/*! @endcond */
/**
* @brief Utility class for creating a static task graph.
*
* This class offers minimal support (but sufficient in many cases) for creating
* an execution graph from functions and their requirements on resources.<br/>
* Note that the resulting tasks aren't executed in any case. This isn't the
* goal of the tool. Instead, they are returned to the user in the form of a
* graph that allows for safe execution.
*
* @tparam Registry Basic registry type.
*/
template<typename Registry>
class basic_organizer final {
using callback_type = void(const void *, Registry &);
using prepare_type = void(Registry &);
using dependency_type = stl::size_t(const bool, const type_info **, const stl::size_t);
struct vertex_data final {
stl::size_t ro_count{};
stl::size_t rw_count{};
const char *name{};
const void *payload{};
callback_type *callback{};
dependency_type *dependency{};
prepare_type *prepare{};
const type_info *info{};
};
template<typename Type>
[[nodiscard]] static decltype(auto) extract(Registry &reg) {
if constexpr(stl::is_same_v<Type, Registry>) {
return reg;
} else if constexpr(internal::is_view_v<Type>) {
return static_cast<Type>(as_view{reg});
} else if constexpr(internal::is_group_v<Type>) {
return static_cast<Type>(as_group{reg});
} else {
return reg.ctx().template emplace<stl::remove_reference_t<Type>>();
}
}
template<typename... Args>
[[nodiscard]] static auto to_args(Registry &reg, type_list<Args...>) {
return stl::tuple<decltype(extract<Args>(reg))...>(extract<Args>(reg)...);
}
template<typename... Type>
[[nodiscard]] static stl::size_t fill_dependencies(type_list<Type...>, [[maybe_unused]] const type_info **buffer, [[maybe_unused]] const stl::size_t count) {
if constexpr(sizeof...(Type) == 0u) {
return {};
} else {
// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays, modernize-avoid-c-arrays)
const type_info *info[]{&type_id<Type>()...};
const auto length = count < sizeof...(Type) ? count : sizeof...(Type);
for(stl::size_t pos{}; pos < length; ++pos) {
// NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
buffer[pos] = info[pos];
}
return length;
}
}
template<typename... RO, typename... RW>
void track_dependencies(stl::size_t index, const bool sync_point, type_list<RO...>, type_list<RW...>) {
builder.bind(static_cast<id_type>(index));
builder.set(type_hash<Registry>::value(), sync_point || (sizeof...(RO) + sizeof...(RW) == 0u));
(builder.ro(type_hash<RO>::value()), ...);
(builder.rw(type_hash<RW>::value()), ...);
}
public:
/*! Basic registry type. */
using registry_type = Registry;
/*! @brief Underlying entity identifier. */
using entity_type = registry_type::entity_type;
/*! @brief Unsigned integer type. */
using size_type = stl::size_t;
/*! @brief Raw task function type. */
using function_type = callback_type;
/*! @brief Vertex type of a task graph defined as an adjacency list. */
struct vertex {
/**
* @brief Constructs a vertex of the task graph.
* @param data The data associated with the vertex.
* @param from List of in-edges of the vertex.
* @param to List of out-edges of the vertex.
*/
vertex(vertex_data data, stl::vector<stl::size_t> from, stl::vector<stl::size_t> to)
: node{stl::move(data)},
in{stl::move(from)},
out{stl::move(to)} {}
/**
* @brief Fills a buffer with the type info objects for the writable
* resources of a vertex.
* @param buffer A buffer pre-allocated by the user.
* @param length The length of the user-supplied buffer.
* @return The number of type info objects written to the buffer.
*/
[[nodiscard]] size_type ro_dependency(const type_info **buffer, const stl::size_t length) const noexcept {
return node.dependency(false, buffer, length);
}
/**
* @brief Fills a buffer with the type info objects for the read-only
* resources of a vertex.
* @param buffer A buffer pre-allocated by the user.
* @param length The length of the user-supplied buffer.
* @return The number of type info objects written to the buffer.
*/
[[nodiscard]] size_type rw_dependency(const type_info **buffer, const stl::size_t length) const noexcept {
return node.dependency(true, buffer, length);
}
/**
* @brief Returns the number of read-only resources of a vertex.
* @return The number of read-only resources of the vertex.
*/
[[nodiscard]] size_type ro_count() const noexcept {
return node.ro_count;
}
/**
* @brief Returns the number of writable resources of a vertex.
* @return The number of writable resources of the vertex.
*/
[[nodiscard]] size_type rw_count() const noexcept {
return node.rw_count;
}
/**
* @brief Checks if a vertex is also a top-level one.
* @return True if the vertex is a top-level one, false otherwise.
*/
[[nodiscard]] bool top_level() const noexcept {
return in.empty();
}
/**
* @brief Returns a type info object associated with a vertex.
* @return A properly initialized type info object.
*/
[[nodiscard]] const type_info &info() const noexcept {
return *node.info;
}
/**
* @brief Returns a user defined name associated with a vertex, if any.
* @return The user defined name associated with the vertex, if any.
*/
[[nodiscard]] const char *name() const noexcept {
return node.name;
}
/**
* @brief Returns the function associated with a vertex.
* @return The function associated with the vertex.
*/
[[nodiscard]] function_type *callback() const noexcept {
return node.callback;
}
/**
* @brief Returns the payload associated with a vertex, if any.
* @return The payload associated with the vertex, if any.
*/
[[nodiscard]] const void *data() const noexcept {
return node.payload;
}
/**
* @brief Returns the list of in-edges of a vertex.
* @return The list of in-edges of a vertex.
*/
[[nodiscard]] const stl::vector<stl::size_t> &in_edges() const noexcept {
return in;
}
/**
* @brief Returns the list of out-edges of a vertex.
* @return The list of out-edges of a vertex.
*/
[[nodiscard]] const stl::vector<stl::size_t> &out_edges() const noexcept {
return out;
}
/**
* @brief Prepares a registry and assures that all required resources
* are properly instantiated before using them.
* @param reg A valid registry.
*/
void prepare(registry_type &reg) const {
node.prepare ? node.prepare(reg) : void();
}
private:
vertex_data node;
stl::vector<stl::size_t> in;
stl::vector<stl::size_t> out;
};
/**
* @brief Adds a free function to the task list.
* @tparam Candidate Function to add to the task list.
* @tparam Req Additional requirements and/or override resource access mode.
* @param name Optional name to associate with the task.
*/
template<auto Candidate, typename... Req>
void emplace(const char *name = nullptr) {
using resource_type = decltype(internal::free_function_to_resource_traits<registry_type, Req...>(Candidate));
callback_type *callback = +[](const void *, registry_type &reg) {
stl::apply(Candidate, to_args(reg, typename resource_type::args{}));
};
vertex_data vdata{
resource_type::ro::size,
resource_type::rw::size,
name,
nullptr,
callback,
+[](const bool rw, const type_info **buffer, const stl::size_t length) { return rw ? fill_dependencies(typename resource_type::rw{}, buffer, length) : fill_dependencies(typename resource_type::ro{}, buffer, length); },
+[](registry_type &reg) { void(to_args(reg, typename resource_type::args{})); },
&type_id<stl::integral_constant<decltype(Candidate), Candidate>>()};
track_dependencies(vertices.size(), resource_type::sync_point, typename resource_type::ro{}, typename resource_type::rw{});
vertices.push_back(stl::move(vdata));
}
/**
* @brief Adds a free function with payload or a member function with an
* instance to the task list.
* @tparam Candidate Function or member to add to the task list.
* @tparam Req Additional requirements and/or override resource access mode.
* @tparam Type Type of class or type of payload.
* @param value_or_instance A valid object that fits the purpose.
* @param name Optional name to associate with the task.
*/
template<auto Candidate, typename... Req, typename Type>
void emplace(Type &value_or_instance, const char *name = nullptr) {
using resource_type = decltype(internal::constrained_function_to_resource_traits<registry_type, Req...>(Candidate));
callback_type *callback = +[](const void *payload, registry_type &reg) {
Type *curr = static_cast<Type *>(const_cast<constness_as_t<void, Type> *>(payload));
stl::apply(Candidate, stl::tuple_cat(stl::forward_as_tuple(*curr), to_args(reg, typename resource_type::args{})));
};
vertex_data vdata{
resource_type::ro::size,
resource_type::rw::size,
name,
&value_or_instance,
callback,
+[](const bool rw, const type_info **buffer, const stl::size_t length) { return rw ? fill_dependencies(typename resource_type::rw{}, buffer, length) : fill_dependencies(typename resource_type::ro{}, buffer, length); },
+[](registry_type &reg) { void(to_args(reg, typename resource_type::args{})); },
&type_id<stl::integral_constant<decltype(Candidate), Candidate>>()};
track_dependencies(vertices.size(), resource_type::sync_point, typename resource_type::ro{}, typename resource_type::rw{});
vertices.push_back(stl::move(vdata));
}
/**
* @brief Adds an user defined function with optional payload to the task
* list.
* @tparam Req Additional requirements and/or override resource access mode.
* @param func Function to add to the task list.
* @param payload User defined arbitrary data.
* @param name Optional name to associate with the task.
*/
template<typename... Req>
void emplace(function_type *func, const void *payload = nullptr, const char *name = nullptr) {
using resource_type = internal::resource_traits<registry_type, type_list<>, type_list<Req...>>;
track_dependencies(vertices.size(), true, typename resource_type::ro{}, typename resource_type::rw{});
vertex_data vdata{
resource_type::ro::size,
resource_type::rw::size,
name,
payload,
func,
+[](const bool rw, const type_info **buffer, const stl::size_t length) { return rw ? fill_dependencies(typename resource_type::rw{}, buffer, length) : fill_dependencies(typename resource_type::ro{}, buffer, length); },
nullptr,
&type_id<void>()};
vertices.push_back(stl::move(vdata));
}
/**
* @brief Generates a task graph for the current content.
* @return The adjacency list of the task graph.
*/
[[nodiscard]] stl::vector<vertex> graph() const {
stl::vector<vertex> adjacency_list{};
adjacency_list.reserve(vertices.size());
for(auto adjacency_matrix = builder.graph(); auto curr: adjacency_matrix.vertices()) {
stl::vector<stl::size_t> in{};
stl::vector<stl::size_t> out{};
for(auto &&edge: adjacency_matrix.in_edges(curr)) {
in.push_back(edge.first);
}
for(auto &&edge: adjacency_matrix.out_edges(curr)) {
out.push_back(edge.second);
}
adjacency_list.emplace_back(vertices[curr], stl::move(in), stl::move(out));
}
return adjacency_list;
}
/*! @brief Erases all elements from a container. */
void clear() {
builder.clear();
vertices.clear();
}
private:
stl::vector<vertex_data> vertices;
flow builder;
};
} // namespace entt
#endif

View File

@@ -0,0 +1,28 @@
#ifndef ENTT_ENTITY_RANGES_HPP
#define ENTT_ENTITY_RANGES_HPP
#include <version>
#if defined(__cpp_lib_ranges)
# include <ranges>
# include "fwd.hpp"
namespace std::ranges {
template<class... Args>
inline constexpr bool enable_borrowed_range<entt::basic_view<Args...>>{true};
template<class... Args>
inline constexpr bool enable_borrowed_range<entt::basic_group<Args...>>{true};
template<class... Args>
inline constexpr bool enable_view<entt::basic_view<Args...>>{true};
template<class... Args>
inline constexpr bool enable_view<entt::basic_group<Args...>>{true};
} // namespace std::ranges
#endif
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,322 @@
#ifndef ENTT_ENTITY_RUNTIME_VIEW_HPP
#define ENTT_ENTITY_RUNTIME_VIEW_HPP
#include "../stl/algorithm.hpp"
#include "../stl/cstddef.hpp"
#include "../stl/iterator.hpp"
#include "../stl/utility.hpp"
#include "../stl/vector.hpp"
#include "entity.hpp"
#include "fwd.hpp"
namespace entt {
/*! @cond ENTT_INTERNAL */
namespace internal {
template<typename Set>
class runtime_view_iterator final {
using iterator_type = Set::iterator;
using iterator_traits = stl::iterator_traits<iterator_type>;
[[nodiscard]] bool valid() const {
return (!tombstone_check || *it != tombstone)
&& stl::all_of(++pools->begin(), pools->end(), [entt = *it](const auto *curr) { return curr->contains(entt); })
&& stl::none_of(filter->cbegin(), filter->cend(), [entt = *it](const auto *curr) { return curr && curr->contains(entt); });
}
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::bidirectional_iterator_tag;
constexpr runtime_view_iterator() noexcept
: pools{},
filter{},
it{},
tombstone_check{} {}
runtime_view_iterator(const stl::vector<Set *> &cpools, iterator_type curr, const stl::vector<Set *> &ignore) noexcept
: pools{&cpools},
filter{&ignore},
it{curr},
tombstone_check{pools->size() == 1u && (*pools)[0u]->policy() == deletion_policy::in_place} {
if(it != (*pools)[0]->end() && !valid()) {
++(*this);
}
}
runtime_view_iterator &operator++() {
++it;
for(const auto last = (*pools)[0]->end(); it != last && !valid(); ++it) {}
return *this;
}
runtime_view_iterator operator++(int) {
const runtime_view_iterator orig = *this;
return ++(*this), orig;
}
runtime_view_iterator &operator--() {
--it;
for(const auto first = (*pools)[0]->begin(); it != first && !valid(); --it) {}
return *this;
}
runtime_view_iterator operator--(int) {
const runtime_view_iterator orig = *this;
return operator--(), orig;
}
[[nodiscard]] pointer operator->() const noexcept {
return it.operator->();
}
[[nodiscard]] reference operator*() const noexcept {
return *operator->();
}
[[nodiscard]] constexpr bool operator==(const runtime_view_iterator &other) const noexcept {
return it == other.it;
}
private:
const stl::vector<Set *> *pools;
const stl::vector<Set *> *filter;
iterator_type it;
bool tombstone_check;
};
} // namespace internal
/*! @endcond */
/**
* @brief Generic runtime view.
*
* Runtime views iterate over those entities that are at least in the given
* storage. During initialization, a runtime view looks at the number of
* entities available for each element and uses the smallest set in order to get
* a performance boost when iterating.
*
* @b Important
*
* Iterators aren't invalidated if:
*
* * New elements are added to the storage.
* * The entity currently pointed is modified (for example, elements are added
* or removed from it).
* * The entity currently pointed is destroyed.
*
* In all other cases, modifying the storage iterated by the view in any way
* invalidates all the iterators.
*
* @tparam Type Common base type.
* @tparam Allocator Type of allocator used to manage memory and elements.
*/
template<typename Type, typename Allocator>
class basic_runtime_view {
using alloc_traits = stl::allocator_traits<Allocator>;
static_assert(stl::is_same_v<typename alloc_traits::value_type, Type *>, "Invalid value type");
using container_type = stl::vector<Type *, Allocator>;
[[nodiscard]] auto offset() const noexcept {
ENTT_ASSERT(!pools.empty(), "Invalid view");
const auto &leading = *pools.front();
return (leading.policy() == deletion_policy::swap_only) ? leading.free_list() : leading.size();
}
public:
/*! @brief Allocator type. */
using allocator_type = Allocator;
/*! @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 Common type among all storage types. */
using common_type = Type;
/*! @brief Bidirectional iterator type. */
using iterator = internal::runtime_view_iterator<common_type>;
/*! @brief Default constructor to use to create empty, invalid views. */
basic_runtime_view() noexcept
: basic_runtime_view{allocator_type{}} {}
/**
* @brief Constructs an empty, invalid view with a given allocator.
* @param allocator The allocator to use.
*/
explicit basic_runtime_view(const allocator_type &allocator)
: pools{allocator},
filter{allocator} {}
/*! @brief Default copy constructor. */
basic_runtime_view(const basic_runtime_view &) = default;
/**
* @brief Allocator-extended copy constructor.
* @param other The instance to copy from.
* @param allocator The allocator to use.
*/
basic_runtime_view(const basic_runtime_view &other, const allocator_type &allocator)
: pools{other.pools, allocator},
filter{other.filter, allocator} {}
/*! @brief Default move constructor. */
basic_runtime_view(basic_runtime_view &&) noexcept = default;
/**
* @brief Allocator-extended move constructor.
* @param other The instance to move from.
* @param allocator The allocator to use.
*/
basic_runtime_view(basic_runtime_view &&other, const allocator_type &allocator)
: pools{stl::move(other.pools), allocator},
filter{stl::move(other.filter), allocator} {}
/*! @brief Default destructor. */
~basic_runtime_view() = default;
/**
* @brief Default copy assignment operator.
* @return This runtime view.
*/
basic_runtime_view &operator=(const basic_runtime_view &) = default;
/**
* @brief Default move assignment operator.
* @return This runtime view.
*/
basic_runtime_view &operator=(basic_runtime_view &&) noexcept = default;
/**
* @brief Exchanges the contents with those of a given view.
* @param other View to exchange the content with.
*/
void swap(basic_runtime_view &other) noexcept {
using stl::swap;
swap(pools, other.pools);
swap(filter, other.filter);
}
/**
* @brief Returns the associated allocator.
* @return The associated allocator.
*/
[[nodiscard]] constexpr allocator_type get_allocator() const noexcept {
return pools.get_allocator();
}
/*! @brief Clears the view. */
void clear() {
pools.clear();
filter.clear();
}
/**
* @brief Appends an opaque storage object to a runtime view.
* @param base An opaque reference to a storage object.
* @return This runtime view.
*/
basic_runtime_view &iterate(common_type &base) {
if(pools.empty() || !(base.size() < pools.front()->size())) {
pools.push_back(&base);
} else {
pools.push_back(stl::exchange(pools.front(), &base));
}
return *this;
}
/**
* @brief Adds an opaque storage object as a filter of a runtime view.
* @param base An opaque reference to a storage object.
* @return This runtime view.
*/
basic_runtime_view &exclude(common_type &base) {
filter.push_back(&base);
return *this;
}
/**
* @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 {
return pools.empty() ? size_type{} : offset();
}
/**
* @brief Returns an iterator to the first entity that has the given
* elements.
*
* If the view is empty, the returned iterator will be equal to `end()`.
*
* @return An iterator to the first entity that has the given elements.
*/
[[nodiscard]] iterator begin() const {
return pools.empty() ? iterator{} : iterator{pools, pools.front()->end() - static_cast<difference_type>(offset()), filter};
}
/**
* @brief Returns an iterator that is past the last entity that has the
* given elements.
* @return An iterator to the entity following the last entity that has the
* given elements.
*/
[[nodiscard]] iterator end() const {
return pools.empty() ? iterator{} : iterator{pools, pools.front()->end(), filter};
}
/**
* @brief Checks whether a view is initialized or not.
* @return True if the view is initialized, false otherwise.
*/
[[nodiscard]] explicit operator bool() const noexcept {
return !(pools.empty() && filter.empty());
}
/**
* @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 {
return !pools.empty()
&& stl::all_of(pools.cbegin(), pools.cend(), [entt](const auto *curr) { return curr->contains(entt); })
&& stl::none_of(filter.cbegin(), filter.cend(), [entt](const auto *curr) { return curr && curr->contains(entt); })
&& pools.front()->index(entt) < offset();
}
/**
* @brief Iterates entities and applies the given function object to them.
*
* The function object is invoked for each entity. It is provided only with
* the entity itself.<br/>
* The signature of the function should be equivalent to the following:
*
* @code{.cpp}
* void(const entity_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 {
for(const auto entity: *this) {
func(entity);
}
}
private:
container_type pools;
container_type filter;
};
} // namespace entt
#endif

View File

@@ -0,0 +1,509 @@
#ifndef ENTT_ENTITY_SNAPSHOT_HPP
#define ENTT_ENTITY_SNAPSHOT_HPP
#include "../config/config.h"
#include "../container/dense_map.hpp"
#include "../core/type_traits.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"
#include "view.hpp"
namespace entt {
/*! @cond ENTT_INTERNAL */
namespace internal {
template<typename Registry>
void orphans(Registry &registry) {
for(auto &storage = registry.template storage<typename Registry::entity_type>(); auto entt: storage) {
if(registry.orphan(entt)) {
storage.erase(entt);
}
}
}
} // namespace internal
/*! @endcond */
/**
* @brief Utility class to create snapshots from a registry.
*
* A _snapshot_ can be either a dump of the entire registry or a narrower
* selection of elements of interest.<br/>
* This type can be used in both cases if provided with a correctly configured
* output archive.
*
* @tparam Registry Basic registry type.
*/
template<typename Registry>
class basic_snapshot {
static_assert(!stl::is_const_v<Registry>, "Non-const registry type required");
using traits_type = entt_traits<typename Registry::entity_type>;
public:
/*! Basic registry type. */
using registry_type = Registry;
/*! @brief Underlying entity identifier. */
using entity_type = registry_type::entity_type;
/**
* @brief Constructs an instance that is bound to a given registry.
* @param source A valid reference to a registry.
*/
basic_snapshot(const registry_type &source) noexcept
: reg{&source} {}
/*! @brief Default copy constructor, deleted on purpose. */
basic_snapshot(const basic_snapshot &) = delete;
/*! @brief Default move constructor. */
basic_snapshot(basic_snapshot &&) noexcept = default;
/*! @brief Default destructor. */
~basic_snapshot() = default;
/**
* @brief Default copy assignment operator, deleted on purpose.
* @return This snapshot.
*/
basic_snapshot &operator=(const basic_snapshot &) = delete;
/**
* @brief Default move assignment operator.
* @return This snapshot.
*/
basic_snapshot &operator=(basic_snapshot &&) noexcept = default;
/**
* @brief Serializes all elements of a type with associated identifiers.
* @tparam Type Type of elements to serialize.
* @tparam Archive Type of output archive.
* @param archive A valid reference to an output archive.
* @param id Optional name used to map the storage within the registry.
* @return An object of this type to continue creating the snapshot.
*/
template<typename Type, typename Archive>
const basic_snapshot &get(Archive &archive, const id_type id = type_hash<Type>::value()) const {
if(const auto *storage = reg->template storage<Type>(id); storage) {
const typename registry_type::common_type &base = *storage;
archive(static_cast<traits_type::entity_type>(storage->size()));
if constexpr(stl::is_same_v<Type, entity_type>) {
archive(static_cast<traits_type::entity_type>(storage->free_list()));
for(auto first = base.rbegin(), last = base.rend(); first != last; ++first) {
archive(*first);
}
} else if constexpr(registry_type::template storage_for_type<Type>::storage_policy == deletion_policy::in_place) {
for(auto it = base.rbegin(), last = base.rend(); it != last; ++it) {
if(const auto entt = *it; entt == tombstone) {
archive(static_cast<entity_type>(null));
} else {
archive(entt);
stl::apply([&archive](auto &&...args) { (archive(stl::forward<decltype(args)>(args)), ...); }, storage->get_as_tuple(entt));
}
}
} else {
for(auto elem: storage->reach()) {
stl::apply([&archive](auto &&...args) { (archive(stl::forward<decltype(args)>(args)), ...); }, elem);
}
}
} else {
archive(typename traits_type::entity_type{});
}
return *this;
}
/**
* @brief Serializes all elements of a type with associated identifiers for
* the entities in a range.
* @tparam Type Type of elements to serialize.
* @tparam Archive Type of output archive.
* @param archive A valid reference to an output archive.
* @param first An iterator to the first element of the range to serialize.
* @param last An iterator past the last element of the range to serialize.
* @param id Optional name used to map the storage within the registry.
* @return An object of this type to continue creating the snapshot.
*/
template<typename Type, typename Archive>
const basic_snapshot &get(Archive &archive, stl::input_iterator auto first, stl::input_iterator auto last, const id_type id = type_hash<Type>::value()) const {
static_assert(!stl::is_same_v<Type, entity_type>, "Entity types not supported");
if(const auto *storage = reg->template storage<Type>(id); storage && !storage->empty()) {
archive(static_cast<traits_type::entity_type>(stl::distance(first, last)));
for(; first != last; ++first) {
if(const auto entt = *first; storage->contains(entt)) {
archive(entt);
stl::apply([&archive](auto &&...args) { (archive(stl::forward<decltype(args)>(args)), ...); }, storage->get_as_tuple(entt));
} else {
archive(static_cast<entity_type>(null));
}
}
} else {
archive(typename traits_type::entity_type{});
}
return *this;
}
private:
const registry_type *reg;
};
/**
* @brief Utility class to restore a snapshot as a whole.
*
* A snapshot loader requires that the destination registry be empty and loads
* all the data at once while keeping intact the identifiers that the entities
* originally had.<br/>
* An example of use is the implementation of a save/restore utility.
*
* @tparam Registry Basic registry type.
*/
template<typename Registry>
class basic_snapshot_loader {
static_assert(!stl::is_const_v<Registry>, "Non-const registry type required");
using traits_type = entt_traits<typename Registry::entity_type>;
public:
/*! Basic registry type. */
using registry_type = Registry;
/*! @brief Underlying entity identifier. */
using entity_type = registry_type::entity_type;
/**
* @brief Constructs an instance that is bound to a given registry.
* @param source A valid reference to a registry.
*/
basic_snapshot_loader(registry_type &source) noexcept
: reg{&source} {
// restoring a snapshot as a whole requires a clean registry
ENTT_ASSERT(reg->template storage<entity_type>().free_list() == 0u, "Registry must be empty");
}
/*! @brief Default copy constructor, deleted on purpose. */
basic_snapshot_loader(const basic_snapshot_loader &) = delete;
/*! @brief Default move constructor. */
basic_snapshot_loader(basic_snapshot_loader &&) noexcept = default;
/*! @brief Default destructor. */
~basic_snapshot_loader() = default;
/**
* @brief Default copy assignment operator, deleted on purpose.
* @return This loader.
*/
basic_snapshot_loader &operator=(const basic_snapshot_loader &) = delete;
/**
* @brief Default move assignment operator.
* @return This loader.
*/
basic_snapshot_loader &operator=(basic_snapshot_loader &&) noexcept = default;
/**
* @brief Restores all elements of a type with associated identifiers.
* @tparam Type Type of elements to restore.
* @tparam Archive Type of input archive.
* @param archive A valid reference to an input archive.
* @param id Optional name used to map the storage within the registry.
* @return A valid loader to continue restoring data.
*/
template<typename Type, typename Archive>
basic_snapshot_loader &get(Archive &archive, const id_type id = type_hash<Type>::value()) {
auto &storage = reg->template storage<Type>(id);
typename traits_type::entity_type length{};
archive(length);
if constexpr(stl::is_same_v<Type, entity_type>) {
typename traits_type::entity_type count{};
entity_type placeholder{};
storage.reserve(length);
archive(count);
for(entity_type entity = null; length; --length) {
archive(entity);
storage.generate(entity);
placeholder = (entity > placeholder) ? entity : placeholder;
}
storage.start_from(traits_type::next(placeholder));
storage.free_list(count);
} else {
auto &other = reg->template storage<entity_type>();
entity_type entt{null};
while(length--) {
if(archive(entt); entt != null) {
const auto entity = other.contains(entt) ? entt : other.generate(entt);
ENTT_ASSERT(entity == entt, "Entity not available for use");
if constexpr(stl::tuple_size_v<decltype(storage.get_as_tuple({}))> == 0u) {
storage.emplace(entity);
} else {
Type elem{};
archive(elem);
storage.emplace(entity, stl::move(elem));
}
}
}
}
return *this;
}
/**
* @brief Destroys those entities that have no elements.
*
* In case all the entities were serialized but only part of the elements
* was saved, it could happen that some of the entities have no elements
* once restored.<br/>
* This function helps to identify and destroy those entities.
*
* @return A valid loader to continue restoring data.
*/
basic_snapshot_loader &orphans() {
internal::orphans(*reg);
return *this;
}
private:
registry_type *reg;
};
/**
* @brief Utility class for _continuous loading_.
*
* A _continuous loader_ is designed to load data from a source registry to a
* (possibly) non-empty destination. The loader can accommodate in a registry
* more than one snapshot in a sort of _continuous loading_ that updates the
* destination one step at a time.<br/>
* Identifiers that entities originally had are not transferred to the target.
* Instead, the loader maps remote identifiers to local ones while restoring a
* snapshot.<br/>
* An example of use is the implementation of a client-server application with
* the requirement of transferring somehow parts of the representation side to
* side.
*
* @tparam Registry Basic registry type.
*/
template<typename Registry>
class basic_continuous_loader {
static_assert(!stl::is_const_v<Registry>, "Non-const registry type required");
using traits_type = entt_traits<typename Registry::entity_type>;
void restore(Registry::entity_type entt) {
if(const auto entity = to_entity(entt); remloc.contains(entity) && remloc[entity].first == entt) {
if(!reg->valid(remloc[entity].second)) {
remloc[entity].second = reg->create();
}
} else {
remloc.insert_or_assign(entity, stl::make_pair(entt, reg->create()));
}
}
template<typename Container>
auto update(int, Container &container) -> decltype(typename Container::mapped_type{}, void()) {
// map like container
Container other;
for(auto &&pair: container) {
using first_type = stl::remove_const_t<typename stl::decay_t<decltype(pair)>::first_type>;
using second_type = stl::decay_t<decltype(pair)>::second_type;
if constexpr(stl::is_same_v<first_type, entity_type> && stl::is_same_v<second_type, entity_type>) {
other.emplace(map(pair.first), map(pair.second));
} else if constexpr(stl::is_same_v<first_type, entity_type>) {
other.emplace(map(pair.first), stl::move(pair.second));
} else {
static_assert(stl::is_same_v<second_type, entity_type>, "Neither the key nor the value are of entity type");
other.emplace(stl::move(pair.first), map(pair.second));
}
}
using stl::swap;
swap(container, other);
}
template<typename Container>
auto update(char, Container &container) -> decltype(typename Container::value_type{}, void()) {
// vector like container
static_assert(stl::is_same_v<typename Container::value_type, entity_type>, "Invalid value type");
for(auto &&entt: container) {
entt = map(entt);
}
}
template<typename Component, typename Other, typename Member>
void update([[maybe_unused]] Component &instance, [[maybe_unused]] Member Other::*member) {
if constexpr(!stl::is_same_v<Component, Other>) {
return;
} else if constexpr(stl::is_same_v<Member, entity_type>) {
instance.*member = map(instance.*member);
} else {
// maybe a container? let's try...
update(0, instance.*member);
}
}
public:
/*! Basic registry type. */
using registry_type = Registry;
/*! @brief Underlying entity identifier. */
using entity_type = registry_type::entity_type;
/**
* @brief Constructs an instance that is bound to a given registry.
* @param source A valid reference to a registry.
*/
basic_continuous_loader(registry_type &source) noexcept
: remloc{source.get_allocator()},
reg{&source} {}
/*! @brief Default copy constructor, deleted on purpose. */
basic_continuous_loader(const basic_continuous_loader &) = delete;
/*! @brief Default move constructor. */
basic_continuous_loader(basic_continuous_loader &&) noexcept = default;
/*! @brief Default destructor. */
~basic_continuous_loader() = default;
/**
* @brief Default copy assignment operator, deleted on purpose.
* @return This loader.
*/
basic_continuous_loader &operator=(const basic_continuous_loader &) = delete;
/**
* @brief Default move assignment operator.
* @return This loader.
*/
basic_continuous_loader &operator=(basic_continuous_loader &&) noexcept = default;
/**
* @brief Restores all elements of a type with associated identifiers.
*
* It creates local counterparts for remote elements as needed.<br/>
* Members are either data members of type entity_type or containers of
* entities. In both cases, a loader visits them and replaces entities with
* their local counterpart.
*
* @tparam Type Type of elements to restore.
* @tparam Archive Type of input archive.
* @param archive A valid reference to an input archive.
* @param id Optional name used to map the storage within the registry.
* @return A valid loader to continue restoring data.
*/
template<typename Type, typename Archive>
basic_continuous_loader &get(Archive &archive, const id_type id = type_hash<Type>::value()) {
auto &storage = reg->template storage<Type>(id);
typename traits_type::entity_type length{};
entity_type entt{null};
archive(length);
if constexpr(stl::is_same_v<Type, entity_type>) {
typename traits_type::entity_type in_use{};
storage.reserve(length);
archive(in_use);
for(stl::size_t pos{}; pos < in_use; ++pos) {
archive(entt);
restore(entt);
}
for(stl::size_t pos = in_use; pos < length; ++pos) {
archive(entt);
if(const auto entity = to_entity(entt); remloc.contains(entity)) {
if(reg->valid(remloc[entity].second)) {
reg->destroy(remloc[entity].second);
}
remloc.erase(entity);
}
}
} else {
for(auto &&ref: remloc) {
storage.remove(ref.second.second);
}
while(length--) {
if(archive(entt); entt != null) {
restore(entt);
if constexpr(stl::tuple_size_v<decltype(storage.get_as_tuple({}))> == 0u) {
storage.emplace(map(entt));
} else {
Type elem{};
archive(elem);
storage.emplace(map(entt), stl::move(elem));
}
}
}
}
return *this;
}
/**
* @brief Destroys those entities that have no elements.
*
* In case all the entities were serialized but only part of the elements
* was saved, it could happen that some of the entities have no elements
* once restored.<br/>
* This function helps to identify and destroy those entities.
*
* @return A non-const reference to this loader.
*/
basic_continuous_loader &orphans() {
internal::orphans(*reg);
return *this;
}
/**
* @brief Tests if a loader knows about a given entity.
* @param entt A valid identifier.
* @return True if `entity` is managed by the loader, false otherwise.
*/
[[nodiscard]] bool contains(entity_type entt) const noexcept {
const auto it = remloc.find(to_entity(entt));
return it != remloc.cend() && it->second.first == entt;
}
/**
* @brief Returns the identifier to which an entity refers.
* @param entt A valid identifier.
* @return The local identifier if any, the null entity otherwise.
*/
[[nodiscard]] entity_type map(entity_type entt) const noexcept {
if(const auto it = remloc.find(to_entity(entt)); it != remloc.cend() && it->second.first == entt) {
return it->second.second;
}
return null;
}
private:
dense_map<typename traits_type::entity_type, stl::pair<entity_type, entity_type>> remloc;
registry_type *reg;
};
} // namespace entt
#endif

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

1142
include/entt/entity/view.hpp Normal file

File diff suppressed because it is too large Load Diff

93
include/entt/entt.hpp Normal file
View File

@@ -0,0 +1,93 @@
/*! @brief `EnTT` default namespace. */
namespace entt {}
/*! @brief Custom `EnTT` namespace for the standard template library. */
namespace entt::stl {}
// IWYU pragma: begin_exports
#include "config/config.h"
#include "config/macro.h"
#include "config/version.h"
#include "container/dense_map.hpp"
#include "container/dense_set.hpp"
#include "container/table.hpp"
#include "core/algorithm.hpp"
#include "core/any.hpp"
#include "core/bit.hpp"
#include "core/compressed_pair.hpp"
#include "core/concepts.hpp"
#include "core/enum.hpp"
#include "core/family.hpp"
#include "core/hashed_string.hpp"
#include "core/ident.hpp"
#include "core/iterator.hpp"
#include "core/memory.hpp"
#include "core/monostate.hpp"
#include "core/ranges.hpp"
#include "core/tuple.hpp"
#include "core/type_info.hpp"
#include "core/type_traits.hpp"
#include "core/utility.hpp"
#include "entity/component.hpp"
#include "entity/entity.hpp"
#include "entity/group.hpp"
#include "entity/handle.hpp"
#include "entity/helper.hpp"
#include "entity/mixin.hpp"
#include "entity/organizer.hpp"
#include "entity/ranges.hpp"
#include "entity/registry.hpp"
#include "entity/runtime_view.hpp"
#include "entity/snapshot.hpp"
#include "entity/sparse_set.hpp"
#include "entity/storage.hpp"
#include "entity/view.hpp"
#include "graph/adjacency_matrix.hpp"
#include "graph/dot.hpp"
#include "graph/flow.hpp"
#include "locator/locator.hpp"
#include "meta/adl_pointer.hpp"
#include "meta/container.hpp"
#include "meta/context.hpp"
#include "meta/factory.hpp"
#include "meta/meta.hpp"
#include "meta/node.hpp"
#include "meta/pointer.hpp"
#include "meta/policy.hpp"
#include "meta/range.hpp"
#include "meta/resolve.hpp"
#include "meta/template.hpp"
#include "meta/type_traits.hpp"
#include "meta/utility.hpp"
#include "poly/poly.hpp"
#include "process/process.hpp"
#include "process/scheduler.hpp"
#include "resource/cache.hpp"
#include "resource/loader.hpp"
#include "resource/resource.hpp"
#include "signal/delegate.hpp"
#include "signal/dispatcher.hpp"
#include "signal/emitter.hpp"
#include "signal/sigh.hpp"
#include "stl/algorithm.hpp"
#include "stl/array.hpp"
#include "stl/atomic.hpp"
#include "stl/bit.hpp"
#include "stl/cmath.hpp"
#include "stl/concepts.hpp"
#include "stl/cstddef.hpp"
#include "stl/cstdint.hpp"
#include "stl/functional.hpp"
#include "stl/ios.hpp"
#include "stl/iterator.hpp"
#include "stl/limits.hpp"
#include "stl/memory.hpp"
#include "stl/ostream.hpp"
#include "stl/sstream.hpp"
#include "stl/string.hpp"
#include "stl/string_view.hpp"
#include "stl/tuple.hpp"
#include "stl/type_traits.hpp"
#include "stl/utility.hpp"
#include "stl/vector.hpp"
// IWYU pragma: end_exports

Some files were not shown because too many files have changed in this diff Show More