diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..a6457c9 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,251 @@ +# Code Modification Guidelines + +## 1. Git Branch Protection + +- Never modify code directly on the `main` branch. +- Before editing, check the current branch: + +```bash +git branch --show-current +```` + +* If the result is `main`, create or switch to a development branch first: + +```bash +git checkout -b feature/xxx +``` + +* Do not bypass branch protection by: + + * Committing directly to `main`. + * Running code generation, formatting, or large refactors on `main`. + * Rewriting or damaging `main` history. + +--- + +## 2. Code Comments + +* All new comments must be written in English. +* Comments must be: + + * Short and clear. + * Explain the purpose, not repeat the code. + * Avoid unnecessary details. + +Recommended: + +```cpp +// AI-generated: Prevent stale handle access. +``` + +Avoid: + +```cpp +// AI-generated: This checks if the handle is invalid because... +``` + +* Important AI-added or modified logic should include: + +```cpp +// AI-generated +``` + +* Do not add comments for trivial changes such as: + + * One or two line logic fixes. + * Spelling corrections. + * Simple variable renames. + +### 2.1 Do not write excessive or granular comments + +AI must avoid sprinkling small explanatory comments throughout code. +Specifically: + +* **No line-by-line narration.** Do not add a comment above (or beside) + every field, signal, slot, branch, or block just to restate what it is. + If the name is self-explanatory, no comment is needed. +* **No multi-part explanations on a single marker.** A line like + `// AI-generated: abort the in-flight download; finished handler clears state.` + is too much. Either drop it, or shorten to a single short clause + (e.g. `// AI-generated: abort in-flight download.`). +* **Prefer zero or one comment per file/section**, not many. A short + header comment explaining the file's purpose is acceptable; dozens of + inline labels are not. +* **Do not comment obvious QML/C++ bindings, layout splits, or default + values** (e.g. `// AI-generated: index 0 -> zh_CN, index 1 -> en.`). + The code already says that. +* **When in doubt, omit the comment.** A missing comment is fine; a + redundant one is noise that future maintainers must clean up. + +Rule of thumb: if removing the comment would not lose useful +information, remove it. + +--- + +## 3. Project Style + +Follow the existing project style strictly: + +* Naming conventions. +* File organization. +* Formatting rules. +* Include order. +* Existing architecture. + +Do not: + +* Introduce unrelated coding styles. +* Add unnecessary design patterns. +* Create duplicate systems. +* Add abstractions without need. +* Change module responsibilities. + +--- + +## 4. Modification Scope + +Only modify the minimum code required. + +Do not: + +* Format unrelated files. +* Remove existing comments. +* Rename large numbers of symbols. +* Change public APIs. +* Perform large refactors without confirmation. + +If large changes are required, explain first: + +1. Current problem. +2. Required changes. +3. Affected modules. +4. Expected impact. + +--- + +## 5. Build and Test + +After modifications: + +* Verify compilation. +* Check for new warnings. +* Ensure existing features still work. + +Do not: + +* Commit without verification. +* Ignore compiler errors. +* Hide problems with temporary hacks. + +--- + +## 6. Compatibility + +Consider: + +* Existing callers. +* API/ABI compatibility. +* Serialization formats. +* Network protocols. +* Save data. +* Threading impact. + +Do not: + +* Change network structures without compatibility handling. +* Break old data formats. +* Change public data layouts carelessly. + +--- + +## 7. Multi-threading Safety + +For multi-threaded code, check: + +* Data races. +* Object lifetime. +* Lock contention. +* Atomic correctness. +* Cross-thread resource access. + +Do not: + +* Assume single-threaded execution. +* Modify shared data without protection. +* Add hidden locks that hurt performance. + +--- + +## 8. Memory Safety + +Consider: + +* Ownership. +* Lifetime. +* RAII. +* Memory leaks. +* Dangling references. +* Iterator invalidation. + +Do not: + +* Return references to local variables. +* Store pointers to temporary objects. +* Use uninitialized data. +* Introduce undefined behavior. + +--- + +## 9. Third-party Libraries + +Before adding or changing dependencies: + +Check: + +* Existing project dependencies. +* Build system impact. +* Maintenance cost. + +Do not: + +* Add large libraries for simple features. +* Duplicate existing library functionality. +* Modify third-party source code. + +--- + +## 10. AI Behavior Rules + +AI-generated code must: + +* Reuse existing code first. +* Preserve current architecture. +* Prefer simple solutions. +* Minimize risk and code changes. + +Do not: + +* Guess requirements. +* Add unrequested features. +* Redesign architecture. +* Add unnecessary abstraction layers. + +Choose the implementation with: + +> The smallest change, lowest risk, and best compatibility with existing code. + +--- + +## 11. Pre-commit Checklist + +Before committing: + +* [ ] Not on `main`. +* [ ] Changes match the requested task. +* [ ] New comments are in English. +* [ ] AI changes are marked when needed. +* [ ] No unrelated formatting changes. +* [ ] Build passes. +* [ ] No obvious performance/security/thread issues. +* [ ] No API, protocol, or data format breakage. + diff --git a/CMakeLists.txt b/CMakeLists.txt index 2e765f4..19601e4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -148,6 +148,7 @@ target_link_libraries(${PROJECT_NAME} OpenAL::OpenAL harfbuzz::harfbuzz Opus::Opus + assimp::assimp $<$:ws2_32> ) diff --git a/assets/data/block/air.toml b/assets/data/block/air.toml index d3a07c9..3b833c2 100644 --- a/assets/data/block/air.toml +++ b/assets/data/block/air.toml @@ -8,5 +8,4 @@ is_passable = true is_transitional = false is_transparent = true name = 'air' -name_key = 'block.air' roughness = 1.0 \ No newline at end of file diff --git a/assets/data/block/dirt.toml b/assets/data/block/dirt.toml index 8de0d17..d7f3c5b 100644 --- a/assets/data/block/dirt.toml +++ b/assets/data/block/dirt.toml @@ -8,5 +8,4 @@ is_passable = false is_transitional = true is_transparent = false name = 'dirt' -name_key = 'block.dirt' roughness = 1.0 \ No newline at end of file diff --git a/assets/data/block/grass.toml b/assets/data/block/grass.toml index ad4998c..688a14c 100644 --- a/assets/data/block/grass.toml +++ b/assets/data/block/grass.toml @@ -8,5 +8,4 @@ is_passable = true is_transitional = false is_transparent = true name = 'grass' -name_key = 'block.grass' roughness = 0.90000000000000002 \ No newline at end of file diff --git a/assets/data/block/grass_block.toml b/assets/data/block/grass_block.toml index c998df8..3918475 100644 --- a/assets/data/block/grass_block.toml +++ b/assets/data/block/grass_block.toml @@ -8,5 +8,4 @@ is_passable = false is_transitional = true is_transparent = false name = 'grass_block' -name_key = 'block.grass_block' roughness = 0.90000000000000002 \ No newline at end of file diff --git a/assets/data/block/leaf.toml b/assets/data/block/leaf.toml index 5c93fcf..186c03f 100644 --- a/assets/data/block/leaf.toml +++ b/assets/data/block/leaf.toml @@ -8,5 +8,4 @@ is_passable = false is_transitional = false is_transparent = true name = 'leaf' -name_key = 'block.leaf' roughness = 0.69999999999999996 \ No newline at end of file diff --git a/assets/data/block/log.toml b/assets/data/block/log.toml index 169f9ad..b38b4e9 100644 --- a/assets/data/block/log.toml +++ b/assets/data/block/log.toml @@ -8,5 +8,4 @@ is_passable = false is_transitional = false is_transparent = false name = 'log' -name_key = 'block.log' roughness = 0.69999999999999996 \ No newline at end of file diff --git a/assets/data/block/sand.toml b/assets/data/block/sand.toml index fe411c7..c00bd1f 100644 --- a/assets/data/block/sand.toml +++ b/assets/data/block/sand.toml @@ -8,5 +8,4 @@ is_passable = false is_transitional = true is_transparent = false name = 'sand' -name_key = 'block.sand' roughness = 0.80000000000000004 \ No newline at end of file diff --git a/assets/data/block/snowy_grass_block.toml b/assets/data/block/snowy_grass_block.toml index 220b93f..109d65a 100644 --- a/assets/data/block/snowy_grass_block.toml +++ b/assets/data/block/snowy_grass_block.toml @@ -8,5 +8,4 @@ is_passable = false is_transitional = true is_transparent = false name = 'snowy_grass_block' -name_key = 'block.snowy_grass_block' roughness = 0.90000000000000002 \ No newline at end of file diff --git a/assets/data/block/stone.toml b/assets/data/block/stone.toml index e39d214..c0c7016 100644 --- a/assets/data/block/stone.toml +++ b/assets/data/block/stone.toml @@ -8,5 +8,4 @@ is_passable = false is_transitional = true is_transparent = false name = 'stone' -name_key = 'block.stone' roughness = 0.75 \ No newline at end of file diff --git a/assets/data/block/template.toml b/assets/data/block/template.toml index 34c62ce..30911fa 100644 --- a/assets/data/block/template.toml +++ b/assets/data/block/template.toml @@ -9,4 +9,3 @@ is_discard = false is_blend = false is_transitional = false roughness = 1.0 -name_key = "block.template" diff --git a/assets/data/block/water.toml b/assets/data/block/water.toml index 33bec27..0cd0e4e 100644 --- a/assets/data/block/water.toml +++ b/assets/data/block/water.toml @@ -8,5 +8,4 @@ is_passable = true is_transitional = false is_transparent = true name = 'water' -name_key = 'block.water' roughness = 0.02 \ No newline at end of file diff --git a/assets/item/air.json b/assets/item/air.json new file mode 100644 index 0000000..ab8272d --- /dev/null +++ b/assets/item/air.json @@ -0,0 +1,6 @@ +{ + "id": 0, + "name": "air", + "description": "", + "type": "block" +} \ No newline at end of file diff --git a/assets/item/dirt.json b/assets/item/dirt.json new file mode 100644 index 0000000..70f9e19 --- /dev/null +++ b/assets/item/dirt.json @@ -0,0 +1,7 @@ +{ + "id": 2, + "name": "dirt", + "description": "", + "texture": "texture/item/block/dirt.png", + "type": "block" +} \ No newline at end of file diff --git a/assets/item/grass.json b/assets/item/grass.json new file mode 100644 index 0000000..228d566 --- /dev/null +++ b/assets/item/grass.json @@ -0,0 +1,7 @@ +{ + "id": 9, + "name": "grass", + "description": "", + "texture": "texture/item/block/grass.png", + "type": "block" +} \ No newline at end of file diff --git a/assets/item/grass_block.json b/assets/item/grass_block.json new file mode 100644 index 0000000..e00e186 --- /dev/null +++ b/assets/item/grass_block.json @@ -0,0 +1,7 @@ +{ + "id": 1, + "name": "grass_block", + "description": "", + "texture": "texture/item/block/grass_block.png", + "type": "block" +} \ No newline at end of file diff --git a/assets/item/leaf.json b/assets/item/leaf.json new file mode 100644 index 0000000..6263836 --- /dev/null +++ b/assets/item/leaf.json @@ -0,0 +1,7 @@ +{ + "id": 6, + "name": "leaf", + "description": "", + "texture": "texture/item/block/leaf.png", + "type": "block" +} \ No newline at end of file diff --git a/assets/item/log.json b/assets/item/log.json new file mode 100644 index 0000000..aeceded --- /dev/null +++ b/assets/item/log.json @@ -0,0 +1,7 @@ +{ + "id": 5, + "name": "log", + "description": "", + "texture": "texture/item/block/log.png", + "type": "block" +} \ No newline at end of file diff --git a/assets/item/pig_spawn_egg.json b/assets/item/pig_spawn_egg.json new file mode 100644 index 0000000..6df5ead --- /dev/null +++ b/assets/item/pig_spawn_egg.json @@ -0,0 +1,8 @@ +{ + "id": 10, + "name": "pig_spawn_egg", + "description": "", + "texture": "texture/item/spawn_egg/pig_spawn_egg.png", + "type": "spawn_egg", + "creature": "cubed:pig" +} \ No newline at end of file diff --git a/assets/item/sand.json b/assets/item/sand.json new file mode 100644 index 0000000..5d022c0 --- /dev/null +++ b/assets/item/sand.json @@ -0,0 +1,7 @@ +{ + "id": 4, + "name": "sand", + "description": "", + "texture": "texture/item/block/sand.png", + "type": "block" +} \ No newline at end of file diff --git a/assets/item/snowy_grass_block.json b/assets/item/snowy_grass_block.json new file mode 100644 index 0000000..f1eb8f1 --- /dev/null +++ b/assets/item/snowy_grass_block.json @@ -0,0 +1,7 @@ +{ + "id": 8, + "name": "snowy_grass_block", + "description": "", + "texture": "texture/item/block/snowy_grass_block.png", + "type": "block" +} \ No newline at end of file diff --git a/assets/item/stone.json b/assets/item/stone.json new file mode 100644 index 0000000..8e177b8 --- /dev/null +++ b/assets/item/stone.json @@ -0,0 +1,7 @@ +{ + "id": 3, + "name": "stone", + "description": "", + "texture": "texture/item/block/stone.png", + "type": "block" +} \ No newline at end of file diff --git a/assets/item/water.json b/assets/item/water.json new file mode 100644 index 0000000..0f6e7ee --- /dev/null +++ b/assets/item/water.json @@ -0,0 +1,7 @@ +{ + "id": 7, + "name": "water", + "description": "", + "texture": "texture/item/block/water.png", + "type": "block" +} \ No newline at end of file diff --git a/assets/lang/en_US.json b/assets/lang/en_US.json index 4444ab9..bd45502 100644 --- a/assets/lang/en_US.json +++ b/assets/lang/en_US.json @@ -36,14 +36,15 @@ "joingame.server_ip": "Server Ip", "joingame.join_world": "Join World", "error.disable_voice": "Server Disable Voice Chat", - "block.air": "air", - "block.dirt": "dirt", - "block.grass_block": "grass block", - "block.grass": "grass", - "block.leaf": "leaf", - "block.log": "log", - "block.sand": "sand", - "block.snowy_grass_block": "snowy grass block", - "block.stone": "stone", - "block.water": "water" + "item.air.name": "air", + "item.dirt.name": "dirt", + "item.grass_block.name": "grass block", + "item.grass.name": "grass", + "item.leaf.name": "leaf", + "item.log.name": "log", + "item.sand.name": "sand", + "item.snowy_grass_block.name": "snowy grass block", + "item.stone.name": "stone", + "item.water.name": "water", + "item.pig_spawn_egg.name": "pig spawn egg" } \ No newline at end of file diff --git a/assets/lang/zh_CN.json b/assets/lang/zh_CN.json index 1d83700..362fb53 100644 --- a/assets/lang/zh_CN.json +++ b/assets/lang/zh_CN.json @@ -36,14 +36,15 @@ "joingame.server_ip": "服务器IP", "joingame.join_world": "加入世界", "error.disable_voice": "服务器已禁用语音聊天", - "block.air": "空气", - "block.dirt": "泥土", - "block.grass_block": "草方块", - "block.grass": "草", - "block.leaf": "树叶", - "block.log": "原木", - "block.sand": "沙子", - "block.snowy_grass_block": "覆雪草方块", - "block.stone": "石头", - "block.water": "水" + "item.air.name": "空气", + "item.dirt.name": "泥土", + "item.grass_block.name": "草方块", + "item.grass.name": "草", + "item.leaf.name": "树叶", + "item.log.name": "原木", + "item.sand.name": "沙子", + "item.snowy_grass_block.name": "覆雪草方块", + "item.stone.name": "石头", + "item.water.name": "水", + "item.pig_spawn_egg.name": "猪猪刷怪蛋" } \ No newline at end of file diff --git a/assets/model/creature/pig/animation.json b/assets/model/creature/pig/animation.json new file mode 100644 index 0000000..53859c2 --- /dev/null +++ b/assets/model/creature/pig/animation.json @@ -0,0 +1,33 @@ +{ + "walk": { + "speed": 6.0, + "amplitude": 25.0 + }, + "run": { + "speed": 12.0, + "amplitude": 40.0 + }, + "body_bob": 0.03, + "head": { + "node": "Head", + "amplitude": 4.0 + }, + "legs": [ + { + "node": "Foot_FL", + "phase": 0.0 + }, + { + "node": "Foot_FR", + "phase": 3.14159 + }, + { + "node": "Foot_HL", + "phase": 3.14159 + }, + { + "node": "Foot_HR", + "phase": 0.0 + } + ] +} \ No newline at end of file diff --git a/assets/model/creature/pig/collision.json b/assets/model/creature/pig/collision.json new file mode 100644 index 0000000..69607ea --- /dev/null +++ b/assets/model/creature/pig/collision.json @@ -0,0 +1,16 @@ +{ + "boxes": [ + { + "center": [ + 0, + 0.78, + 0 + ], + "half": [ + 0.53, + 0.78, + 0.406 + ] + } + ] +} \ No newline at end of file diff --git a/assets/model/creature/pig/pig.glb b/assets/model/creature/pig/pig.glb new file mode 100644 index 0000000..b94f8d9 Binary files /dev/null and b/assets/model/creature/pig/pig.glb differ diff --git a/assets/model/creature/player/collision.json b/assets/model/creature/player/collision.json new file mode 100644 index 0000000..2db6de1 --- /dev/null +++ b/assets/model/creature/player/collision.json @@ -0,0 +1,16 @@ +{ + "boxes": [ + { + "center": [ + 0, + 0.9, + 0 + ], + "half": [ + 0.3, + 0.9, + 0.3 + ] + } + ] +} \ No newline at end of file diff --git a/assets/model/creature/player/player.glb b/assets/model/creature/player/player.glb new file mode 100644 index 0000000..b94f8d9 Binary files /dev/null and b/assets/model/creature/player/player.glb differ diff --git a/assets/shaders/depth_player_fragment_shader.glsl b/assets/shaders/depth_model_frag.glsl similarity index 100% rename from assets/shaders/depth_player_fragment_shader.glsl rename to assets/shaders/depth_model_frag.glsl diff --git a/assets/shaders/depth_model_instance_vert.glsl b/assets/shaders/depth_model_instance_vert.glsl new file mode 100644 index 0000000..dd0ecab --- /dev/null +++ b/assets/shaders/depth_model_instance_vert.glsl @@ -0,0 +1,13 @@ +#version 460 + +layout (location = 0) in vec3 pos; +layout (location = 1) in vec2 texCoord; +layout (location = 3) in mat4 modelMatrix; +uniform mat4 lightSpaceMatrix; + +out vec2 tc; + +void main() { + tc = texCoord; + gl_Position = lightSpaceMatrix * modelMatrix * vec4(pos, 1.0); +} \ No newline at end of file diff --git a/assets/shaders/depth_player_shader.glsl b/assets/shaders/depth_model_vert copy.glsl similarity index 91% rename from assets/shaders/depth_player_shader.glsl rename to assets/shaders/depth_model_vert copy.glsl index 2de921a..93f577b 100644 --- a/assets/shaders/depth_player_shader.glsl +++ b/assets/shaders/depth_model_vert copy.glsl @@ -7,7 +7,6 @@ uniform mat4 lightSpaceMatrix; uniform mat4 modelMatrix; out vec2 tc; -flat out int tex_layer; void main() { tc = texCoord; diff --git a/assets/shaders/depth_model_vert.glsl b/assets/shaders/depth_model_vert.glsl new file mode 100644 index 0000000..93f577b --- /dev/null +++ b/assets/shaders/depth_model_vert.glsl @@ -0,0 +1,14 @@ +#version 460 + +layout (location = 0) in vec3 pos; +layout (location = 1) in vec2 texCoord; + +uniform mat4 lightSpaceMatrix; +uniform mat4 modelMatrix; + +out vec2 tc; + +void main() { + tc = texCoord; + gl_Position = lightSpaceMatrix * modelMatrix * vec4(pos, 1.0); +} \ No newline at end of file diff --git a/assets/shaders/player_f_shader.glsl b/assets/shaders/model_frag.glsl similarity index 100% rename from assets/shaders/player_f_shader.glsl rename to assets/shaders/model_frag.glsl diff --git a/assets/shaders/model_instance_vert.glsl b/assets/shaders/model_instance_vert.glsl new file mode 100644 index 0000000..cded335 --- /dev/null +++ b/assets/shaders/model_instance_vert.glsl @@ -0,0 +1,26 @@ +#version 460 + +layout (location = 0) in vec3 pos; +layout (location = 1) in vec2 texCoord; +layout (location = 2) in vec3 aNormal; +layout (location = 3) in mat4 modelMatrix; +uniform mat4 view_matrix; +uniform mat4 proj_matrix; +uniform mat4 lightSpaceMatrix; +out vec4 FragPosLightSpace; +out vec3 normal; +out vec2 tc; +out vec3 vert_pos; + + +void main() { + vec4 worldPos = modelMatrix * vec4(pos, 1.0); + FragPosLightSpace = lightSpaceMatrix * worldPos; + mat4 mv_matrix= view_matrix * modelMatrix; + mat4 norm_matrix = transpose(inverse(mv_matrix)); + vec4 viewPos = mv_matrix * vec4(pos, 1.0); + tc = texCoord; + vert_pos = pos; + normal = normalize(mat3(norm_matrix) * aNormal); + gl_Position = proj_matrix * viewPos; +} \ No newline at end of file diff --git a/assets/shaders/player_v_shader.glsl b/assets/shaders/model_vert.glsl similarity index 94% rename from assets/shaders/player_v_shader.glsl rename to assets/shaders/model_vert.glsl index d42b89a..961e8de 100644 --- a/assets/shaders/player_v_shader.glsl +++ b/assets/shaders/model_vert.glsl @@ -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; diff --git a/assets/sound/creature/pig/call.mp3 b/assets/sound/creature/pig/call.mp3 new file mode 100644 index 0000000..1075145 Binary files /dev/null and b/assets/sound/creature/pig/call.mp3 differ diff --git a/assets/texture/item/spawn_egg/pig_spawn_egg.png b/assets/texture/item/spawn_egg/pig_spawn_egg.png new file mode 100644 index 0000000..2688620 Binary files /dev/null and b/assets/texture/item/spawn_egg/pig_spawn_egg.png differ diff --git a/cmake/Dependencies.cmake b/cmake/Dependencies.cmake index 59005f2..3475177 100644 --- a/cmake/Dependencies.cmake +++ b/cmake/Dependencies.cmake @@ -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) diff --git a/include/Cubed/AABB.hpp b/include/Cubed/AABB.hpp deleted file mode 100644 index ec6ad52..0000000 --- a/include/Cubed/AABB.hpp +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once -#include - -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 \ No newline at end of file diff --git a/include/Cubed/app.hpp b/include/Cubed/app.hpp index 4d9ee45..9fd6c7c 100644 --- a/include/Cubed/app.hpp +++ b/include/Cubed/app.hpp @@ -49,7 +49,6 @@ private: Window m_window; TextureManager m_texture_manager; - AudioEngine m_audio; Renderer m_renderer; diff --git a/include/Cubed/argument.hpp b/include/Cubed/argument.hpp index 4f4ac31..79b29c2 100644 --- a/include/Cubed/argument.hpp +++ b/include/Cubed/argument.hpp @@ -13,5 +13,6 @@ struct Argument { std::optional log_level; std::optional enable_filelog; std::optional enable_consolelog; + std::optional direct_enter; }; } // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/camera.hpp b/include/Cubed/camera.hpp index ff40f72..5fe9769 100644 --- a/include/Cubed/camera.hpp +++ b/include/Cubed/camera.hpp @@ -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 diff --git a/include/Cubed/dev_panel.hpp b/include/Cubed/dev_panel.hpp index 6b4303c..28a6935 100644 --- a/include/Cubed/dev_panel.hpp +++ b/include/Cubed/dev_panel.hpp @@ -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; diff --git a/include/Cubed/gameplay/block.hpp b/include/Cubed/gameplay/block.hpp index bc4499d..8a8056f 100644 --- a/include/Cubed/gameplay/block.hpp +++ b/include/Cubed/gameplay/block.hpp @@ -42,7 +42,6 @@ struct LookBlock { struct BlockData { std::string name; - std::string name_key; BlockType id = 0; bool is_liquid = false; @@ -56,42 +55,14 @@ struct BlockData { bool is_blend = false; bool is_transitional = false; float roughness = 1.0f; - BlockData(std::string_view b_name, std::string_view name_k, BlockType b_id, - bool liquid, bool passable, bool cross_plane, bool transparent, - bool gas, bool discard, bool blend, bool transitional, float r) - : name(b_name), name_key(name_k), id(b_id), is_liquid(liquid), - is_gas(gas), is_passable(passable), is_cross_plane(cross_plane), + BlockData(std::string_view b_name, BlockType b_id, bool liquid, + bool passable, bool cross_plane, bool transparent, bool gas, + bool discard, bool blend, bool transitional, float r) + : name(b_name), id(b_id), is_liquid(liquid), is_gas(gas), + is_passable(passable), is_cross_plane(cross_plane), is_transparent(transparent), is_discard(discard), is_blend(blend), is_transitional(transitional), roughness(r) {} -}; - -class BlockManager { - -public: - static const std::vector& datas(); - static void init(); - static unsigned sums(); - static unsigned cross_plane_sum(); - static const std::string& name_form_id(BlockType id); - static std::string local_name(BlockType id); - static bool is_gas(BlockType id); - static bool is_liquid(BlockType id); - - static bool is_cross_plane(BlockType id); - static bool is_transparent(BlockType id); - static bool is_passable(BlockType id); - - static bool is_discard(BlockType id); - static bool is_blend(BlockType id); - static bool is_transitional(BlockType id); - static float roughness(BlockType id); - static BlockType cross_plane_index(BlockType id); - -private: - static void set_up_cross_plane_map(); - static inline std::vector m_datas; - static inline bool is_init = false; - static inline std::unordered_map m_cross_plane_map; + BlockData() { name = ""; } }; } // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/gameplay/block_manager.hpp b/include/Cubed/gameplay/block_manager.hpp new file mode 100644 index 0000000..4fb8e28 --- /dev/null +++ b/include/Cubed/gameplay/block_manager.hpp @@ -0,0 +1,45 @@ +#pragma once + +#include "Cubed/gameplay/block.hpp" + +#include +namespace Cubed { +class BlockManager { + +public: + static void init(); + static unsigned sums(); + static unsigned cross_plane_sum(); + static const std::string& name_form_id(BlockType id); + static bool is_gas(BlockType id); + static bool is_liquid(BlockType id); + + static bool is_cross_plane(BlockType id); + static bool is_transparent(BlockType id); + static bool is_passable(BlockType id); + + static bool is_discard(BlockType id); + static bool is_blend(BlockType id); + static bool is_transitional(BlockType id); + static float roughness(BlockType id); + static BlockType cross_plane_index(BlockType id); + + static BlockType id_from_name(const std::string& name); + +private: + using BlockMap = tbb::concurrent_hash_map; + using acc = BlockMap::accessor; + using cacc = BlockMap::const_accessor; + using IDMap = tbb::concurrent_hash_map; + using CrossPlaneMap = tbb::concurrent_hash_map; + + static inline const BlockData EMPTY; + + static inline BlockMap m_datas; + static inline IDMap m_id_map; + static inline bool is_init = false; + static inline CrossPlaneMap m_cross_plane_map; + static void set_up_cross_plane_map( + const std::vector>& types); +}; +} // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/gameplay/chunk.hpp b/include/Cubed/gameplay/chunk.hpp new file mode 100644 index 0000000..f2ef335 --- /dev/null +++ b/include/Cubed/gameplay/chunk.hpp @@ -0,0 +1,27 @@ +#pragma once +#include "Cubed/gameplay/chunk_pos.hpp" + +#include +#include +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 world_to_block(int world_x, int world_y, + int world_z, int chunk_x, + int chunk_z); + static std::tuple world_to_block(const glm::ivec3& block_pos, + ChunkPos chunk_pos); + static std::tuple block_to_world(int x, int y, int z, + int chunk_x, int chunk_z); + static std::tuple block_to_world(const glm::ivec3& block_pos, + ChunkPos chunk_pos); +}; +} // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/gameplay/chunk_generator.hpp b/include/Cubed/gameplay/chunk_generator.hpp index 27faca9..a61f4f6 100644 --- a/include/Cubed/gameplay/chunk_generator.hpp +++ b/include/Cubed/gameplay/chunk_generator.hpp @@ -50,6 +50,8 @@ public: void generate_cave(); void generate_river(); + void spawn_creature(); + private: static inline std::atomic is_init{false}; static inline unsigned m_generator_seed{0}; diff --git a/include/Cubed/gameplay/client_chunk.hpp b/include/Cubed/gameplay/client_chunk.hpp index a4fe258..52a2952 100644 --- a/include/Cubed/gameplay/client_chunk.hpp +++ b/include/Cubed/gameplay/client_chunk.hpp @@ -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 world_to_block(int world_x, int world_y, - int world_z, int chunk_x, - int chunk_z); - static std::tuple world_to_block(const glm::ivec3& block_pos, - ChunkPos chunk_pos); - static std::tuple block_to_world(int x, int y, int z, - int chunk_x, int chunk_z); - static std::tuple block_to_world(const glm::ivec3& block_pos, - ChunkPos chunk_pos); BiomeType get_biome() const; ChunkPos get_chunk_pos() const; const std::vector& get_chunk_blocks() const; diff --git a/include/Cubed/gameplay/client_entity_manager.hpp b/include/Cubed/gameplay/client_entity_manager.hpp new file mode 100644 index 0000000..1230edf --- /dev/null +++ b/include/Cubed/gameplay/client_entity_manager.hpp @@ -0,0 +1,82 @@ +#pragma once +#include "Cubed/gameplay/ecs/entity.hpp" +#include "Cubed/gameplay/gait.hpp" +#include "Cubed/tools/cubed_random.hpp" +#include "glm/ext/vector_float3.hpp" +#include "world/entity.pb.h" + +#include +#include +#include +namespace Cubed { +class ClientWorld; +class ClientEntityManager { +public: + enum class Command { CREATE, DESTORY, UPDATE }; + + ClientEntityManager(ClientWorld& world); + void update(float dt); + void init(); + + void receive_entity_create(S2CEntityCreate& msg); + void receive_entity_destory(EntityID id); + void receive_entity_update(const S2CEntityUpdate& msg); + void receive_entity_update(S2CEntityUpdateBatch& msg); + void destory(EntityID id); + void create(std::string_view name, const glm::vec3& pos); + + const entt::registry& get_registry() const; + + void player_sound(float dt); + +private: + struct EntityCreateElement { + EntityID id; + std::string name; + glm::vec3 pos; + }; + + struct UpdateInfo { + EntityID id; + glm::vec3 pos; + glm::vec3 direction; + Gait gait; + }; + + using EntityMap = tbb::concurrent_hash_map; + using acc = EntityMap::accessor; + using cacc = EntityMap::const_accessor; + using CreateFunc = std::function; + using TaskElement = std::variant; + using TaskPair = std::pair; + + ClientWorld& m_world; + entt::registry m_registry; + EntityMap m_entities; + std::unordered_map m_factories; + tbb::concurrent_queue m_tasks; + Random m_random; + void handle_task(float dt); + 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, float dt); + template + 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>( + entity, std::forward(args))), + ...); + m_entities.emplace(id, entity); + return; + } +}; +} // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/gameplay/client_player.hpp b/include/Cubed/gameplay/client_player.hpp index 0ab5c12..267402a 100644 --- a/include/Cubed/gameplay/client_player.hpp +++ b/include/Cubed/gameplay/client_player.hpp @@ -1,167 +1,36 @@ #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 -#include -#include -#include +#include "Cubed/gameplay/ecs/animation.hpp" +#include "Cubed/gameplay/ecs/identity.hpp" +#include "Cubed/gameplay/ecs/transform.hpp" + +#include 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; - 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& 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 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 m_yaw = 0.0f; - std::atomic m_pitch = 0.0f; - std::array 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 m_gait = Gait::STOP; - MoveState m_move_state{}; - MouseState m_mouse_state{}; - GameMode m_game_mode = CREATIVE; - std::optional 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 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 ClientPlayerSnapshot { + double time_ms = 0.0f; + glm::vec3 pos{0.0f}; + float yaw = 0.0f; + float pitch = 0.0f; }; -} // namespace Cubed +struct ClientPlayerState { + std::deque value; +}; +struct ClientPlayer { + Position pos{}; + EntityInfo entity{}; + WalkPose walk{}; + Orientation angle{}; + Position render_pos{}; + Orientation render_angle{}; + ClientPlayerState history{}; +}; + +struct PlayerRenderData { + EntityInfo info{}; + Position render_pos{}; + Orientation angle{}; + Gait gait{}; +}; + +} // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/gameplay/client_player_manager.hpp b/include/Cubed/gameplay/client_player_manager.hpp new file mode 100644 index 0000000..a5888b8 --- /dev/null +++ b/include/Cubed/gameplay/client_player_manager.hpp @@ -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 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::Handle; + SparseVector m_players; + std::vector m_render_data; + std::unordered_map m_players_handle; + LocalPlayer m_local; + + void update_players_data(float dt); +}; +} // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/gameplay/client_world.hpp b/include/Cubed/gameplay/client_world.hpp index 1c50df6..3040b22 100644 --- a/include/Cubed/gameplay/client_world.hpp +++ b/include/Cubed/gameplay/client_world.hpp @@ -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 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 client); - void update(float delta_time); + std::shared_ptr client, RunMode mode); + void update(float dt); bool handle_event(const Event& e); const std::optional& 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& planes(); const std::vector& render_snapshots() const; - const std::vector& render_player_data() const; - std::vector& render_player_data(); glm::vec3 sunlight_dir() const; bool sphere_collide_world(glm::vec3 center, float radius) const; @@ -99,23 +79,29 @@ 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 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; + + bool is_render(const glm::vec3& pos) const; template - void register_ticktimer(std::string_view id, TickType threshold, Fn&& f) { - m_ticktimers.emplace( - std::piecewise_construct, std::forward_as_tuple(std::string(id)), - std::forward_as_tuple(threshold, std::forward(f))); + 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(f))); } private: @@ -136,7 +122,6 @@ private: ChunkPos::TBBHash>; using ChunkPosSet = absl::flat_hash_set; using ChunkPosVector = std::vector; - using OtherPlayerHashMap = std::unordered_map; using chunk_acc = ChunkHashMap::accessor; using chunk_cacc = ChunkHashMap::const_accessor; @@ -147,16 +132,14 @@ private: static constexpr int WORLD_EXIT_TIMEOUT = 200; static constexpr int MAX_UPLOAD_CHUNK_SUM = 16; - ClientPlayer m_player; - OtherPlayerHashMap m_player_info; + std::atomic m_runmode = RunMode::HYBRID; + ClientEntityManager m_entity_manager; + ClientPlayerManager m_player_manager; ChunkHashMap m_chunks; AudioEngine& m_audio; Config& m_config; WorldScene& m_world_scene; std::vector m_planes; - std::jthread m_client_thread; - - mutable std::shared_mutex m_player_info_mutex; tbb::concurrent_queue> m_pending_upload_queue; tbb::concurrent_queue m_dirty_chunk_queue; @@ -166,9 +149,7 @@ private: std::deque m_dirty_queue; std::vector m_render_snapshots; - std::vector m_render_player_data; - tbb::concurrent_unordered_map m_ticktimers; std::unordered_map m_timers; std::atomic m_exit_direct{false}; std::atomic m_game_running{false}; @@ -176,6 +157,7 @@ private: std::atomic m_rendering_distance{24}; std::atomic m_game_ticks{0}; std::atomic m_day_tick{6000}; + std::atomic m_per_tick_time = DEFAULT_PER_TICK_TIME; std::atomic m_requesting_chunk{false}; std::atomic m_is_rebuilding{false}; std::atomic m_chunk_task_id{0}; @@ -187,10 +169,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); diff --git a/include/Cubed/gameplay/creatures/pig.hpp b/include/Cubed/gameplay/creatures/pig.hpp new file mode 100644 index 0000000..fd0f8ce --- /dev/null +++ b/include/Cubed/gameplay/creatures/pig.hpp @@ -0,0 +1,13 @@ +#pragma once + +namespace Cubed { +struct PigTag {}; + +namespace PigDefaults { +constexpr float MAX_SPEED = 0.1f; // tiles/tick → 2 tiles/sec +constexpr float ACCELERATION = 0.01f; // ~10 ticks (0.5s) to reach full speed +constexpr float DECELERATION = 0.02f; // ~5 ticks (0.25s) to stop +constexpr float GRAVITY = 1.0f; // ≈20 tiles/s², close to the player +} // namespace PigDefaults + +} // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/gameplay/creatures/spawn.hpp b/include/Cubed/gameplay/creatures/spawn.hpp new file mode 100644 index 0000000..e591dcf --- /dev/null +++ b/include/Cubed/gameplay/creatures/spawn.hpp @@ -0,0 +1,20 @@ +#pragma once +#include "Cubed/gameplay/biome.hpp" + +#include +#include +#include +namespace Cubed { +struct SpawnConfig { + std::string_view name; // factory key, e.g. "cubed:pig" + std::span biomes; // allowed biomes + float probability = 0.0f; // per chunk spawn probability + unsigned max_spawn_count = 0; // per chunk_max_spawn_sum +}; + +namespace SpawnDefaults { +constexpr std::array PIG_BIOMES{BiomeType::PLAIN, + BiomeType::FOREST}; +constexpr SpawnConfig PIG{"cubed:pig", PIG_BIOMES, 0.02f, 3}; +} // namespace SpawnDefaults +} // namespace Cubed diff --git a/include/Cubed/gameplay/ecs/ai_struct.hpp b/include/Cubed/gameplay/ecs/ai_struct.hpp new file mode 100644 index 0000000..c9715d8 --- /dev/null +++ b/include/Cubed/gameplay/ecs/ai_struct.hpp @@ -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 \ No newline at end of file diff --git a/include/Cubed/gameplay/ecs/animation.hpp b/include/Cubed/gameplay/ecs/animation.hpp new file mode 100644 index 0000000..7a261f3 --- /dev/null +++ b/include/Cubed/gameplay/ecs/animation.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include "Cubed/gameplay/gait.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 \ No newline at end of file diff --git a/include/Cubed/gameplay/ecs/client_entity.hpp b/include/Cubed/gameplay/ecs/client_entity.hpp new file mode 100644 index 0000000..98df8af --- /dev/null +++ b/include/Cubed/gameplay/ecs/client_entity.hpp @@ -0,0 +1,31 @@ +#pragma once +#include "Cubed/gameplay/ecs/animation.hpp" +#include "Cubed/gameplay/ecs/transform.hpp" +#include "Cubed/gameplay/model.hpp" + +#include +namespace Cubed { +struct BaseClientCreature { + + Transform transform; + + WalkPose pose; + + ModelID model; +}; + +struct SoundTime { + float next_call_time; +}; + +struct ClientEntitySnapshot { + double time_ms = 0.0; + glm::vec3 pos{0.0f}; + glm::vec3 dir{0.0f}; +}; + +struct ClientEntityState { + std::deque history; +}; + +} // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/gameplay/ecs/entity.hpp b/include/Cubed/gameplay/ecs/entity.hpp new file mode 100644 index 0000000..67699c4 --- /dev/null +++ b/include/Cubed/gameplay/ecs/entity.hpp @@ -0,0 +1,14 @@ +#pragma once +#include +namespace Cubed { +using EntityID = uint64_t; + +enum class EntityType { CREATURE, ITEM }; + +struct Entity { + EntityID id; + EntityType type; + Entity(EntityID id, EntityType type) : id(id), type(type) {} +}; + +} // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/gameplay/ecs/health.hpp b/include/Cubed/gameplay/ecs/health.hpp new file mode 100644 index 0000000..7d65a76 --- /dev/null +++ b/include/Cubed/gameplay/ecs/health.hpp @@ -0,0 +1,10 @@ +#pragma once + +namespace Cubed { + +struct Health { + float hp = 20; + float max_hp = 20; +}; + +} // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/gameplay/ecs/identity.hpp b/include/Cubed/gameplay/ecs/identity.hpp new file mode 100644 index 0000000..8982d52 --- /dev/null +++ b/include/Cubed/gameplay/ecs/identity.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include + +namespace Cubed { +struct EntityInfo { + std::string name; + std::string uuid; +}; +} // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/gameplay/ecs/movement.hpp b/include/Cubed/gameplay/ecs/movement.hpp new file mode 100644 index 0000000..52d605c --- /dev/null +++ b/include/Cubed/gameplay/ecs/movement.hpp @@ -0,0 +1,34 @@ +#pragma once +#include "Cubed/constants.hpp" + +#include +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 \ No newline at end of file diff --git a/include/Cubed/gameplay/ecs/server_entity.hpp b/include/Cubed/gameplay/ecs/server_entity.hpp new file mode 100644 index 0000000..462e300 --- /dev/null +++ b/include/Cubed/gameplay/ecs/server_entity.hpp @@ -0,0 +1,22 @@ +#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{}; + + Gravity gravity{}; + + Health health{}; + + HitboxID hitbox{}; +}; + +} // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/gameplay/ecs/state.hpp b/include/Cubed/gameplay/ecs/state.hpp new file mode 100644 index 0000000..4a2e91d --- /dev/null +++ b/include/Cubed/gameplay/ecs/state.hpp @@ -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 \ No newline at end of file diff --git a/include/Cubed/gameplay/ecs/transform.hpp b/include/Cubed/gameplay/ecs/transform.hpp new file mode 100644 index 0000000..f1554d8 --- /dev/null +++ b/include/Cubed/gameplay/ecs/transform.hpp @@ -0,0 +1,30 @@ +#pragma once +#include +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{}; +}; + +struct RenderTransform { + Position position{}; + Orientation orientation{}; + Direction direction{}; +}; + +} // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/gameplay/player.hpp b/include/Cubed/gameplay/gait.hpp similarity index 96% rename from include/Cubed/gameplay/player.hpp rename to include/Cubed/gameplay/gait.hpp index 09deb0b..da10ea3 100644 --- a/include/Cubed/gameplay/player.hpp +++ b/include/Cubed/gameplay/gait.hpp @@ -1,6 +1,7 @@ #pragma once #include #include + namespace Cubed { enum class Gait { STOP = 0, WALK = 1, RUN = 2 }; constexpr int get_gait_id(Gait gait) { return std::to_underlying(gait); } @@ -17,5 +18,4 @@ inline Gait get_gait_from_id(int id) { throw std::runtime_error("Unknown Gait"); } } - -} // namespace Cubed +} // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/gameplay/hitbox.hpp b/include/Cubed/gameplay/hitbox.hpp new file mode 100644 index 0000000..70ab592 --- /dev/null +++ b/include/Cubed/gameplay/hitbox.hpp @@ -0,0 +1,26 @@ +#pragma once +#include + +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 \ No newline at end of file diff --git a/include/Cubed/gameplay/hitbox_manager.hpp b/include/Cubed/gameplay/hitbox_manager.hpp new file mode 100644 index 0000000..b8a2292 --- /dev/null +++ b/include/Cubed/gameplay/hitbox_manager.hpp @@ -0,0 +1,37 @@ +#pragma once +#include "Cubed/gameplay/hitbox.hpp" + +#include +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; + using IDMap = tbb::concurrent_hash_map; + using NameMap = tbb::concurrent_hash_map; + HitboxID m_next = 0; + IDMap m_id_map; + NameMap m_name_map; + HitboxMap m_hitboxes; + Handle load(std::string_view name); +}; +} // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/gameplay/item.hpp b/include/Cubed/gameplay/item.hpp new file mode 100644 index 0000000..71da9dc --- /dev/null +++ b/include/Cubed/gameplay/item.hpp @@ -0,0 +1,39 @@ +#pragma once +#include "Cubed/gameplay/block.hpp" + +#include +#include +#include +namespace Cubed { +using ItemID = uint16_t; + +enum class ItemKind { + NONE, + BLOCK, + SPAWN_EGG + +}; + +using ItemProperty = std::variant; + +struct ItemData { + ItemID id = 0; + std::string name; + std::string local_name; + std::string description; + std::string path; + ItemKind kind = ItemKind::NONE; + ItemProperty property; +}; + +inline constexpr ItemKind get_item_kind(std::string_view kind) { + if (kind == "block") { + return ItemKind::BLOCK; + } + if (kind == "spawn_egg") { + return ItemKind::SPAWN_EGG; + } + return ItemKind::NONE; +} + +} // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/gameplay/item_manager.hpp b/include/Cubed/gameplay/item_manager.hpp new file mode 100644 index 0000000..c9fc02c --- /dev/null +++ b/include/Cubed/gameplay/item_manager.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include "Cubed/gameplay/item.hpp" + +#include +#include +#include +namespace Cubed { +class ItemManager { +public: + ItemManager(); + void init(); + static ItemManager& instance(); + + static const ItemData& get(std::string_view key); + static const ItemData& get(ItemID id); + + static ItemID size(); + + const ItemData& get_item_data(std::string_view key) const; + const ItemData& get_item_data(ItemID id) const; + +private: + using ItemMap = tbb::concurrent_hash_map; + using acc = ItemMap::accessor; + using cacc = ItemMap::const_accessor; + + using IDMap = tbb::concurrent_hash_map; + using BlockToIDMap = tbb::concurrent_hash_map; + void add(const std::filesystem::path& path); + + ItemMap m_map; + + IDMap m_id_map; + BlockToIDMap m_block_to_id_map; + + static inline const ItemData EMPTY; +}; +} // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/gameplay/item_stack.hpp b/include/Cubed/gameplay/item_stack.hpp index 5e723a2..70fd02f 100644 --- a/include/Cubed/gameplay/item_stack.hpp +++ b/include/Cubed/gameplay/item_stack.hpp @@ -1,8 +1,9 @@ #pragma once -#include "Cubed/gameplay/block.hpp" +#include "Cubed/gameplay/item.hpp" namespace Cubed { + struct ItemStack { - BlockType type = 0; + ItemID id = 0; size_t sum = 0; }; } // namespace Cubed diff --git a/include/Cubed/gameplay/local_player.hpp b/include/Cubed/gameplay/local_player.hpp new file mode 100644 index 0000000..520c05c --- /dev/null +++ b/include/Cubed/gameplay/local_player.hpp @@ -0,0 +1,161 @@ +#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/input/event.hpp" +#include "Cubed/input/input.hpp" + +#include +#include +#include +#include +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; + 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& 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 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 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 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 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 update_physical(float dt, glm::vec3& pos); + glm::vec3 get_move_distance(float dt); +}; +} // namespace Cubed diff --git a/include/Cubed/gameplay/model.hpp b/include/Cubed/gameplay/model.hpp new file mode 100644 index 0000000..3899181 --- /dev/null +++ b/include/Cubed/gameplay/model.hpp @@ -0,0 +1,7 @@ +#pragma once +#include + +using ModelID = uint32_t; +struct Model { + ModelID id; +}; \ No newline at end of file diff --git a/include/Cubed/gameplay/network_server.hpp b/include/Cubed/gameplay/network_server.hpp index f12fbd6..7abf197 100644 --- a/include/Cubed/gameplay/network_server.hpp +++ b/include/Cubed/gameplay/network_server.hpp @@ -14,8 +14,7 @@ public: void stop(); // Run in another thread after initialization is complete - void start_server(int port); - void start_server(); + void start_server(int port, RunMode mode); int port() const; ServerWorld& server_world(); diff --git a/include/Cubed/gameplay/packet.hpp b/include/Cubed/gameplay/packet.hpp index 1af675a..a2a3ac7 100644 --- a/include/Cubed/gameplay/packet.hpp +++ b/include/Cubed/gameplay/packet.hpp @@ -51,18 +51,28 @@ 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, + S2C_ENTITY_UPDATE_BATCH = 3012, + CHAT_MSG = 4001, VOICE_MSG = 4002, + PING = 9001, PONG = 9002 @@ -111,6 +121,18 @@ template <> constexpr uint16_t get_packet_id() { template <> constexpr uint16_t get_packet_id() { return std::to_underlying(PacketEnum::S2C_CLEAR_ALL_CHUNKS); } +template <> constexpr uint16_t get_packet_id() { + return std::to_underlying(PacketEnum::S2C_ENTITY_CREATE); +} +template <> constexpr uint16_t get_packet_id() { + return std::to_underlying(PacketEnum::S2C_ENTITY_DESTORY); +} +template <> constexpr uint16_t get_packet_id() { + return std::to_underlying(PacketEnum::C2S_ENTITY_CREATE_REQUEST); +} +template <> constexpr uint16_t get_packet_id() { + return std::to_underlying(PacketEnum::C2S_ENTITY_DESTORY_REQUEST); +} template <> constexpr uint16_t get_packet_id() { return std::to_underlying(PacketEnum::UPDATE_TIME); } @@ -129,6 +151,12 @@ template <> constexpr uint16_t get_packet_id() { template <> constexpr uint16_t get_packet_id() { return std::to_underlying(PacketEnum::VOICE_MSG); } +template <> constexpr uint16_t get_packet_id() { + return std::to_underlying(PacketEnum::S2C_ENTITY_UPDATE); +} +template <> constexpr uint16_t get_packet_id() { + return std::to_underlying(PacketEnum::S2C_ENTITY_UPDATE_BATCH); +} template requires std::derived_from @@ -180,6 +208,12 @@ Packet make_packet(const T& msg) { return packet; } +template + requires std::derived_from +Packet make_packet(const T* msg) { + return make_packet(*msg); +} + inline PacketHeader decode_packet_header(std::span header) { if (header.size() < HEADER_LEN) throw std::runtime_error("Invalid header"); diff --git a/include/Cubed/gameplay/server_chunk.hpp b/include/Cubed/gameplay/server_chunk.hpp index 8fa7075..9eb9a9a 100644 --- a/include/Cubed/gameplay/server_chunk.hpp +++ b/include/Cubed/gameplay/server_chunk.hpp @@ -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 namespace Cubed { class ServerWorld; -class ServerChunk { +class ServerChunk : public Chunk { public: ServerChunk(ServerWorld& world, ChunkPos chunk_pos, bool temp_chunk = false); diff --git a/include/Cubed/gameplay/server_entity_manager.hpp b/include/Cubed/gameplay/server_entity_manager.hpp new file mode 100644 index 0000000..e5af786 --- /dev/null +++ b/include/Cubed/gameplay/server_entity_manager.hpp @@ -0,0 +1,80 @@ +#pragma once +#include "Cubed/gameplay/ecs/entity.hpp" +#include "Cubed/gameplay/gait.hpp" +#include "glm/ext/vector_float3.hpp" + +#include +#include +#include +#include +namespace Cubed { +class ServerWorld; +class Session; +class ServerEntityManager { +public: + static constexpr size_t PER_CREATURE_LIMITS = 100; + + ServerEntityManager(ServerWorld& world); + + void init(); + void update(); + void add_creature(std::string_view name, const glm::vec3& world_pos); + void destory(EntityID id); + void handle_player_login(std::shared_ptr session); + + size_t max_creature_sum() const; + size_t creature_sum() const; + size_t entity_sum() const; + +private: + enum class Command { CREATE, SEND_ALL_ENTITIES, DESTORY }; + struct EntityCreateElement { + std::string name; + glm::vec3 pos; + }; + + struct EntitySendData { + EntityID id; + glm::vec3 pos; + glm::vec3 dir; + Gait gait; + }; + + using EntityMap = tbb::concurrent_hash_map; + using acc = EntityMap::accessor; + using cacc = EntityMap::const_accessor; + using CreateFunc = std::function; + using TaskElement = + std::variant, EntityCreateElement, EntityID>; + using TaskPair = std::pair; + ServerWorld& m_world; + std::atomic m_creature_sum{0}; + std::atomic m_entity_sum{0}; + tbb::concurrent_queue m_tasks; + entt::registry m_registry; + EntityID m_next = 0; + EntityMap m_entities; + std::unordered_map 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); + void update_ai(entt::entity e); + void update_move(entt::entity e); + void update_send(entt::entity e, + tbb::concurrent_vector& sessions); + template + EntityID create_entity_in_factory(Args&&... args) { + auto entity = m_registry.create(); + + ((m_registry.emplace>( + entity, std::forward(args))), + ...); + auto id = m_next++; + m_entities.emplace(id, entity); + return id; + } +}; +} // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/gameplay/server_player.hpp b/include/Cubed/gameplay/server_player.hpp index c98ee92..9c28b79 100644 --- a/include/Cubed/gameplay/server_player.hpp +++ b/include/Cubed/gameplay/server_player.hpp @@ -1,7 +1,7 @@ #pragma once #include "Cubed/gameplay/chunk_pos.hpp" +#include "Cubed/gameplay/gait.hpp" #include "Cubed/gameplay/game_time.hpp" -#include "Cubed/gameplay/player.hpp" #include #include diff --git a/include/Cubed/gameplay/server_world.hpp b/include/Cubed/gameplay/server_world.hpp index 8502570..e1e6930 100644 --- a/include/Cubed/gameplay/server_world.hpp +++ b/include/Cubed/gameplay/server_world.hpp @@ -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" @@ -20,19 +22,20 @@ #include #include #include +#include #include #include #include namespace Cubed { class Session; -class ServerWorld { +class ServerWorld : public World { public: enum class ThreadPoolKind { NET, GEN }; ServerWorld(Config& config); ~ServerWorld(); void stop(); void handle_player_exit(const std::string& uuid); - void init_world(); + void init_world(RunMode mode); void need_gen(std::string uuid); void update(); void hot_reload(); @@ -84,7 +87,24 @@ 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; + + tbb::concurrent_vector> get_all_session() const; + + uint32_t get_chunk_ref_count(const glm::vec3& pos) const; + ServerEntityManager& entity_manager(); + std::shared_ptr get_compute_pool(); + size_t player_sum() 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 void register_timer(std::string_view id, TickType threshold, Fn&& f) { m_timers.emplace(std::piecewise_construct, @@ -97,7 +117,27 @@ private: struct ChunkEntity { ChunkState state; std::shared_ptr chunk; - uint32_t ref_count = 0; + std::atomic ref_count = 0; + ChunkEntity() = default; + ChunkEntity(ChunkState s, std::shared_ptr c = {}) + : state(s), chunk(std::move(c)) {} + + ChunkEntity& operator=(ChunkEntity&& o) noexcept { + if (this == &o) { + return *this; + } + + state = std::exchange(o.state, ServerWorld::ChunkState::NONE); + chunk = std::move(o.chunk); + ref_count = o.ref_count.exchange(0); + return *this; + } + + ChunkEntity(ChunkEntity&& o) noexcept + : state(std::exchange(o.state, ServerWorld::ChunkState::NONE)), + chunk(std::move(o.chunk)), ref_count(o.ref_count.exchange(0)) {} + ChunkEntity(const ChunkEntity&) = delete; + ChunkEntity& operator=(const ChunkEntity&) = delete; }; enum class ChunkLoadStyle { RANDOM, CENTER }; @@ -119,13 +159,14 @@ private: using PlayerUUIDMap = tbb::concurrent_hash_map; 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; - + std::atomic m_runmode{RunMode::HYBRID}; + ServerEntityManager m_entity_manager; // key = uuid PlayerHashMap m_players; ChunkHashMap m_chunks; @@ -146,8 +187,9 @@ private: std::atomic m_init{false}; std::atomic m_stopped{false}; std::atomic m_rendering_distance{24}; - std::atomic m_gen_pool_threads{0}; - std::atomic m_net_pool_threads{0}; + std::atomic m_gen_threads{0}; + std::atomic m_net_threads{0}; + std::atomic m_compute_threads{0}; std::atomic m_max_threads{1}; std::atomic m_player_sum{0}; std::atomic m_game_ticks{0}; @@ -155,7 +197,7 @@ private: std::atomic m_tick_running{true}; std::atomic 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; @@ -163,6 +205,7 @@ private: std::atomic> m_gen_thread_pool; std::atomic> m_net_thread_pool; + std::atomic> m_compute_thread_pool; std::atomic m_chunk_load_style{ChunkLoadStyle::CENTER}; diff --git a/include/Cubed/gameplay/systems/physical_system.hpp b/include/Cubed/gameplay/systems/physical_system.hpp new file mode 100644 index 0000000..5a57406 --- /dev/null +++ b/include/Cubed/gameplay/systems/physical_system.hpp @@ -0,0 +1,16 @@ +#pragma once +#include "Cubed/gameplay/ecs/movement.hpp" + +#include +namespace Cubed { +class ServerWorld; +class PhysicalSystem { +public: + static glm::vec3 get_move_distance(const TickVelocity& v); + + static void update(ServerWorld& world, entt::registry& registry, + entt::entity e); + +private: +}; +} // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/gameplay/systems/speed_system.hpp b/include/Cubed/gameplay/systems/speed_system.hpp new file mode 100644 index 0000000..090b5e4 --- /dev/null +++ b/include/Cubed/gameplay/systems/speed_system.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include +namespace Cubed { +class SpeedSystem { +public: + static void update(float dt, entt::registry& registry, entt::entity e); + +private: +}; +} // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/gameplay/systems/wander_ai_system.hpp b/include/Cubed/gameplay/systems/wander_ai_system.hpp new file mode 100644 index 0000000..24c7f12 --- /dev/null +++ b/include/Cubed/gameplay/systems/wander_ai_system.hpp @@ -0,0 +1,14 @@ +#pragma once +#include "Cubed/gameplay/ecs/ai_struct.hpp" +#include "Cubed/gameplay/ecs/server_entity.hpp" + +#include +namespace Cubed { +class WanderAISystem { +public: + static void update(entt::registry& registry, entt::entity e); + +private: + static void do_ai(BaseServerCreature& creature, MoveBoost& move_boost); +}; +} // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/gameplay/world.hpp b/include/Cubed/gameplay/world.hpp new file mode 100644 index 0000000..710166a --- /dev/null +++ b/include/Cubed/gameplay/world.hpp @@ -0,0 +1,32 @@ +#pragma once +#include "Cubed/gameplay/block.hpp" +#include "Cubed/gameplay/hitbox.hpp" + +#include +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(pos.x) + 0.5f, + static_cast(pos.y) + 0.5f, + static_cast(pos.z) + 0.5f}, + glm::vec3{0.5f, 0.5f, 0.5f}}; + } +}; + +enum class RunMode { CLIENT_ONLY, SERVER_ONLY, HYBRID }; + +} // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/input/input.hpp b/include/Cubed/input/input.hpp index ef3e549..f634796 100644 --- a/include/Cubed/input/input.hpp +++ b/include/Cubed/input/input.hpp @@ -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; diff --git a/include/Cubed/render/model_manager.hpp b/include/Cubed/render/model_manager.hpp new file mode 100644 index 0000000..c1109f1 --- /dev/null +++ b/include/Cubed/render/model_manager.hpp @@ -0,0 +1,46 @@ +#pragma once +#include "Cubed/gameplay/model.hpp" +#include "Cubed/render/model_node.hpp" +#include "Cubed/tools/model_loader.hpp" + +#include +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; + using IDMap = tbb::concurrent_hash_map; + using NameMap = tbb::concurrent_hash_map; + ModelMap m_models; + IDMap m_id_map; + NameMap m_name_map; + Handle load_model(std::string_view model_name); + void load_anim_config(ModelNode& node, const std::string& path); +}; +} // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/render/model_node.hpp b/include/Cubed/render/model_node.hpp new file mode 100644 index 0000000..9f3fac1 --- /dev/null +++ b/include/Cubed/render/model_node.hpp @@ -0,0 +1,79 @@ +#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 +#include +#include +#include +#include +namespace Cubed { + +struct Mesh { + std::vector vertices; + std::vector indices; + std::unique_ptr vbo; + std::unique_ptr ebo; + std::unique_ptr vao; + std::unique_ptr texture; + void upload() { + vao = std::make_unique(); + vao->bind(); + vbo = std::make_unique(); + vbo->buffer_data(vertices.data(), vertices.size() * sizeof(Vertex3D)); + ebo = std::make_unique(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 NodeAnimRule { + enum class Role { NONE, LEG, HEAD }; + std::string node; + Role role = Role::NONE; + float phase = 0.0f; +}; + +struct ModelAnimConfig { + float walk_speed = 6.0f; + float walk_amp = 25.0f; + float run_speed = 12.0f; + float run_amp = 40.0f; + float body_bob = 0.05f; + float head_amp = 4.0f; + std::vector nodes; + + const NodeAnimRule* rule_for(std::string_view name) const { + for (const auto& r : nodes) { + if (r.node == name) { + return &r; + } + } + return nullptr; + } + + bool has_role(NodeAnimRule::Role role) const { + for (const auto& r : nodes) { + if (r.role == role) { + return true; + } + } + return false; + } +}; +struct ModelNode { + std::string name; + glm::mat4 transform{1.0f}; + + std::vector meshes; + std::vector children; + ModelAnimConfig anim; +}; + +} // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/render/model_renderer.hpp b/include/Cubed/render/model_renderer.hpp new file mode 100644 index 0000000..53c852a --- /dev/null +++ b/include/Cubed/render/model_renderer.hpp @@ -0,0 +1,55 @@ +#pragma once + +#include "Cubed/gameplay/ecs/animation.hpp" +#include "Cubed/gameplay/model.hpp" +#include "Cubed/render/model_node.hpp" + +#include +#include +#include +#include +namespace Cubed { +class Renderer; +class Camera; +class ModelRender { +public: + struct InstanceData { + glm::vec3 pos{0.0f}; + float yaw = 0.0f; + WalkPose pose; + }; + + struct DrawEntry { + const Mesh* mesh = nullptr; + size_t node_slot = 0; + }; + + struct ModelBatch { + size_t node_count = 0; + size_t capacity = 0; + std::vector entries; + std::vector instance_matrices; + std::unique_ptr instance_vbo; + }; + + ModelRender(Renderer& renderer); + + void render_instance(ModelID id, size_t sum, const Camera& camera, + bool shadow); + void build_vertices(ModelID id, std::span instances); + +private: + Renderer& m_renderer; + + std::unordered_map m_batches; + + size_t collect_matrices(const ModelNode& node, const glm::mat4& parent, + const WalkPose& pose, const ModelAnimConfig& cfg, + std::vector& out, size_t slot); + glm::mat4 pose_node(const ModelNode& node, const ModelAnimConfig& cfg, + const WalkPose& pose); + + ModelBatch& get_batch(ModelID id, const ModelNode& root); + size_t flatten_nodes(const ModelNode& node, size_t slot, ModelBatch& batch); +}; +} // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/render/player_renderer.hpp b/include/Cubed/render/player_renderer.hpp index 4ac66ad..d35e80e 100644 --- a/include/Cubed/render/player_renderer.hpp +++ b/include/Cubed/render/player_renderer.hpp @@ -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 { diff --git a/include/Cubed/render/renderer.hpp b/include/Cubed/render/renderer.hpp index 00edc28..b2deb3f 100644 --- a/include/Cubed/render/renderer.hpp +++ b/include/Cubed/render/renderer.hpp @@ -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 m_ui; WorldRenderer m_world_renderer; + ModelRender m_model_renderer; Config& m_config; bool handle_window_resize_event(const WindowResizeEvent& e); diff --git a/include/Cubed/render/texture.hpp b/include/Cubed/render/texture.hpp index f058ef5..129a400 100644 --- a/include/Cubed/render/texture.hpp +++ b/include/Cubed/render/texture.hpp @@ -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); diff --git a/include/Cubed/render/vertex_array.hpp b/include/Cubed/render/vertex_array.hpp index 8e55da1..cc5442c 100644 --- a/include/Cubed/render/vertex_array.hpp +++ b/include/Cubed/render/vertex_array.hpp @@ -19,6 +19,7 @@ public: void attribute(GLuint index, GLint size, GLenum type, GLsizei stride, const void* ptr, bool normalized = false) const; + void divisor(GLuint index, GLuint divisor = 1); private: GLuint m_vao = 0; diff --git a/include/Cubed/render/vertex_buffer.hpp b/include/Cubed/render/vertex_buffer.hpp index 83b9380..ca32817 100644 --- a/include/Cubed/render/vertex_buffer.hpp +++ b/include/Cubed/render/vertex_buffer.hpp @@ -26,6 +26,8 @@ public: GLuint id() const; void buffer_data(const void* data, GLsizeiptr size, BufferUsage usage = BufferUsage::STATIC_DRAW) const; + void buffer_sub_data(const void* data, GLsizeiptr size, + GLintptr offset) const; private: GLuint m_vbo = 0; diff --git a/include/Cubed/render/world_renderer.hpp b/include/Cubed/render/world_renderer.hpp index 40cd118..51a2c1f 100644 --- a/include/Cubed/render/world_renderer.hpp +++ b/include/Cubed/render/world_renderer.hpp @@ -1,10 +1,14 @@ #pragma once +#include "Cubed/gameplay/model.hpp" #include "Cubed/render/frame_buffer.hpp" +#include "Cubed/render/model_renderer.hpp" #include "Cubed/render/player_renderer.hpp" #include "Cubed/render/texture.hpp" #include #include +#include +#include namespace Cubed { class Renderer; class ClientWorld; @@ -12,6 +16,8 @@ class TextureManager; class Camera; class WorldRenderer { public: + using InstanceDataMap = + std::unordered_map>; struct ParallelLight { glm::vec3 sundir; // direction from sun to vertex glm::vec3 lightdir; @@ -128,11 +134,13 @@ private: void render_world(ClientWorld& world); - void shadow_map_generate(ClientWorld& world); + void shadow_map_generate(ClientWorld& world, const InstanceDataMap& map); 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, + const InstanceDataMap& map); + void render_entity(ClientWorld& world, const InstanceDataMap& map); void render_normal_block(const glm::mat4& model_mat, const glm::mat4& mv_mat, const glm::mat4& norm_mat, @@ -146,5 +154,6 @@ private: float angle_step_deg) const; glm::vec3 get_smoothed_shadow_lightdir(const glm::vec3& raw_shadow_sundir, float dt); + InstanceDataMap entity_build(ClientWorld& world); }; } // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/scene/scene_manager.hpp b/include/Cubed/scene/scene_manager.hpp index 973d380..e5a322c 100644 --- a/include/Cubed/scene/scene_manager.hpp +++ b/include/Cubed/scene/scene_manager.hpp @@ -36,6 +36,7 @@ public: App& app(); WorldSceneParam& world_scene_param(); + void push(SceneType type); private: enum class OperationType { PUSH, POP, CHANGE }; @@ -51,7 +52,7 @@ private: std::stack> m_scenes; void process_operation(); void change(SceneType type); - void push(SceneType type); + void pop(bool re_enter = true); std::unique_ptr create_scene(SceneType); diff --git a/include/Cubed/scene/world_scene.hpp b/include/Cubed/scene/world_scene.hpp index 6878d4b..448142e 100644 --- a/include/Cubed/scene/world_scene.hpp +++ b/include/Cubed/scene/world_scene.hpp @@ -42,6 +42,8 @@ public: bool is_recording() const; + RunMode runmode() const; + private: enum class PauseUI { PAUSE_MENU, INVENTORY }; SceneManager& m_scene_manager; @@ -61,6 +63,7 @@ private: ErrorUI m_error_ui; const Argument& m_argument; VoiceInputType m_input_type; + RunMode m_runmode = RunMode::HYBRID; bool handle_mouse_move_event(const MouseMoveEvent& e) override; bool handle_mouse_button_event(const MouseButtonEvent& e) override; bool handle_window_resize_event(const WindowResizeEvent& e) override; diff --git a/include/Cubed/texture_manager.hpp b/include/Cubed/texture_manager.hpp index 998ffd7..b078106 100644 --- a/include/Cubed/texture_manager.hpp +++ b/include/Cubed/texture_manager.hpp @@ -1,6 +1,7 @@ #pragma once #include "Cubed/config.hpp" #include "Cubed/gameplay/block.hpp" +#include "Cubed/gameplay/item.hpp" #include "Cubed/input/event.hpp" #include "Cubed/render/texture.hpp" @@ -10,6 +11,26 @@ namespace Cubed { class TextureManager { +public: + using ItemTextureMap = std::unordered_map>; + TextureManager(Config& config); + ~TextureManager(); + + void delete_texture(); + const Texture* get_block_status_array() const; + const Texture* get_texture_array() const; + const Texture* get_cross_plane_array() const; + const Texture* get_image_texture(const std::string& path); + const Texture* get_pbr_texture() const; + const ItemTextureMap& get_item_textures() const; + const Texture* get_skin() const; + void init_texture(); + + void need_reload(); + void update(); + int max_aniso() const; + bool handle_event(const Event& e); + private: bool m_need_reload = false; bool m_init = false; @@ -18,7 +39,7 @@ private: std::unique_ptr m_cross_plane_array; std::unique_ptr m_normal_texture_array; std::unique_ptr m_skin; - std::vector> m_item_textures; + ItemTextureMap m_item_textures; std::unordered_map> m_ui_map; GLfloat m_max_aniso = 0.0f; Config& m_config; @@ -26,7 +47,7 @@ private: void load_block_status(unsigned status_id); void load_block_texture(unsigned block_id); - void load_block_item_texture(unsigned id); + void init_item_texture(); void load_cross_plane_texture(unsigned id); const Texture* load_image_texture(const std::string& path); void load_pbr_texture(unsigned id); @@ -37,25 +58,6 @@ private: void init_skin(); void hot_reload(); bool handle_key_event(const KeyEvent& e); - -public: - TextureManager(Config& config); - ~TextureManager(); - - void delete_texture(); - const Texture* get_block_status_array() const; - const Texture* get_texture_array() const; - const Texture* get_cross_plane_array() const; - const Texture* get_image_texture(const std::string& path); - const Texture* get_pbr_texture() const; - const std::vector>& get_item_textures() const; - const Texture* get_skin() const; - void init_texture(); - - void need_reload(); - void update(); - int max_aniso() const; - bool handle_event(const Event& e); }; } // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/tools/cubed_concepts.hpp b/include/Cubed/tools/cubed_concepts.hpp new file mode 100644 index 0000000..7e8c846 --- /dev/null +++ b/include/Cubed/tools/cubed_concepts.hpp @@ -0,0 +1,7 @@ +#pragma once + +#include +namespace Cubed { +template +concept Ptr = std::is_pointer_v; +} diff --git a/include/Cubed/tools/cubed_random.hpp b/include/Cubed/tools/cubed_random.hpp index 8e13065..7553dba 100644 --- a/include/Cubed/tools/cubed_random.hpp +++ b/include/Cubed/tools/cubed_random.hpp @@ -1,4 +1,6 @@ #pragma once +#include "glm/ext/vector_float3.hpp" + #include 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; diff --git a/include/Cubed/tools/json_utils.hpp b/include/Cubed/tools/json_utils.hpp new file mode 100644 index 0000000..043742c --- /dev/null +++ b/include/Cubed/tools/json_utils.hpp @@ -0,0 +1,8 @@ + +#include +#include +#include +namespace Tools { +std::unordered_map +doc_to_map(const rapidjson::Document& doc); +} \ No newline at end of file diff --git a/include/Cubed/tools/model_loader.hpp b/include/Cubed/tools/model_loader.hpp new file mode 100644 index 0000000..da301da --- /dev/null +++ b/include/Cubed/tools/model_loader.hpp @@ -0,0 +1,22 @@ +#pragma once +#include "Cubed/render/model_node.hpp" + +#include +#include +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 \ No newline at end of file diff --git a/include/Cubed/tools/name_space.hpp b/include/Cubed/tools/name_space.hpp new file mode 100644 index 0000000..10c9d50 --- /dev/null +++ b/include/Cubed/tools/name_space.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include +#include +namespace Cubed { +inline std::vector parse_namespace(std::string_view str) { + std::vector 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 \ No newline at end of file diff --git a/include/Cubed/tools/net_utils.hpp b/include/Cubed/tools/net_utils.hpp new file mode 100644 index 0000000..56c524e --- /dev/null +++ b/include/Cubed/tools/net_utils.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include "Cubed/tools/cubed_concepts.hpp" +#include "common/vector3.pb.h" +#include "glm/ext/vector_float3.hpp" +namespace Cubed { +namespace Tools { +inline void set_net_vec3(Vec3* p, const glm::vec3& pos) { + p->set_x(pos.x); + p->set_y(pos.y); + p->set_z(pos.z); +} +template void set_net_pos(T ptr, const glm::vec3& pos) { + set_net_vec3(ptr->mutable_pos(), pos); +} + +inline glm::vec3 get_net_vec3(const Vec3* p) { + return glm::vec3{p->x(), p->y(), p->z()}; +} +inline glm::vec3 get_net_vec3(const Vec3& p) { + return glm::vec3{p.x(), p.y(), p.z()}; +} +} // namespace Tools +} // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/tools/sensitive_filter.hpp b/include/Cubed/tools/sensitive_filter.hpp index a980921..bf93e9f 100644 --- a/include/Cubed/tools/sensitive_filter.hpp +++ b/include/Cubed/tools/sensitive_filter.hpp @@ -1,11 +1,13 @@ #pragma once -#include +#include +#include +#include namespace Cubed { class SensitiveFilter { public: SensitiveFilter(); - void load(const nlohmann::json& j); + void load(const rapidjson::Document& doc); std::string filter(std::string_view text); private: diff --git a/include/Cubed/tools/shader_tools.hpp b/include/Cubed/tools/shader_tools.hpp index 443328a..d9eb81e 100644 --- a/include/Cubed/tools/shader_tools.hpp +++ b/include/Cubed/tools/shader_tools.hpp @@ -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 diff --git a/include/Cubed/tools/sparse_vector.hpp b/include/Cubed/tools/sparse_vector.hpp new file mode 100644 index 0000000..3b57c61 --- /dev/null +++ b/include/Cubed/tools/sparse_vector.hpp @@ -0,0 +1,318 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +namespace Cubed { +template class SparseVector { +private: + std::vector> m_data; + std::vector m_free_list; + std::vector m_generation; + + std::vector m_dense; + std::vector 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(m_index) - + static_cast(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(m_index) - + static_cast(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 [[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(value)); + + m_dense_index[id] = m_dense.size(); + } else { + id = m_data.size(); + m_data.push_back(std::forward(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 Handle emplace(Args&&... args) { + return insert(T(std::forward(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 \ No newline at end of file diff --git a/include/Cubed/tools/threas_utils.hpp b/include/Cubed/tools/threas_utils.hpp new file mode 100644 index 0000000..a598433 --- /dev/null +++ b/include/Cubed/tools/threas_utils.hpp @@ -0,0 +1,130 @@ +#pragma once +#include "Cubed/gameplay/world.hpp" +#include "Cubed/tools/cubed_assert.hpp" + +#include +#include +namespace Cubed { +namespace Tools { + +constexpr size_t SERVER_RESERVED_THREADS = 3; // tick + netio + gen scheduler +constexpr size_t CLIENT_RESERVED_THREADS = + 3; // main/render + netio + system reserved + +constexpr size_t safe_sub(size_t a, size_t b) { return a > b ? a - b : 0; } + +inline size_t get_hardware_threads() { + auto hc = std::thread::hardware_concurrency(); + return hc == 0 ? 4 : static_cast(hc); +} + +inline size_t get_server_available_threads() { + return std::max( + 1, safe_sub(get_hardware_threads(), SERVER_RESERVED_THREADS)); +} + +inline size_t get_client_available_threads() { + return std::max( + 1, safe_sub(get_hardware_threads(), CLIENT_RESERVED_THREADS)); +} + +inline size_t get_client_threads(RunMode mode) { + switch (mode) { + case RunMode::SERVER_ONLY: + ASSERT_MSG(false, "Server Only don't need client pool"); + return 1; + case RunMode::CLIENT_ONLY: { + auto available = get_client_available_threads(); + return std::clamp(available, 1, 16); + } + + case RunMode::HYBRID: { + auto available = get_client_available_threads(); + return std::clamp(available / 2, 1, 4); + } + } + return 1; +} + +inline size_t get_server_net_pool_threads(RunMode mode) { + switch (mode) { + + case RunMode::SERVER_ONLY: { + auto available = get_server_available_threads(); + return std::clamp(available / 4, 1, + std::min(4, available)); + } + + case RunMode::CLIENT_ONLY: + ASSERT_MSG(false, "Client Only don't need net pool"); + return 1; + case RunMode::HYBRID: { + auto available = get_server_available_threads(); + return std::clamp(available / 8, 1, + std::min(4, available)); + } + } + return 1; +} + +inline size_t get_server_compute_treads(RunMode mode) { + switch (mode) { + case RunMode::HYBRID: + case RunMode::SERVER_ONLY: { + auto available = get_server_available_threads(); + + return std::clamp(available / 4, 1, + std::min(4, available)); + } + case RunMode::CLIENT_ONLY: + ASSERT_MSG(false, "Client Only don't need update pool"); + return 1; + } + return 1; +} + +inline size_t get_server_gen_threads(RunMode mode) { + switch (mode) { + case RunMode::SERVER_ONLY: { + auto available = get_server_available_threads(); + + auto net_pool = get_server_net_pool_threads(mode); + + auto update_pool = get_server_compute_treads(mode); + + size_t remain = available; + + remain -= std::min(remain, net_pool); + + remain -= std::min(remain, update_pool); + + return std::max(1, remain); + } + case RunMode::CLIENT_ONLY: + ASSERT_MSG(false, "Client Only don't need gen pool"); + return 1; + case RunMode::HYBRID: { + auto available = get_server_available_threads(); + + auto net_pool = get_server_net_pool_threads(mode); + + auto update_pool = get_server_compute_treads(mode); + + auto client = get_client_threads(mode) + CLIENT_RESERVED_THREADS; + + size_t remain = available; + + remain -= std::min(remain, net_pool); + + remain -= std::min(remain, update_pool); + + remain -= std::min(remain, client); + + return std::max(1, remain); + } + } + return 1; +} + +} // namespace Tools +} // namespace Cubed diff --git a/include/Cubed/tools/time_tools.hpp b/include/Cubed/tools/time_tools.hpp index 7d4add8..3c22df6 100644 --- a/include/Cubed/tools/time_tools.hpp +++ b/include/Cubed/tools/time_tools.hpp @@ -4,6 +4,7 @@ #include namespace Cubed { namespace Tools { +// return ms inline uint64_t get_time_ticks() { return SDL_GetTicks(); } } // namespace Tools diff --git a/include/Cubed/ui/inventory_ui.hpp b/include/Cubed/ui/inventory_ui.hpp index f53ea7e..c676e9e 100644 --- a/include/Cubed/ui/inventory_ui.hpp +++ b/include/Cubed/ui/inventory_ui.hpp @@ -20,7 +20,7 @@ private: Label* m_item_info = nullptr; Image* m_selected_image = nullptr; - BlockType m_selected_block = 0; + BlockType m_selected_id = 0; void update_item_info(); bool handle_mouse_button_event(const MouseButtonEvent& e) override; diff --git a/include/Cubed/ui/item_slot.hpp b/include/Cubed/ui/item_slot.hpp index ff06f8a..36bf015 100644 --- a/include/Cubed/ui/item_slot.hpp +++ b/include/Cubed/ui/item_slot.hpp @@ -1,6 +1,6 @@ #pragma once -#include "Cubed/gameplay/block.hpp" +#include "Cubed/gameplay/item.hpp" #include "Cubed/ui/image.hpp" #include "Cubed/ui/widget.hpp" namespace Cubed { @@ -13,11 +13,11 @@ public: ItemSlot& set_default_background(TextureManager& m_texture_manager); ItemSlot& set_scale(float m_scale); - ItemSlot& set_item(BlockType id, const Texture* texture); + ItemSlot& set_item(ItemID id, const Texture* texture); float width() const override; float height() const override; bool handle_mouse_move_event(const MouseMoveEvent& e) override; - BlockType block() const; + ItemID id() const; bool hovered() const; private: @@ -26,7 +26,7 @@ private: void on_update(float dt) override; std::unique_ptr m_background; std::unique_ptr m_foreground; - BlockType m_block_type; + ItemID m_id; float m_scale = 1.0f; bool m_hovered = false; }; diff --git a/include/nlohmann/.clang-format b/include/entt/.clang-format similarity index 100% rename from include/nlohmann/.clang-format rename to include/entt/.clang-format diff --git a/include/entt/config/config.h b/include/entt/config/config.h new file mode 100644 index 0000000..a200d29 --- /dev/null +++ b/include/entt/config/config.h @@ -0,0 +1,134 @@ +#ifndef ENTT_CONFIG_CONFIG_H +#define ENTT_CONFIG_CONFIG_H + +#if __has_include() +# include +#endif + +#include +#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 +#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 +# 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 diff --git a/include/entt/config/macro.h b/include/entt/config/macro.h new file mode 100644 index 0000000..b7b2323 --- /dev/null +++ b/include/entt/config/macro.h @@ -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 diff --git a/include/entt/config/version.h b/include/entt/config/version.h new file mode 100644 index 0000000..d014997 --- /dev/null +++ b/include/entt/config/version.h @@ -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 diff --git a/include/entt/container/dense_map.hpp b/include/entt/container/dense_map.hpp new file mode 100644 index 0000000..e4f5ee5 --- /dev/null +++ b/include/entt/container/dense_map.hpp @@ -0,0 +1,1026 @@ +#ifndef ENTT_CONTAINER_DENSE_MAP_HPP +#define ENTT_CONTAINER_DENSE_MAP_HPP + +#include +#include "../config/config.h" +#include "../core/bit.hpp" +#include "../core/compressed_pair.hpp" +#include "../core/iterator.hpp" +#include "../core/memory.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_map_placeholder_position = (stl::numeric_limits::max)(); + +template +struct dense_map_node final { + using value_type = stl::pair; + + template + dense_map_node(const stl::size_t pos, Args &&...args) + : next{pos}, + element{stl::forward(args)...} {} + + template + dense_map_node(stl::allocator_arg_t, const auto &allocator, const stl::size_t pos, Args &&...args) + : next{pos}, + element{entt::make_obj_using_allocator(allocator, stl::forward(args)...)} {} + + dense_map_node(stl::allocator_arg_t, const auto &allocator, const dense_map_node &other) + : next{other.next}, + element{entt::make_obj_using_allocator(allocator, other.element)} {} + + dense_map_node(stl::allocator_arg_t, const auto &allocator, dense_map_node &&other) + : next{other.next}, + element{entt::make_obj_using_allocator(allocator, stl::move(other.element))} {} + + stl::size_t next; + value_type element; +}; + +template +class dense_map_iterator final { + template + friend class dense_map_iterator; + + static_assert(stl::is_pointer_v, "Not a pointer type"); + using first_type = decltype(stl::as_const(stl::declval()->element.first)); + using second_type = decltype((stl::declval()->element.second)); + +public: + using value_type = stl::pair; + using pointer = input_iterator_pointer; + 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 dense_map_iterator() noexcept + : it{} {} + + constexpr dense_map_iterator(const It iter) noexcept + : it{iter} {} + + template + requires (!stl::same_as && stl::constructible_from) + constexpr dense_map_iterator(const dense_map_iterator &other) noexcept + : it{other.it} {} + + constexpr dense_map_iterator &operator++() noexcept { + return ++it, *this; + } + + constexpr dense_map_iterator operator++(int) noexcept { + const dense_map_iterator orig = *this; + return ++(*this), orig; + } + + constexpr dense_map_iterator &operator--() noexcept { + return --it, *this; + } + + constexpr dense_map_iterator operator--(int) noexcept { + const dense_map_iterator orig = *this; + return operator--(), orig; + } + + constexpr dense_map_iterator &operator+=(const difference_type value) noexcept { + it += value; + return *this; + } + + constexpr dense_map_iterator operator+(const difference_type value) const noexcept { + dense_map_iterator copy = *this; + return (copy += value); + } + + constexpr dense_map_iterator &operator-=(const difference_type value) noexcept { + return (*this += -value); + } + + constexpr dense_map_iterator operator-(const difference_type value) const noexcept { + return (*this + -value); + } + + [[nodiscard]] constexpr reference operator[](const difference_type value) const noexcept { + return {it[value].element.first, it[value].element.second}; + } + + [[nodiscard]] constexpr pointer operator->() const noexcept { + return operator*(); + } + + [[nodiscard]] constexpr reference operator*() const noexcept { + return operator[](0); + } + + template + [[nodiscard]] constexpr stl::ptrdiff_t operator-(const dense_map_iterator &other) const noexcept { + return it - other.it; + } + + template + [[nodiscard]] constexpr bool operator==(const dense_map_iterator &other) const noexcept { + return it == other.it; + } + + template + [[nodiscard]] constexpr auto operator<=>(const dense_map_iterator &other) const noexcept { + return it <=> other.it; + } + +private: + It it; +}; + +template +class dense_map_local_iterator final { + template + friend class dense_map_local_iterator; + + static_assert(stl::is_pointer_v, "Not a pointer type"); + using first_type = decltype(stl::as_const(stl::declval()->element.first)); + using second_type = decltype((stl::declval()->element.second)); + +public: + using value_type = stl::pair; + using pointer = input_iterator_pointer; + 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 dense_map_local_iterator() noexcept = default; + + constexpr dense_map_local_iterator(It iter, const stl::size_t pos) noexcept + : it{iter}, + offset{pos} {} + + template + requires (!stl::same_as && stl::constructible_from) + constexpr dense_map_local_iterator(const dense_map_local_iterator &other) noexcept + : it{other.it}, + offset{other.offset} {} + + constexpr dense_map_local_iterator &operator++() noexcept { + return (offset = it[static_cast(offset)].next), *this; + } + + constexpr dense_map_local_iterator operator++(int) noexcept { + const dense_map_local_iterator orig = *this; + return ++(*this), orig; + } + + [[nodiscard]] constexpr pointer operator->() const noexcept { + return operator*(); + } + + [[nodiscard]] constexpr reference operator*() const noexcept { + const auto idx = static_cast(offset); + return {it[idx].element.first, it[idx].element.second}; + } + + template + [[nodiscard]] constexpr bool operator==(const dense_map_local_iterator &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_map_placeholder_position}; +}; + +} // namespace internal +/*! @endcond */ + +/** + * @brief Associative container for key-value pairs with unique keys. + * + * Internally, elements are organized into buckets. Which bucket an element is + * placed into depends entirely on the hash of its key. Keys with the same hash + * code appear in the same bucket. + * + * @tparam Key Key type of the associative container. + * @tparam Type Mapped type of the associative container. + * @tparam Hash Type of function to use to hash the keys. + * @tparam KeyEqual Type of function to use to compare the keys for equality. + * @tparam Allocator Type of allocator used to manage memory and elements. + */ +template +class dense_map { + static constexpr float default_threshold = 0.875f; + static constexpr stl::size_t minimum_capacity = 8u; + static constexpr stl::size_t placeholder_position = internal::dense_map_placeholder_position; + + using node_type = internal::dense_map_node; + using alloc_traits = stl::allocator_traits; + static_assert(stl::is_same_v>, "Invalid value type"); + using sparse_container_type = stl::vector>; + using packed_container_type = stl::vector>; + + [[nodiscard]] stl::size_t key_to_bucket(const auto &key) const noexcept { + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay) + return fast_mod(static_cast(sparse.second()(key)), bucket_count()); + } + + [[nodiscard]] auto constrained_find(const auto &key, const stl::size_t bucket) { + for(auto offset = sparse.first()[bucket]; offset != placeholder_position; offset = packed.first()[offset].next) { + if(packed.second()(packed.first()[offset].element.first, key)) { + return begin() + static_cast(offset); + } + } + + return end(); + } + + [[nodiscard]] auto constrained_find(const auto &key, const stl::size_t bucket) const { + for(auto offset = sparse.first()[bucket]; offset != placeholder_position; offset = packed.first()[offset].next) { + if(packed.second()(packed.first()[offset].element.first, key)) { + return cbegin() + static_cast(offset); + } + } + + return cend(); + } + + template + [[nodiscard]] auto insert_or_do_nothing(Other &&key, Args &&...args) { + const auto index = key_to_bucket(key); + + if(auto it = constrained_find(key, index); it != end()) { + return stl::make_pair(it, false); + } + + packed.first().emplace_back(sparse.first()[index], stl::piecewise_construct, stl::forward_as_tuple(stl::forward(key)), stl::forward_as_tuple(stl::forward(args)...)); + sparse.first()[index] = packed.first().size() - 1u; + rehash_if_required(); + + return stl::make_pair(--end(), true); + } + + template + [[nodiscard]] auto insert_or_overwrite(Other &&key, Arg &&value) { + const auto index = key_to_bucket(key); + + if(auto it = constrained_find(key, index); it != end()) { + it->second = stl::forward(value); + return stl::make_pair(it, false); + } + + packed.first().emplace_back(sparse.first()[index], stl::forward(key), stl::forward(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()[key_to_bucket(packed.first().back().element.first)]; + packed.first()[pos] = stl::move(packed.first().back()); + for(; *curr != last; curr = &packed.first()[*curr].next) {} + *curr = pos; + } + + packed.first().pop_back(); + } + + void rehash_if_required() { + if(const auto bc = bucket_count(); size() > static_cast(static_cast(bc) * max_load_factor())) { + rehash(bc * 2u); + } + } + +public: + /*! @brief Allocator type. */ + using allocator_type = Allocator; + /*! @brief Key type of the container. */ + using key_type = Key; + /*! @brief Mapped type of the container. */ + using mapped_type = Type; + /*! @brief Key-value type of the container. */ + using value_type = stl::pair; + /*! @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 keys. */ + using hasher = Hash; + /*! @brief Type of function to use to compare the keys for equality. */ + using key_equal = KeyEqual; + /*! @brief Input iterator type. */ + using iterator = internal::dense_map_iterator; + /*! @brief Constant input iterator type. */ + using const_iterator = internal::dense_map_iterator; + /*! @brief Input iterator type. */ + using local_iterator = internal::dense_map_local_iterator; + /*! @brief Constant input iterator type. */ + using const_local_iterator = internal::dense_map_local_iterator; + + /*! @brief Default constructor. */ + dense_map() + : dense_map{minimum_capacity} {} + + /** + * @brief Constructs an empty container with a given allocator. + * @param allocator The allocator to use. + */ + explicit dense_map(const allocator_type &allocator) + : dense_map{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_map(const size_type cnt, const allocator_type &allocator) + : dense_map{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_map(const size_type cnt, const hasher &hash, const allocator_type &allocator) + : dense_map{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_map(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_map(const dense_map &) = default; + + /** + * @brief Allocator-extended copy constructor. + * @param other The instance to copy from. + * @param allocator The allocator to use. + */ + dense_map(const dense_map &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_map(dense_map &&) noexcept = default; + + /** + * @brief Allocator-extended move constructor. + * @param other The instance to move from. + * @param allocator The allocator to use. + */ + dense_map(dense_map &&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_map() = default; + + /** + * @brief Default copy assignment operator. + * @return This container. + */ + dense_map &operator=(const dense_map &) = default; + + /** + * @brief Default move assignment operator. + * @return This container. + */ + dense_map &operator=(dense_map &&) noexcept = default; + + /** + * @brief Exchanges the contents with those of a given container. + * @param other Container to exchange the content with. + */ + void swap(dense_map &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 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 the key does not exist. + * @param value A key-value pair eventually convertible to the value type. + * @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 insert(const value_type &value) { + return insert_or_do_nothing(value.first, value.second); + } + + /*! @copydoc insert */ + stl::pair insert(value_type &&value) { + return insert_or_do_nothing(stl::move(value.first), stl::move(value.second)); + } + + /** + * @copydoc insert + * @tparam Arg Type of the key-value pair to insert into the container. + */ + template + requires stl::constructible_from + stl::pair insert(Arg &&value) { + return insert_or_do_nothing(stl::forward(value).first, stl::forward(value).second); + } + + /** + * @brief Inserts elements into the container, if their keys 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 Inserts an element into the container or assigns to the current + * element if the key already exists. + * @tparam Arg Type of the value to insert or assign. + * @param key A key used both to look up and to insert if not found. + * @param value A value to insert or assign. + * @return A pair consisting of an iterator to the element and a bool + * denoting whether the insertion took place. + */ + template + stl::pair insert_or_assign(const key_type &key, Arg &&value) { + return insert_or_overwrite(key, stl::forward(value)); + } + + /*! @copydoc insert_or_assign */ + template + stl::pair insert_or_assign(key_type &&key, Arg &&value) { + return insert_or_overwrite(stl::move(key), stl::forward(value)); + } + + /** + * @brief Constructs an element in-place, if the key 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 + stl::pair emplace([[maybe_unused]] Args &&...args) { + if constexpr(sizeof...(Args) == 0u) { + return insert_or_do_nothing(key_type{}); + } else if constexpr(sizeof...(Args) == 1u) { + return insert_or_do_nothing(stl::forward(args).first..., stl::forward(args).second...); + } else if constexpr(sizeof...(Args) == 2u) { + return insert_or_do_nothing(stl::forward(args)...); + } else { + auto &node = packed.first().emplace_back(packed.first().size(), stl::forward(args)...); + const auto index = key_to_bucket(node.element.first); + + if(auto it = constrained_find(node.element.first, index); it != end()) { + packed.first().pop_back(); + return stl::make_pair(it, false); + } + + stl::swap(node.next, sparse.first()[index]); + rehash_if_required(); + + return stl::make_pair(--end(), true); + } + } + + /** + * @brief Inserts in-place if the key does not exist, does nothing if the + * key exists. + * @tparam Args Types of arguments to forward to the constructor of the + * element. + * @param key A key used both to look up and to insert if not found. + * @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 + stl::pair try_emplace(const key_type &key, Args &&...args) { + return insert_or_do_nothing(key, stl::forward(args)...); + } + + /*! @copydoc try_emplace */ + template + stl::pair try_emplace(key_type &&key, Args &&...args) { + return insert_or_do_nothing(stl::move(key), stl::forward(args)...); + } + + /** + * @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->first); + 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(from) - 1u].element.first); + } + + return (begin() + dist); + } + + /** + * @brief Removes the element associated with a given key. + * @param key A key value of an element to remove. + * @return Number of elements removed (either 0 or 1). + */ + size_type erase(const key_type &key) { + for(size_type *curr = &sparse.first()[key_to_bucket(key)]; *curr != placeholder_position; curr = &packed.first()[*curr].next) { + if(packed.second()(packed.first()[*curr].element.first, key)) { + const auto index = *curr; + *curr = packed.first()[*curr].next; + move_and_pop(index); + return 1u; + } + } + + return 0u; + } + + /** + * @brief Accesses a given element with bounds checking. + * @param key A key of an element to find. + * @return A reference to the mapped value of the requested element. + */ + [[nodiscard]] mapped_type &at(const key_type &key) { + auto it = find(key); + ENTT_ASSERT(it != end(), "Invalid key"); + return it->second; + } + + /*! @copydoc at */ + [[nodiscard]] const mapped_type &at(const key_type &key) const { + auto it = find(key); + ENTT_ASSERT(it != cend(), "Invalid key"); + return it->second; + } + + /** + * @brief Accesses a given element with bounds checking. + * @param key A key of an element to find. + * @return A reference to the mapped value of the requested element. + */ + [[nodiscard]] mapped_type const &at(const auto &key) const + requires is_transparent_v && is_transparent_v { + auto it = find(key); + ENTT_ASSERT(it != cend(), "Invalid key"); + return it->second; + } + + /*! @copydoc at */ + [[nodiscard]] mapped_type &at(const auto &key) + requires is_transparent_v && is_transparent_v { + auto it = find(key); + ENTT_ASSERT(it != end(), "Invalid key"); + return it->second; + } + + /** + * @brief Accesses or inserts a given element. + * @param key A key of an element to find or insert. + * @return A reference to the mapped value of the requested element. + */ + [[nodiscard]] mapped_type &operator[](const key_type &key) { + return insert_or_do_nothing(key).first->second; + } + + /** + * @brief Accesses or inserts a given element. + * @param key A key of an element to find or insert. + * @return A reference to the mapped value of the requested element. + */ + [[nodiscard]] mapped_type &operator[](key_type &&key) { + return insert_or_do_nothing(stl::move(key)).first->second; + } + + /** + * @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 key_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 && is_transparent_v { + return find(key) != end(); + } + + /** + * @brief Finds an element with a given key. + * @param key Key value of an element to search for. + * @return An iterator to an element with the given key. If no such element + * is found, a past-the-end iterator is returned. + */ + [[nodiscard]] iterator find(const key_type &key) { + return constrained_find(key, key_to_bucket(key)); + } + + /*! @copydoc find */ + [[nodiscard]] const_iterator find(const key_type &key) const { + return constrained_find(key, key_to_bucket(key)); + } + + /** + * @brief Finds an element with a key that compares _equivalent_ to a given + * key. + * @param key Key value of an element to search for. + * @return An iterator to an element with the given key. If no such element + * is found, a past-the-end iterator is returned. + */ + [[nodiscard]] iterator find(const auto &key) + requires is_transparent_v && is_transparent_v { + return constrained_find(key, key_to_bucket(key)); + } + + /*! @copydoc find */ + [[nodiscard]] const_iterator find(const auto &key) const + requires is_transparent_v && is_transparent_v { + return constrained_find(key, key_to_bucket(key)); + } + + /** + * @brief Returns a range containing all elements with a given key. + * @param key Key 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 equal_range(const key_type &key) { + const auto it = find(key); + return {it, it + !(it == end())}; + } + + /*! @copydoc equal_range */ + [[nodiscard]] stl::pair equal_range(const key_type &key) const { + const auto it = find(key); + return {it, it + !(it == cend())}; + } + + /** + * @brief Returns a range containing all elements that compare _equivalent_ + * to a given key. + * @param key Key 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 equal_range(const auto &key) + requires is_transparent_v && is_transparent_v { + const auto it = find(key); + return {it, it + !(it == end())}; + } + + /*! @copydoc equal_range */ + [[nodiscard]] stl::pair equal_range(const auto &key) const + requires is_transparent_v && is_transparent_v { + const auto it = find(key); + return {it, it + !(it == cend())}; + } + + /** + * @brief Checks if the container contains an element with a given key. + * @param key Key value of an element to search for. + * @return True if there is such an element, false otherwise. + */ + [[nodiscard]] bool contains(const key_type &key) const { + return (find(key) != cend()); + } + + /** + * @brief Checks if the container contains an element with a key that + * compares _equivalent_ to a given value. + * @param key Key value of an element to search for. + * @return True if there is such an element, false otherwise. + */ + [[nodiscard]] bool contains(const auto &key) const + requires is_transparent_v && is_transparent_v { + return (find(key) != 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(stl::distance(begin(index), end(index))); + } + + /** + * @brief Returns the bucket for a given key. + * @param key The value of the key to examine. + * @return The bucket for the given key. + */ + [[nodiscard]] size_type bucket(const key_type &key) const { + return key_to_bucket(key); + } + + /** + * @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(size()) / static_cast(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(static_cast(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 = key_to_bucket(packed.first()[pos].element.first); + packed.first()[pos].next = 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(stl::ceil(static_cast(cnt) / max_load_factor()))); + } + + /** + * @brief Returns the function used to hash the keys. + * @return The function used to hash the keys. + */ + [[nodiscard]] hasher hash_function() const { + return sparse.second(); + } + + /** + * @brief Returns the function used to compare keys for equality. + * @return The function used to compare keys for equality. + */ + [[nodiscard]] key_equal key_eq() const { + return packed.second(); + } + +private: + compressed_pair sparse; + compressed_pair packed; + float threshold{default_threshold}; +}; + +} // namespace entt + +/*! @cond ENTT_INTERNAL */ +#include + +namespace std { + +template +struct uses_allocator, Allocator> + : entt::stl::true_type {}; + +} // namespace std +/*! @endcond */ + +#endif diff --git a/include/entt/container/dense_set.hpp b/include/entt/container/dense_set.hpp new file mode 100644 index 0000000..df71b72 --- /dev/null +++ b/include/entt/container/dense_set.hpp @@ -0,0 +1,890 @@ +#ifndef ENTT_CONTAINER_DENSE_SET_HPP +#define ENTT_CONTAINER_DENSE_SET_HPP + +#include +#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::max)(); + +template +class dense_set_iterator final { + template + friend class dense_set_iterator; + + static_assert(stl::is_pointer_v, "Not a pointer type"); + +public: + using value_type = stl::remove_const_t>::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 + requires (!stl::same_as && stl::constructible_from) + constexpr dense_set_iterator(const dense_set_iterator &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 + [[nodiscard]] constexpr stl::ptrdiff_t operator-(const dense_set_iterator &other) const noexcept { + return it - other.it; + } + + template + [[nodiscard]] constexpr bool operator==(const dense_set_iterator &other) const noexcept { + return it == other.it; + } + + template + [[nodiscard]] constexpr auto operator<=>(const dense_set_iterator &other) const noexcept { + return it <=> other.it; + } + +private: + It it; +}; + +template +class dense_set_local_iterator final { + template + friend class dense_set_local_iterator; + + static_assert(stl::is_pointer_v, "Not a pointer type"); + +public: + using value_type = stl::remove_const_t>::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 + requires (!stl::same_as && stl::constructible_from) + constexpr dense_set_local_iterator(const dense_set_local_iterator &other) noexcept + : it{other.it}, + offset{other.offset} {} + + constexpr dense_set_local_iterator &operator++() noexcept { + return offset = it[static_cast(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(offset)].second); + } + + [[nodiscard]] constexpr reference operator*() const noexcept { + return *operator->(); + } + + template + [[nodiscard]] constexpr bool operator==(const dense_set_local_iterator &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 +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; + using alloc_traits = stl::allocator_traits; + static_assert(stl::is_same_v, "Invalid value type"); + using sparse_container_type = stl::vector>; + using packed_container_type = stl::vector>; + + [[nodiscard]] stl::size_t value_to_bucket(const auto &value) const noexcept { + return fast_mod(static_cast(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(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(offset); + } + } + + return cend(); + } + + template + [[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(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(static_cast(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; + /*! @brief Constant random access iterator type. */ + using const_iterator = internal::dense_set_iterator; + /*! @brief Reverse iterator type. */ + using reverse_iterator = stl::reverse_iterator; + /*! @brief Constant reverse iterator type. */ + using const_reverse_iterator = stl::reverse_iterator; + /*! @brief Forward iterator type. */ + using local_iterator = internal::dense_set_local_iterator; + /*! @brief Constant forward iterator type. */ + using const_local_iterator = internal::dense_set_local_iterator; + + /*! @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 insert(const value_type &value) { + return insert_or_do_nothing(value); + } + + /*! @copydoc insert */ + stl::pair 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 + stl::pair emplace(Args &&...args) { + if constexpr(((sizeof...(Args) == 1u) && ... && stl::is_same_v, value_type>)) { + return insert_or_do_nothing(stl::forward(args)...); + } else { + auto &node = packed.first().emplace_back(stl::piecewise_construct, stl::make_tuple(packed.first().size()), stl::forward_as_tuple(stl::forward(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(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 && is_transparent_v { + 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 && is_transparent_v { + return constrained_find(value, value_to_bucket(value)); + } + + /*! @copydoc find */ + [[nodiscard]] const_iterator find(const auto &value) const + requires is_transparent_v && is_transparent_v { + 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 equal_range(const value_type &value) { + const auto it = find(value); + return {it, it + !(it == end())}; + } + + /*! @copydoc equal_range */ + [[nodiscard]] stl::pair 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 equal_range(const auto &value) + requires is_transparent_v && is_transparent_v { + const auto it = find(value); + return {it, it + !(it == end())}; + } + + /*! @copydoc equal_range */ + [[nodiscard]] stl::pair equal_range(const auto &value) const + requires is_transparent_v && is_transparent_v { + 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 && is_transparent_v { + 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(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(size()) / static_cast(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(static_cast(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(stl::ceil(static_cast(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; + compressed_pair packed; + float threshold{default_threshold}; +}; + +} // namespace entt + +#endif diff --git a/include/entt/container/fwd.hpp b/include/entt/container/fwd.hpp new file mode 100644 index 0000000..72f0906 --- /dev/null +++ b/include/entt/container/fwd.hpp @@ -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, + typename = stl::equal_to<>, + typename = stl::allocator>> +class dense_map; + +template< + typename Type, + typename = stl::hash, + typename = stl::equal_to<>, + typename = stl::allocator> +class dense_set; + +template +class basic_table; + +/** + * @brief Alias declaration for the most common use case. + * @tparam Type Element types. + */ +template +using table = basic_table...>; + +} // namespace entt + +#endif diff --git a/include/entt/container/table.hpp b/include/entt/container/table.hpp new file mode 100644 index 0000000..86e278d --- /dev/null +++ b/include/entt/container/table.hpp @@ -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 +class table_iterator { + template + friend class table_iterator; + +public: + using value_type = decltype(stl::forward_as_tuple(*stl::declval()...)); + using pointer = input_iterator_pointer; + 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 + requires (stl::constructible_from && ...) + constexpr table_iterator(const table_iterator &other) noexcept + : table_iterator{stl::get(other.it)...} {} + + constexpr table_iterator &operator++() noexcept { + return (++stl::get(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), ...), *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) += 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)[value]...); + } + + [[nodiscard]] constexpr pointer operator->() const noexcept { + return {operator[](0)}; + } + + [[nodiscard]] constexpr reference operator*() const noexcept { + return operator[](0); + } + + template + [[nodiscard]] constexpr stl::ptrdiff_t operator-(const table_iterator &other) const noexcept { + return stl::get<0>(it) - stl::get<0>(other.it); + } + + template + [[nodiscard]] constexpr bool operator==(const table_iterator &other) const noexcept { + return stl::get<0>(it) == stl::get<0>(other.it); + } + + template + [[nodiscard]] constexpr auto operator<=>(const table_iterator &other) const noexcept { + return stl::get<0>(it) <=> stl::get<0>(other.it); + } + +private: + stl::tuple 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 +class basic_table { + using container_type = stl::tuple; + +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; + /*! @brief Constant input iterator type. */ + using const_iterator = internal::table_iterator; + /*! @brief Reverse iterator type. */ + using reverse_iterator = internal::table_iterator; + /*! @brief Constant reverse iterator type. */ + using const_reverse_iterator = internal::table_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(payload).size() * sizeof...(Container)) == (stl::get(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(payload).size() * sizeof...(Container)) == (stl::get(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 + basic_table(const Container &...container, const Allocator &allocator) noexcept + : payload{Container{container, allocator}...} { + ENTT_ASSERT((((stl::get(payload).size() * sizeof...(Container)) == (stl::get(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 + basic_table(Container &&...container, const Allocator &allocator) noexcept + : payload{Container{stl::move(container), allocator}...} { + ENTT_ASSERT((((stl::get(payload).size() * sizeof...(Container)) == (stl::get(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 + basic_table(basic_table &&other, const Allocator &allocator) + : payload{Container{stl::move(stl::get(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(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(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(payload).cbegin()...}; + } + + /*! @copydoc cbegin */ + [[nodiscard]] const_iterator begin() const noexcept { + return cbegin(); + } + + /*! @copydoc begin */ + [[nodiscard]] iterator begin() noexcept { + return {stl::get(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(payload).cend()...}; + } + + /*! @copydoc cend */ + [[nodiscard]] const_iterator end() const noexcept { + return cend(); + } + + /*! @copydoc end */ + [[nodiscard]] iterator end() noexcept { + return {stl::get(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(payload).crbegin()...}; + } + + /*! @copydoc crbegin */ + [[nodiscard]] const_reverse_iterator rbegin() const noexcept { + return crbegin(); + } + + /*! @copydoc rbegin */ + [[nodiscard]] reverse_iterator rbegin() noexcept { + return {stl::get(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(payload).crend()...}; + } + + /*! @copydoc crend */ + [[nodiscard]] const_reverse_iterator rend() const noexcept { + return crend(); + } + + /*! @copydoc rend */ + [[nodiscard]] reverse_iterator rend() noexcept { + return {stl::get(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 + stl::tuple emplace(Args &&...args) { + if constexpr(sizeof...(Args) == 0u) { + return stl::forward_as_tuple(stl::get(payload).emplace_back()...); + } else { + return stl::forward_as_tuple(stl::get(payload).emplace_back(stl::forward(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(payload).erase(stl::get(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(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 operator[](const size_type pos) const { + ENTT_ASSERT(pos < size(), "Index out of bounds"); + return stl::forward_as_tuple(stl::get(payload)[pos]...); + } + + /*! @copydoc operator[] */ + [[nodiscard]] stl::tuple operator[](const size_type pos) { + ENTT_ASSERT(pos < size(), "Index out of bounds"); + return stl::forward_as_tuple(stl::get(payload)[pos]...); + } + + /*! @brief Clears a table. */ + void clear() { + (stl::get(payload).clear(), ...); + } + +private: + container_type payload; +}; + +} // namespace entt + +/*! @cond ENTT_INTERNAL */ +#include + +namespace std { + +template +struct uses_allocator, Allocator> + : entt::stl::bool_constant<(entt::stl::uses_allocator_v && ...)> {}; + +} // namespace std +/*! @endcond */ + +#endif diff --git a/include/entt/core/algorithm.hpp b/include/entt/core/algorithm.hpp new file mode 100644 index 0000000..758f0cd --- /dev/null +++ b/include/entt/core/algorithm.hpp @@ -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.
+ * 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... 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)..., 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> + 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 +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 + void operator()(It first, It last, Getter getter = Getter{}) const { + if(first < last) { + constexpr auto passes = N / Bit; + + using value_type = stl::iterator_traits::value_type; + using difference_type = stl::iterator_traits::difference_type; + stl::vector aux(static_cast(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(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 diff --git a/include/entt/core/any.hpp b/include/entt/core/any.hpp new file mode 100644 index 0000000..74e27d6 --- /dev/null +++ b/include/entt/core/any.hpp @@ -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 +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 +struct basic_any_storage<0u, Align> { + static constexpr bool has_buffer = false; + const void *instance{}; +}; + +template +// NOLINTNEXTLINE(bugprone-sizeof-expression) +struct in_situ: stl::bool_constant<(Len != 0u) && alignof(Type) <= Align && sizeof(Type) <= Len && stl::is_nothrow_move_constructible_v> {}; + +template +struct in_situ: 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 +class basic_any: private internal::basic_any_storage { + using request = internal::any_request; + using base_type = internal::basic_any_storage; + using vtable_type = const void *(const request, const basic_any &, const void *); + using deleter_type = void(const basic_any &); + + template + static constexpr bool in_situ_v = internal::in_situ::value; + + template + static const void *basic_vtable(const request req, const basic_any &value, const void *other) { + switch(const auto *elem = static_cast(value.data()); req) { + using enum internal::any_request; + case info: + return &type_id(); + case transfer: + if constexpr(stl::is_move_assignable_v) { + // NOLINTNEXTLINE(bugprone-casting-through-void) + *const_cast(elem) = stl::move(*static_cast(const_cast(other))); + return other; + } + [[fallthrough]]; + case assign: + if constexpr(stl::is_copy_assignable_v) { + *const_cast(elem) = *static_cast(other); + return other; + } + break; + case compare: + if constexpr(!stl::is_function_v && !stl::is_array_v && is_equality_comparable_v) { + return (*elem == *static_cast(other)) ? other : nullptr; + } else { + return (elem == other) ? other : nullptr; + } + case copy: + if constexpr(stl::is_copy_constructible_v) { + // NOLINTNEXTLINE(bugprone-casting-through-void) + static_cast(const_cast(other))->initialize(*elem); + } + break; + case move: + ENTT_ASSERT(value.mode == any_policy::embedded, "Unexpected policy"); + if constexpr(in_situ_v) { + // NOLINTNEXTLINE(bugprone-casting-through-void, bugprone-multi-level-implicit-pointer-conversion) + return ::new(&static_cast(const_cast(other))->buffer) Type{stl::move(*const_cast(elem))}; + } + } + + return nullptr; + } + + template + 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), "Unexpected policy"); + + const auto *elem = static_cast(value.data()); + + if constexpr(in_situ_v) { + (value.mode == any_policy::embedded) ? elem->~Type() : (delete elem); + } else if constexpr(stl::is_array_v) { + delete[] elem; + } else { + delete elem; + } + } + + template + void initialize([[maybe_unused]] Args &&...args) { + using plain_type = stl::remove_cvref_t; + + vtable = basic_vtable; + underlying_type = type_hash::value(); + + if constexpr(stl::is_void_v) { + deleter = nullptr; + mode = any_policy::empty; + this->instance = nullptr; + } else if constexpr(stl::is_lvalue_reference_v) { + deleter = nullptr; + mode = stl::is_const_v> ? any_policy::cref : any_policy::ref; + static_assert((stl::is_lvalue_reference_v && ...) && (sizeof...(Args) == 1u), "Invalid arguments"); + // NOLINTNEXTLINE(bugprone-multi-level-implicit-pointer-conversion) + this->instance = (stl::addressof(args), ...); + } else if constexpr(in_situ_v) { + if constexpr(stl::is_trivially_destructible_v) { + deleter = nullptr; + } else { + deleter = &basic_deleter; + } + + mode = any_policy::embedded; + + if constexpr(stl::is_aggregate_v && (sizeof...(Args) != 0u || !stl::is_default_constructible_v)) { + ::new(&this->buffer) plain_type{stl::forward(args)...}; + } else { + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay) + ::new(&this->buffer) plain_type(stl::forward(args)...); + } + } else { + deleter = &basic_deleter; + mode = any_policy::dynamic; + + if constexpr(stl::is_aggregate_v && (sizeof...(Args) != 0u || !stl::is_default_constructible_v)) { + this->instance = new plain_type{stl::forward(args)...}; + } else if constexpr(stl::is_array_v) { + static_assert(sizeof...(Args) == 0u, "Invalid arguments"); + this->instance = new plain_type[stl::extent_v](); + } else { + this->instance = new plain_type(stl::forward(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} {} + + /** + * @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 + explicit basic_any(stl::in_place_type_t, Args &&...args) + : base_type{} { + initialize(stl::forward(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 + requires (!stl::is_const_v && !stl::is_void_v) + explicit basic_any(stl::in_place_t, Type *value) + : base_type{} { + if(value == nullptr) { + initialize(); + } else { + initialize(*value); + deleter = &basic_deleter; + 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 + requires (!stl::same_as, basic_any>) + basic_any(Type &&value) + : basic_any{stl::in_place_type>, stl::forward(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(); + } + } + + 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 + requires (!stl::same_as, basic_any>) + basic_any &operator=(Type &&value) { + emplace>(stl::forward(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 + [[nodiscard]] bool has_value() const noexcept { + return (underlying_type == type_hash::value()); + } + + /** + * @brief Returns the object type info if any, `type_id()` otherwise. + * @return The object type info if any, `type_id()` otherwise. + */ + [[nodiscard]] const type_info &info() const noexcept { + return *static_cast(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 + [[nodiscard]] const Type *data() const noexcept { + return has_value>() ? static_cast(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(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(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 + [[nodiscard]] Type *data() noexcept { + if constexpr(stl::is_const_v) { + return stl::as_const(*this).template data>(); + } else { + return (mode == any_policy::cref) ? nullptr : const_cast(stl::as_const(*this).template data>()); + } + } + + /** + * @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 + void emplace(Args &&...args) { + invoke_deleter_if_exists(); + initialize(stl::forward(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(); + } + + /** + * @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 +[[nodiscard]] stl::remove_const_t any_cast(const basic_any &data) noexcept { + const auto *const instance = any_cast>(&data); + ENTT_ASSERT(instance, "Invalid instance"); + return static_cast(*instance); +} + +/*! @copydoc any_cast */ +template +[[nodiscard]] stl::remove_const_t any_cast(basic_any &data) noexcept { + // forces const on non-reference types to make them work also with wrappers for const references + auto *const instance = any_cast>(&data); + ENTT_ASSERT(instance, "Invalid instance"); + return static_cast(*instance); +} + +/*! @copydoc any_cast */ +template +// NOLINTNEXTLINE(cppcoreguidelines-rvalue-reference-param-not-moved) +[[nodiscard]] stl::remove_const_t any_cast(basic_any &&data) noexcept { + if constexpr(stl::is_copy_constructible_v>) { + if(auto *const instance = any_cast>(&data); instance) { + return static_cast(stl::move(*instance)); + } + + return any_cast(data); + } else { + auto *const instance = any_cast>(&data); + ENTT_ASSERT(instance, "Invalid instance"); + return static_cast(stl::move(*instance)); + } +} + +/*! @copydoc any_cast */ +template +[[nodiscard]] const Type *any_cast(const basic_any *data) noexcept { + return data->template data>(); +} + +/*! @copydoc any_cast */ +template +[[nodiscard]] Type *any_cast(basic_any *data) noexcept { + if constexpr(stl::is_const_v) { + // last attempt to make wrappers for const references return their values + return any_cast(&stl::as_const(*data)); + } else { + return data->template data(); + } +} + +/** + * @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::length, stl::size_t Align = basic_any::alignment, typename... Args> +[[nodiscard]] basic_any make_any(Args &&...args) { + return basic_any{stl::in_place_type, stl::forward(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::length, stl::size_t Align = basic_any::alignment, typename Type> +[[nodiscard]] basic_any forward_as_any(Type &&value) { + return basic_any{stl::in_place_type, stl::forward(value)}; +} + +} // namespace entt + +#endif diff --git a/include/entt/core/bit.hpp b/include/entt/core/bit.hpp new file mode 100644 index 0000000..d0698f2 --- /dev/null +++ b/include/entt/core/bit.hpp @@ -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 +[[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(value & (mod - 1u)); +} + +} // namespace entt + +#endif diff --git a/include/entt/core/compressed_pair.hpp b/include/entt/core/compressed_pair.hpp new file mode 100644 index 0000000..9f45225 --- /dev/null +++ b/include/entt/core/compressed_pair.hpp @@ -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 +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) + requires stl::default_initializable {} + + template + constexpr compressed_pair_element(Arg &&arg) noexcept(stl::is_nothrow_constructible_v) + requires (!stl::same_as, compressed_pair_element>) + : value{stl::forward(arg)} {} + + template + constexpr compressed_pair_element(stl::tuple args, stl::index_sequence) noexcept(stl::is_nothrow_constructible_v) + : value{stl::forward(stl::get(args))...} {} + + [[nodiscard]] constexpr reference get() noexcept { + return value; + } + + [[nodiscard]] constexpr const_reference get() const noexcept { + return value; + } + +private: + Type value{}; +}; + +template +requires is_ebco_eligible_v +struct compressed_pair_element: Type { + using reference = Type &; + using const_reference = const Type &; + using base_type = Type; + + constexpr compressed_pair_element() noexcept(stl::is_nothrow_default_constructible_v) + requires stl::default_initializable + : base_type{} {} + + template + constexpr compressed_pair_element(Arg &&arg) noexcept(stl::is_nothrow_constructible_v) + requires (!stl::same_as, compressed_pair_element>) + : base_type{stl::forward(arg)} {} + + template + constexpr compressed_pair_element(stl::tuple args, stl::index_sequence) noexcept(stl::is_nothrow_constructible_v) + : base_type{stl::forward(stl::get(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 +class compressed_pair final + : internal::compressed_pair_element, + internal::compressed_pair_element { + using first_base = internal::compressed_pair_element; + using second_base = internal::compressed_pair_element; + +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 && stl::is_nothrow_default_constructible_v) + requires stl::default_initializable && stl::default_initializable + : 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 + constexpr compressed_pair(Arg &&arg, Other &&other) noexcept(stl::is_nothrow_constructible_v && stl::is_nothrow_constructible_v) + : first_base{stl::forward(arg)}, + second_base{stl::forward(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 + constexpr compressed_pair(stl::piecewise_construct_t, stl::tuple args, stl::tuple other) noexcept(stl::is_nothrow_constructible_v && stl::is_nothrow_constructible_v) + : first_base{stl::move(args), stl::index_sequence_for{}}, + second_base{stl::move(other), stl::index_sequence_for{}} {} + + /*! @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(*this).get(); + } + + /*! @copydoc first */ + [[nodiscard]] constexpr const first_type &first() const noexcept { + return static_cast(*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(*this).get(); + } + + /*! @copydoc second */ + [[nodiscard]] constexpr const second_type &second() const noexcept { + return static_cast(*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 + requires (Index <= 1u) + [[nodiscard]] constexpr decltype(auto) get() noexcept { + if constexpr(Index == 0u) { + return first(); + } else { + return second(); + } + } + + /*! @copydoc get */ + template + 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 +compressed_pair(Type &&, Other &&) -> compressed_pair, stl::decay_t>; + +/** + * @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 +constexpr void swap(compressed_pair &lhs, compressed_pair &rhs) noexcept { + lhs.swap(rhs); +} + +} // namespace entt + +/*! @cond ENTT_INTERNAL */ +#include + +namespace std { + +template +struct tuple_size>: integral_constant {}; + +template +requires (Index <= 1u) +struct tuple_element>: conditional {}; + +} // namespace std +/*! @endcond */ + +#endif diff --git a/include/entt/core/concepts.hpp b/include/entt/core/concepts.hpp new file mode 100644 index 0000000..a8bb989 --- /dev/null +++ b/include/entt/core/concepts.hpp @@ -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 +concept cvref_unqualified = stl::is_same_v, Type>; + +} // namespace entt + +#endif diff --git a/include/entt/core/enum.hpp b/include/entt/core/enum.hpp new file mode 100644 index 0000000..ed1b093 --- /dev/null +++ b/include/entt/core/enum.hpp @@ -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 +struct enum_as_bitmask: stl::false_type {}; + +/*! @copydoc enum_as_bitmask */ +template +requires requires { + requires stl::is_enum_v; + { Type::_entt_enum_as_bitmask } -> stl::same_as; +} +struct enum_as_bitmask: stl::true_type {}; + +/** + * @brief Helper variable template. + * @tparam Type The enum class type for which to enable bitmask support. + */ +template +inline constexpr bool enum_as_bitmask_v = enum_as_bitmask::value; + +/** + * @brief Specifies that an enum class supports bitmask operations. + * @tparam Type Enum class type. + */ +template +// check again that it is an enum to deal with incorrect specializations +concept enum_bitmask = stl::is_enum_v && enum_as_bitmask_v; + +} // 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 +[[nodiscard]] constexpr Type operator|(const Type lhs, const Type rhs) noexcept { + return static_cast(static_cast>(lhs) | static_cast>(rhs)); +} + +/*! @copydoc operator| */ +template +[[nodiscard]] constexpr Type operator&(const Type lhs, const Type rhs) noexcept { + return static_cast(static_cast>(lhs) & static_cast>(rhs)); +} + +/*! @copydoc operator| */ +template +[[nodiscard]] constexpr Type operator^(const Type lhs, const Type rhs) noexcept { + return static_cast(static_cast>(lhs) ^ static_cast>(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 +[[nodiscard]] constexpr Type operator~(const Type value) noexcept { + return static_cast(~static_cast>(value)); +} + +/*! @copydoc operator~ */ +template +[[nodiscard]] constexpr bool operator!(const Type value) noexcept { + return !static_cast>(value); +} + +/*! @copydoc operator| */ +template +constexpr Type &operator|=(Type &lhs, const Type rhs) noexcept { + return (lhs = (lhs | rhs)); +} + +/*! @copydoc operator| */ +template +constexpr Type &operator&=(Type &lhs, const Type rhs) noexcept { + return (lhs = (lhs & rhs)); +} + +/*! @copydoc operator| */ +template +constexpr Type &operator^=(Type &lhs, const Type rhs) noexcept { + return (lhs = (lhs ^ rhs)); +} + +#endif diff --git a/include/entt/core/family.hpp b/include/entt/core/family.hpp new file mode 100644 index 0000000..953c12d --- /dev/null +++ b/include/entt/core/family.hpp @@ -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 +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 + // 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 diff --git a/include/entt/core/fwd.hpp b/include/entt/core/fwd.hpp new file mode 100644 index 0000000..c454e8e --- /dev/null +++ b/include/entt/core/fwd.hpp @@ -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 +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 +class compressed_pair; + +template +class basic_hashed_string; + +/*! @brief Aliases for common character types. */ +using hashed_string = basic_hashed_string; + +/*! @brief Aliases for common character types. */ +using hashed_wstring = basic_hashed_string; + +// NOLINTNEXTLINE(bugprone-forward-declaration-namespace) +struct type_info; + +} // namespace entt + +#endif diff --git a/include/entt/core/hashed_string.hpp b/include/entt/core/hashed_string.hpp new file mode 100644 index 0000000..f3398af --- /dev/null +++ b/include/entt/core/hashed_string.hpp @@ -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 +struct fnv_1a_params; + +template<> +struct fnv_1a_params { + static constexpr auto offset = 2166136261; + static constexpr auto prime = 16777619; +}; + +template<> +struct fnv_1a_params { + static constexpr auto offset = 14695981039346656037ull; + static constexpr auto prime = 1099511628211ull; +}; + +template +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.
+ * 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 +class basic_hashed_string: internal::basic_hashed_string { + using base_type = internal::basic_hashed_string; + 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 + // 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(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 + // 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(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(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 +basic_hashed_string(const Char *str, stl::size_t len) -> basic_hashed_string; + +/** + * @brief Deduction guide. + * @tparam Char Character type. + * @tparam N Number of characters of the identifier. + * @param str Human-readable identifier. + */ +template +// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays, modernize-avoid-c-arrays) +basic_hashed_string(const Char (&str)[N]) -> basic_hashed_string; + +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 diff --git a/include/entt/core/ident.hpp b/include/entt/core/ident.hpp new file mode 100644 index 0000000..3413393 --- /dev/null +++ b/include/entt/core/ident.hpp @@ -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 +class ident { + template + [[nodiscard]] static ENTT_CONSTEVAL id_type get(stl::index_sequence) noexcept { + return (0 + ... + (stl::is_same_v...>>> ? 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 + requires (stl::is_same_v, Type> || ...) + static constexpr value_type value = get>(stl::index_sequence_for{}); +}; + +} // namespace entt + +#endif diff --git a/include/entt/core/iterator.hpp b/include/entt/core/iterator.hpp new file mode 100644 index 0000000..f389e23 --- /dev/null +++ b/include/entt/core/iterator.hpp @@ -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 +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{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 +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 Sentinel = It> +struct iterable_adaptor final { + /*! @brief Value type. */ + using value_type = stl::iterator_traits::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 && stl::is_nothrow_default_constructible_v) + : 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 && stl::is_nothrow_move_constructible_v) + : 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 diff --git a/include/entt/core/memory.hpp b/include/entt/core/memory.hpp new file mode 100644 index 0000000..4ec18aa --- /dev/null +++ b/include/entt/core/memory.hpp @@ -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 +constexpr void propagate_on_container_copy_assignment([[maybe_unused]] Allocator &lhs, [[maybe_unused]] Allocator &rhs) noexcept { + if constexpr(stl::allocator_traits::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 +constexpr void propagate_on_container_move_assignment([[maybe_unused]] Allocator &lhs, [[maybe_unused]] Allocator &rhs) noexcept { + if constexpr(stl::allocator_traits::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 +constexpr void propagate_on_container_swap([[maybe_unused]] Allocator &lhs, [[maybe_unused]] Allocator &rhs) noexcept { + if constexpr(stl::allocator_traits::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 +struct allocation_deleter: private Allocator { + /*! @brief Allocator type. */ + using allocator_type = Allocator; + /*! @brief Pointer type. */ + using pointer = stl::allocator_traits::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{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) { + using alloc_traits = stl::allocator_traits; + 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 +constexpr auto allocate_unique(Allocator &allocator, Args &&...args) { + static_assert(!stl::is_array_v, "Array types are not supported"); + + using alloc_traits = stl::allocator_traits::template rebind_traits; + 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)...); + } + ENTT_CATCH { + alloc_traits::deallocate(alloc, ptr, 1u); + ENTT_THROW; + } + + return stl::unique_ptr>{ptr, alloc}; +} + +/*! @cond ENTT_INTERNAL */ +namespace internal { + +template +struct uses_allocator_construction { + template + static constexpr auto args([[maybe_unused]] const Allocator &allocator, Params &&...params) noexcept { + if constexpr(!stl::uses_allocator_v && stl::is_constructible_v) { + return stl::forward_as_tuple(stl::forward(params)...); + } else { + static_assert(stl::uses_allocator_v, "Ill-formed request"); + + if constexpr(stl::is_constructible_v) { + return stl::tuple{stl::allocator_arg, allocator, stl::forward(params)...}; + } else { + static_assert(stl::is_constructible_v, "Ill-formed request"); + return stl::forward_as_tuple(stl::forward(params)..., allocator); + } + } + } +}; + +template +struct uses_allocator_construction> { + using type = stl::pair; + + template + 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::args(allocator, stl::forward(curr)...); }, stl::forward(first)), + stl::apply([&allocator](auto &&...curr) { return uses_allocator_construction::args(allocator, stl::forward(curr)...); }, stl::forward(second))); + } + + static constexpr auto args(const auto &allocator) noexcept { + return uses_allocator_construction::args(allocator, stl::piecewise_construct, stl::tuple<>{}, stl::tuple<>{}); + } + + template + static constexpr auto args(const auto &allocator, First &&first, Second &&second) noexcept { + return uses_allocator_construction::args(allocator, stl::piecewise_construct, stl::forward_as_tuple(stl::forward(first)), stl::forward_as_tuple(stl::forward(second))); + } + + template + static constexpr auto args(const auto &allocator, const stl::pair &value) noexcept { + return uses_allocator_construction::args(allocator, stl::piecewise_construct, stl::forward_as_tuple(value.first), stl::forward_as_tuple(value.second)); + } + + template + static constexpr auto args(const auto &allocator, stl::pair &&value) noexcept { + return uses_allocator_construction::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 +constexpr auto uses_allocator_construction_args(const auto &allocator, Args &&...args) noexcept { + return internal::uses_allocator_construction::args(allocator, stl::forward(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 +constexpr Type make_obj_using_allocator(const auto &allocator, Args &&...args) { + return stl::make_from_tuple(internal::uses_allocator_construction::args(allocator, stl::forward(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 +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(curr)...); }, internal::uses_allocator_construction::args(allocator, stl::forward(args)...)); +} + +} // namespace entt + +#endif diff --git a/include/entt/core/monostate.hpp b/include/entt/core/monostate.hpp new file mode 100644 index 0000000..4116bda --- /dev/null +++ b/include/entt/core/monostate.hpp @@ -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.
+ * 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 +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 + monostate &operator=(Type val) noexcept { + value = 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 + operator Type() const noexcept { + return value; + } + +private: + template + // 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 +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) +inline monostate monostate_v{}; + +} // namespace entt + +#endif diff --git a/include/entt/core/ranges.hpp b/include/entt/core/ranges.hpp new file mode 100644 index 0000000..255d7d7 --- /dev/null +++ b/include/entt/core/ranges.hpp @@ -0,0 +1,22 @@ +#ifndef ENTT_CORE_RANGES_HPP +#define ENTT_CORE_RANGES_HPP + +#include + +#if defined(__cpp_lib_ranges) +# include +# include "iterator.hpp" + +namespace std::ranges { + +template +inline constexpr bool enable_borrowed_range>{true}; + +template +inline constexpr bool enable_view>{true}; + +} // namespace std::ranges + +#endif + +#endif diff --git a/include/entt/core/tuple.hpp b/include/entt/core/tuple.hpp new file mode 100644 index 0000000..fa55ad0 --- /dev/null +++ b/include/entt/core/tuple.hpp @@ -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 +struct is_tuple: stl::false_type {}; + +/** + * @copybrief is_tuple + * @tparam Args Tuple template arguments. + */ +template +struct is_tuple>: stl::true_type {}; + +/** + * @brief Helper variable template. + * @tparam Type The type to test. + */ +template +inline constexpr bool is_tuple_v = is_tuple::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 +constexpr decltype(auto) unwrap_tuple(Type &&value) noexcept { + if constexpr(stl::tuple_size_v> == 1u) { + return stl::get<0>(stl::forward(value)); + } else { + return stl::forward(value); + } +} + +/** + * @brief Utility class to forward-and-apply tuple objects. + * @tparam Func Type of underlying invocable object. + */ +template +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 + constexpr forward_apply(Args &&...args) noexcept(stl::is_nothrow_constructible_v) + : Func{stl::forward(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 + constexpr decltype(auto) operator()(Type &&args) noexcept(noexcept(stl::apply(stl::declval(), args))) { + return stl::apply(static_cast(*this), stl::forward(args)); + } + + /*! @copydoc operator()() */ + template + constexpr decltype(auto) operator()(Type &&args) const noexcept(noexcept(stl::apply(stl::declval(), args))) { + return stl::apply(static_cast(*this), stl::forward(args)); + } +}; + +/** + * @brief Deduction guide. + * @tparam Func Type of underlying invocable object. + */ +template +forward_apply(Func) -> forward_apply>; + +} // namespace entt + +#endif diff --git a/include/entt/core/type_info.hpp b/include/entt/core/type_info.hpp new file mode 100644 index 0000000..1c251ab --- /dev/null +++ b/include/entt/core/type_info.hpp @@ -0,0 +1,232 @@ +#ifndef ENTT_CORE_TYPE_INFO_HPP +#define ENTT_CORE_TYPE_INFO_HPP + +#include +#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 +[[nodiscard]] constexpr const char *pretty_function() noexcept { +#if defined ENTT_PRETTY_FUNCTION + return static_cast(ENTT_PRETTY_FUNCTION); +#else + return ""; +#endif +} + +template +[[nodiscard]] constexpr auto stripped_type_name() noexcept { +#if defined ENTT_PRETTY_FUNCTION + const stl::string_view full_name{pretty_function()}; + 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().find_first_of('.')> +[[nodiscard]] ENTT_CONSTEVAL stl::string_view type_name(int) noexcept { + constexpr auto value = stripped_type_name(); + return value; +} + +template +[[nodiscard]] stl::string_view type_name(char) noexcept { + static const auto value = stripped_type_name(); + return value; +} + +template().find_first_of('.')> +[[nodiscard]] ENTT_CONSTEVAL id_type type_hash(int) noexcept { + constexpr auto stripped = stripped_type_name(); + constexpr auto value = hashed_string::value(stripped.data(), stripped.size()); + return value; +} + +template +[[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()); + return value; +} + +} // namespace internal +/*! @endcond */ + +/** + * @brief Type sequential identifier. + * @tparam Type Type for which to generate a sequential identifier. + */ +template +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 +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(0); +#else + [[nodiscard]] static constexpr id_type value() noexcept { + return type_index::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 +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(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 + // NOLINTBEGIN(modernize-use-transparent-functors) + constexpr type_info(stl::in_place_type_t) noexcept + : seq{type_index>::value()}, + identifier{type_hash>::value()}, + alias{type_name>::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.
+ * 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 +[[nodiscard]] const type_info &type_id() noexcept { + if constexpr(stl::is_same_v>) { + static const type_info instance{stl::in_place_type}; + return instance; + } else { + return type_id>(); + } +} + +/*! @copydoc type_id */ +template +[[nodiscard]] const type_info &type_id(const Type &) noexcept { + return type_id>(); +} + +} // namespace entt + +#endif diff --git a/include/entt/core/type_traits.hpp b/include/entt/core/type_traits.hpp new file mode 100644 index 0000000..2c63a61 --- /dev/null +++ b/include/entt/core/type_traits.hpp @@ -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 +struct choice_t + // unfortunately, doxygen cannot parse such a construct + : /*! @cond ENTT_INTERNAL */ choice_t /*! @endcond */ +{}; + +/*! @copybrief choice_t */ +template<> +struct choice_t<0> {}; + +/** + * @brief Variable template for the choice trick. + * @tparam N Number of choices available. + */ +template +inline constexpr choice_t choice{}; + +/** + * @brief A type-only `sizeof` wrapper that returns 0 where `sizeof` complains. + * @tparam Type The type of which to return the size. + */ +template +struct size_of: stl::integral_constant {}; + +/*! @copydoc size_of */ +template +requires requires { sizeof(Type); } +struct size_of + // NOLINTNEXTLINE(bugprone-sizeof-expression) + : stl::integral_constant {}; + +/** + * @brief Helper variable template. + * @tparam Type The type of which to return the size. + */ +template +inline constexpr stl::size_t size_of_v = size_of::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 +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 +inline constexpr auto unpack_as_value = Value; + +/** + * @brief Wraps a static constant. + * @tparam Value A static constant. + */ +template +using integral_constant = stl::integral_constant; + +/** + * @brief Alias template to facilitate the creation of named values. + * @tparam Value A constant value at least convertible to `id_type`. + */ +template +using tag = integral_constant; + +/** + * @brief A class to use to push around lists of types, nothing more. + * @tparam Type Types provided by the type list. + */ +template +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 +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 +struct type_list_element> + : type_list_element> {}; + +/** + * @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 +struct type_list_element<0u, type_list> { + /*! @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 +using type_list_element_t = type_list_element::type; + +/*! @brief Primary template isn't defined on purpose. */ +template +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 +struct type_list_index> { + /*! @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>::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 +requires (type_list_index>::value == sizeof...(Other)) +struct type_list_index> { + /*! @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 +struct type_list_index> { + /*! @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 +inline constexpr stl::size_t type_list_index_v = type_list_index::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 +ENTT_CONSTEVAL type_list operator+(type_list, type_list) { + return {}; +} + +/*! @brief Primary template isn't defined on purpose. */ +template +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 +struct type_list_cat, type_list, List...> { + /*! @brief A type list composed by the types of all the type lists. */ + using type = type_list_cat, List...>::type; +}; + +/** + * @brief Concatenates multiple type lists. + * @tparam Type Types provided by the type list. + */ +template +struct type_list_cat> { + /*! @brief A type list composed by the types of all the type lists. */ + using type = type_list; +}; + +/** + * @brief Helper type. + * @tparam List Type lists to concatenate. + */ +template +using type_list_cat_t = type_list_cat::type; + +/*! @cond ENTT_INTERNAL */ +namespace internal { + +template +struct type_list_unique; + +template +struct type_list_unique, Type...> + : stl::conditional_t<(stl::is_same_v || ...), type_list_unique, Type...>, type_list_unique, Type..., First>> {}; + +template +struct type_list_unique, Type...> { + using type = type_list; +}; + +} // namespace internal +/*! @endcond */ + +/** + * @brief Removes duplicates types from a type list. + * @tparam List Type list. + */ +template +struct type_list_unique { + /*! @brief A type list without duplicate types. */ + using type = internal::type_list_unique::type; +}; + +/** + * @brief Helper type. + * @tparam List Type list. + */ +template +using type_list_unique_t = type_list_unique::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 +struct type_list_contains; + +/** + * @copybrief type_list_contains + * @tparam Type Types provided by the type list. + * @tparam Other Type to look for. + */ +template +struct type_list_contains, Other> + : stl::bool_constant<(stl::is_same_v || ...)> {}; + +/** + * @brief Helper variable template. + * @tparam List Type list. + * @tparam Type Type to look for. + */ +template +inline constexpr bool type_list_contains_v = type_list_contains::value; + +/*! @brief Primary template isn't defined on purpose. */ +template +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 +struct type_list_diff, type_list> { + /*! @brief A type list that is the difference between the two type lists. */ + using type = type_list_cat_t, Type>, type_list<>, type_list>...>; +}; + +/** + * @brief Helper type. + * @tparam List Type lists between which to compute the difference. + */ +template +using type_list_diff_t = type_list_diff::type; + +/*! @brief Primary template isn't defined on purpose. */ +template 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 class Op> +struct type_list_transform, Op> { + /*! @brief Resulting type list after applying the transform function. */ + // NOLINTNEXTLINE(modernize-type-traits) + using type = type_list::type...>; +}; + +/** + * @brief Helper type. + * @tparam List Type list. + * @tparam Op Unary operation as template class with a type member named `type`. + */ +template class Op> +using type_list_transform_t = type_list_transform::type; + +/** + * @brief A class to use to push around lists of constant values, nothing more. + * @tparam Value Values provided by the value list. + */ +template +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 +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 +struct value_list_element> + : value_list_element> {}; + +/** + * @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 +struct value_list_element<0u, value_list> { + /*! @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 +using value_list_element_t = value_list_element::type; + +/** + * @brief Helper type. + * @tparam Index Index of the value to return. + * @tparam List Value list to search into. + */ +template +inline constexpr auto value_list_element_v = value_list_element::value; + +/*! @brief Primary template isn't defined on purpose. */ +template +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 +struct value_list_index> { + /*! @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; +}; + +/** + * @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 +requires (value_list_index>::value == sizeof...(Other)) +struct value_list_index> { + /*! @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 +struct value_list_index> { + /*! @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 +inline constexpr stl::size_t value_list_index_v = value_list_index::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 +ENTT_CONSTEVAL value_list operator+(value_list, value_list) { + return {}; +} + +/*! @brief Primary template isn't defined on purpose. */ +template +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 +struct value_list_cat, value_list, List...> { + /*! @brief A value list composed by the values of all the value lists. */ + using type = value_list_cat, List...>::type; +}; + +/** + * @brief Concatenates multiple value lists. + * @tparam Value Values provided by the value list. + */ +template +struct value_list_cat> { + /*! @brief A value list composed by the values of all the value lists. */ + using type = value_list; +}; + +/** + * @brief Helper type. + * @tparam List Value lists to concatenate. + */ +template +using value_list_cat_t = value_list_cat::type; + +/*! @brief Primary template isn't defined on purpose. */ +template +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 +struct value_list_unique> { + /*! @brief A value list without duplicate types. */ + using type = stl::conditional_t< + ((Value == Other) || ...), + typename value_list_unique>::type, + value_list_cat_t, typename value_list_unique>::type>>; +}; + +/*! @brief Removes duplicates values from a value list. */ +template<> +struct value_list_unique> { + /*! @brief A value list without duplicate types. */ + using type = value_list<>; +}; + +/** + * @brief Helper type. + * @tparam Type A value list. + */ +template +using value_list_unique_t = value_list_unique::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 +struct value_list_contains; + +/** + * @copybrief value_list_contains + * @tparam Value Values provided by the value list. + * @tparam Other Value to look for. + */ +template +struct value_list_contains, Other> + : stl::bool_constant<((Value == Other) || ...)> {}; + +/** + * @brief Helper variable template. + * @tparam List Value list. + * @tparam Value Value to look for. + */ +template +inline constexpr bool value_list_contains_v = value_list_contains::value; + +/*! @brief Primary template isn't defined on purpose. */ +template +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 +struct value_list_diff, value_list> { + /*! @brief A value list that is the difference between the two lists. */ + using type = value_list_cat_t, Value>, value_list<>, value_list>...>; +}; + +/** + * @brief Helper type. + * @tparam List Value lists between which to compute the difference. + */ +template +using value_list_diff_t = value_list_diff::type; + +/*! @brief Same as stl::is_invocable, but with tuples. */ +template +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 class Tuple, typename... Args> +struct is_applicable>: stl::is_invocable {}; + +/** + * @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 class Tuple, typename... Args> +struct is_applicable>: stl::is_invocable {}; + +/** + * @brief Helper variable template. + * @tparam Func A valid function type. + * @tparam Args The list of arguments to use to probe the function type. + */ +template +inline constexpr bool is_applicable_v = is_applicable::value; + +/*! @brief Same as stl::is_invocable_r, but with tuples for arguments. */ +template +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 +struct is_applicable_r>: stl::is_invocable_r {}; + +/** + * @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 +inline constexpr bool is_applicable_r_v = is_applicable_r::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 +struct is_complete: stl::false_type {}; + +/*! @copydoc is_complete */ +template +requires requires { sizeof(Type); } +struct is_complete: stl::true_type {}; + +/** + * @brief Helper variable template. + * @tparam Type The type to test. + */ +template +inline constexpr bool is_complete_v = is_complete::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 +struct is_iterator: stl::false_type {}; + +/*! @cond ENTT_INTERNAL */ +namespace internal { + +template +struct has_iterator_category: stl::false_type {}; + +template +requires requires { typename stl::iterator_traits::iterator_category; } +struct has_iterator_category: stl::true_type {}; + +} // namespace internal +/*! @endcond */ + +/*! @copydoc is_iterator */ +template +requires (!stl::is_void_v>>) +struct is_iterator: internal::has_iterator_category {}; + +/** + * @brief Helper variable template. + * @tparam Type The type to test. + */ +template +inline constexpr bool is_iterator_v = is_iterator::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 +struct is_ebco_eligible: stl::bool_constant && !stl::is_final_v> {}; + +/** + * @brief Helper variable template. + * @tparam Type The type to test. + */ +template +inline constexpr bool is_ebco_eligible_v = is_ebco_eligible::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 +struct is_transparent: stl::false_type {}; + +/*! @copydoc is_transparent */ +template +requires requires { typename Type::is_transparent; } +struct is_transparent: stl::true_type {}; + +/** + * @brief Helper variable template. + * @tparam Type The type to test. + */ +template +inline constexpr bool is_transparent_v = is_transparent::value; + +/*! @cond ENTT_INTERNAL */ +namespace internal { + +template +struct has_tuple_size_value: stl::false_type {}; + +template +requires is_complete_v> +struct has_tuple_size_value: stl::true_type {}; + +template +struct has_value_type: stl::false_type {}; + +template +requires requires { typename Type::value_type; } +struct has_value_type: stl::true_type {}; + +template +[[nodiscard]] ENTT_CONSTEVAL bool dispatch_is_equality_comparable(); + +template +[[nodiscard]] ENTT_CONSTEVAL bool unpack_maybe_equality_comparable(stl::index_sequence) { + return (dispatch_is_equality_comparable>() && ...); +} + +template +[[nodiscard]] ENTT_CONSTEVAL bool maybe_equality_comparable(char) { + return false; +} + +template +[[nodiscard]] ENTT_CONSTEVAL auto maybe_equality_comparable(int) -> decltype(stl::declval() == stl::declval()) { + return true; +} + +template +[[nodiscard]] ENTT_CONSTEVAL bool dispatch_is_equality_comparable() { + // NOLINTBEGIN(modernize-use-transparent-functors) + if constexpr(stl::is_array_v) { + return false; + } else if constexpr(is_complete_v>>) { + if constexpr(has_tuple_size_value::value) { + return maybe_equality_comparable(0) && unpack_maybe_equality_comparable(stl::make_index_sequence::value>{}); + } else { + return maybe_equality_comparable(0); + } + } else if constexpr(has_value_type::value) { + if constexpr(is_iterator_v || stl::is_same_v || dispatch_is_equality_comparable()) { + return maybe_equality_comparable(0); + } else { + return false; + } + } else { + return maybe_equality_comparable(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 +struct is_equality_comparable: stl::bool_constant()> {}; + +/*! @copydoc is_equality_comparable */ +template +struct is_equality_comparable: is_equality_comparable {}; + +/** + * @brief Helper variable template. + * @tparam Type The type to test. + */ +template +inline constexpr bool is_equality_comparable_v = is_equality_comparable::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 +struct constness_as { + /*! @brief The type resulting from the transcription of the constness. */ + using type = stl::remove_const_t; +}; + +/*! @copydoc constness_as */ +template +struct constness_as { + /*! @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 +using constness_as_t = constness_as::type; + +/*! @brief Primary template isn't defined on purpose. */ +template +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 +requires stl::is_member_pointer_v +class member_class { + template + static Class *clazz(Ret (Class::*)(Args...)); + + template + static Class *clazz(Ret (Class::*)(Args...) const); + + template + static Class *clazz(Type Class::*); + +public: + /*! @brief The class of the given non-static member object or function. */ + using type = stl::remove_pointer_t()))>; +}; + +/** + * @brief Helper type. + * @tparam Member A pointer to a non-static member object or function. + */ +template +using member_class_t = member_class::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 +class nth_argument { + template + static ENTT_CONSTEVAL type_list pick_up(Ret (*)(Args...)); + + template + static ENTT_CONSTEVAL type_list pick_up(Ret (Class ::*)(Args...)); + + template + static ENTT_CONSTEVAL type_list pick_up(Ret (Class ::*)(Args...) const); + + template + static ENTT_CONSTEVAL type_list pick_up(Type Class ::*); + + template + 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()))>; +}; + +/** + * @brief Helper type. + * @tparam Index The index of the argument to extract. + * @tparam Candidate A valid function, member function or data member type. + */ +template +using nth_argument_t = nth_argument::type; + +} // namespace entt + +template +struct entt::stl::tuple_size>: entt::stl::integral_constant::size> {}; + +template +struct entt::stl::tuple_element>: entt::type_list_element> {}; + +template +struct entt::stl::tuple_size>: entt::stl::integral_constant::size> {}; + +template +struct entt::stl::tuple_element>: entt::value_list_element> {}; + +#endif diff --git a/include/entt/core/utility.hpp b/include/entt/core/utility.hpp new file mode 100644 index 0000000..b7e50c4 --- /dev/null +++ b/include/entt/core/utility.hpp @@ -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 +[[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 +[[nodiscard]] constexpr auto overload(Func *func) noexcept { + return func; +} + +/** + * @brief Helper type for visitors. + * @tparam Func Types of function objects. + */ +template +struct overloaded: Func... { + using Func::operator()...; +}; + +/** + * @brief Deduction guide. + * @tparam Func Types of function objects. + */ +template +overloaded(Func...) -> overloaded; + +/** + * @brief Basic implementation of a y-combinator. + * @tparam Func Type of a potentially recursive function. + */ +template +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{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 + constexpr decltype(auto) operator()(Args &&...args) const noexcept(stl::is_nothrow_invocable_v) { + return func(*this, stl::forward(args)...); + } + + /*! @copydoc operator()() */ + template + constexpr decltype(auto) operator()(Args &&...args) noexcept(stl::is_nothrow_invocable_v) { + return func(*this, stl::forward(args)...); + } + +private: + Func func; +}; + +} // namespace entt + +#endif diff --git a/include/entt/entity/component.hpp b/include/entt/entity/component.hpp new file mode 100644 index 0000000..6730fc3 --- /dev/null +++ b/include/entt/entity/component.hpp @@ -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 +struct in_place_delete: stl::bool_constant && stl::is_move_assignable_v)> {}; + +template<> +struct in_place_delete: stl::false_type {}; + +template +requires Type::in_place_delete +struct in_place_delete: stl::true_type {}; + +template +struct page_size: stl::integral_constant * ENTT_PACKED_PAGE> {}; + +template<> +struct page_size: stl::integral_constant {}; + +template +requires stl::is_convertible_v +struct page_size: stl::integral_constant {}; + +} // namespace internal +/*! @endcond */ + +/** + * @brief Common way to access various properties of components. + * @tparam Type Element type. + * @tparam Entity A valid entity type. + */ +template +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::value; + /*! @brief Page size, default is `ENTT_PACKED_PAGE` for non-empty types. */ + static constexpr stl::size_t page_size = internal::page_size::value; +}; + +} // namespace entt + +#endif diff --git a/include/entt/entity/entity.hpp b/include/entt/entity/entity.hpp new file mode 100644 index 0000000..111574a --- /dev/null +++ b/include/entt/entity/entity.hpp @@ -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 +struct entt_traits; + +template +requires requires { + requires stl::is_enum_v; + typename internal::entt_traits>::value_type; +} +struct entt_traits: entt_traits> { + using value_type = Type; +}; + +template +requires requires { typename Type::entity_type; } +struct entt_traits + : entt_traits { + using value_type = Type; +}; + +template<> +struct entt_traits { + 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 { + 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 +concept entity_like = requires { + typename internal::entt_traits::value_type; +}; + +/** + * @brief Common basic entity traits implementation. + * @tparam Traits Actual entity traits to use. + */ +template +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(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(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(vers + (vers == version_mask))); + } + + /** + * @brief Constructs an identifier from its parts. + * + * If the version part is not provided, a tombstone is returned.
+ * 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(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 +struct entt_traits: basic_entt_traits> { + /*! @brief Base type. */ + using base_type = basic_entt_traits>; + /*! @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 +[[nodiscard]] constexpr entt_traits::entity_type to_integral(const Entity value) noexcept { + return entt_traits::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 +[[nodiscard]] constexpr entt_traits::entity_type to_entity(const Entity value) noexcept { + return entt_traits::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 +[[nodiscard]] constexpr entt_traits::version_type to_version(const Entity value) noexcept { + return entt_traits::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 + [[nodiscard]] constexpr operator Entity() const noexcept { + using traits_type = entt_traits; + 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 + [[nodiscard]] constexpr bool operator==(const Entity entity) const noexcept { + using traits_type = entt_traits; + 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 + [[nodiscard]] constexpr operator Entity() const noexcept { + using traits_type = entt_traits; + 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 + [[nodiscard]] constexpr bool operator==(const Entity entity) const noexcept { + using traits_type = entt_traits; + + 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 diff --git a/include/entt/entity/fwd.hpp b/include/entt/entity/fwd.hpp new file mode 100644 index 0000000..f1d1a89 --- /dev/null +++ b/include/entt/entity/fwd.hpp @@ -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 +struct component_traits; + +template> +class basic_sparse_set; + +template> +class basic_storage; + +template +class basic_sigh_mixin; + +template +class basic_reactive_mixin; + +template> +class basic_registry; + +template +class basic_view; + +template> +class basic_runtime_view; + +template +class basic_group; + +template +class basic_organizer; + +template +class basic_handle; + +template +class basic_snapshot; + +template +class basic_snapshot_loader; + +template +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 +using storage = basic_storage; + +/** + * @brief Alias declaration for the most common use case. + * @tparam Type Underlying storage type. + */ +template +using sigh_mixin = basic_sigh_mixin>; + +/** + * @brief Alias declaration for the most common use case. + * @tparam Type Underlying storage type. + */ +template +using reactive_mixin = basic_reactive_mixin>; + +/*! @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; + +/*! @brief Alias declaration for the most common use case. */ +using handle = basic_handle; + +/*! @brief Alias declaration for the most common use case. */ +using const_handle = basic_handle; + +/** + * @brief Alias declaration for the most common use case. + * @tparam Args Other template parameters. + */ +template +using handle_view = basic_handle; + +/** + * @brief Alias declaration for the most common use case. + * @tparam Args Other template parameters. + */ +template +using const_handle_view = basic_handle; + +/*! @brief Alias declaration for the most common use case. */ +using snapshot = basic_snapshot; + +/*! @brief Alias declaration for the most common use case. */ +using snapshot_loader = basic_snapshot_loader; + +/*! @brief Alias declaration for the most common use case. */ +using continuous_loader = basic_continuous_loader; + +/*! @brief Alias declaration for the most common use case. */ +using runtime_view = basic_runtime_view; + +/*! @brief Alias declaration for the most common use case. */ +using const_runtime_view = basic_runtime_view; + +/** + * @brief Alias for exclusion lists. + * @tparam Type List of types. + */ +template +struct exclude_t final: type_list { + /*! @brief Default constructor. */ + explicit ENTT_CONSTEVAL exclude_t() = default; +}; + +/** + * @brief Variable template for exclusion lists. + * @tparam Type List of types. + */ +template +inline constexpr exclude_t exclude{}; + +/** + * @brief Alias for lists of observed elements. + * @tparam Type List of types. + */ +template +struct get_t final: type_list { + /*! @brief Default constructor. */ + explicit ENTT_CONSTEVAL get_t() = default; +}; + +/** + * @brief Variable template for lists of observed elements. + * @tparam Type List of types. + */ +template +inline constexpr get_t get{}; + +/** + * @brief Alias for lists of owned elements. + * @tparam Type List of types. + */ +template +struct owned_t final: type_list { + /*! @brief Default constructor. */ + explicit ENTT_CONSTEVAL owned_t() = default; +}; + +/** + * @brief Variable template for lists of owned elements. + * @tparam Type List of types. + */ +template +inline constexpr owned_t 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 class Op> +struct type_list_transform, Op> { + /*! @brief Resulting get list after applying the transform function. */ + using type = get_t::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 class Op> +struct type_list_transform, Op> { + /*! @brief Resulting exclude list after applying the transform function. */ + using type = exclude_t::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 class Op> +struct type_list_transform, Op> { + /*! @brief Resulting owned list after applying the transform function. */ + using type = owned_t::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> +struct storage_type { + /*! @brief Type-to-storage conversion result. */ + using type = ENTT_STORAGE(sigh_mixin, basic_storage); +}; + +/*! @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 +struct storage_type { + /*! @brief Type-to-storage conversion result. */ + using type = ENTT_STORAGE(reactive_mixin, basic_storage); +}; + +/** + * @brief Helper type. + * @tparam Args Arguments to forward. + */ +template +using storage_type_t = storage_type::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>> +struct storage_for { + /*! @brief Type-to-storage conversion result. */ + using type = constness_as_t, Entity, Allocator>, Type>; +}; + +/** + * @brief Helper type. + * @tparam Args Arguments to forward. + */ +template +using storage_for_t = storage_for::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> +using view = basic_view, type_list_transform_t>; + +/** + * @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 Exclude = exclude_t<>> +using group = basic_group, type_list_transform_t, type_list_transform_t>; + +} // namespace entt + +#endif diff --git a/include/entt/entity/group.hpp b/include/entt/entity/group.hpp new file mode 100644 index 0000000..dcb1714 --- /dev/null +++ b/include/entt/entity/group.hpp @@ -0,0 +1,1052 @@ +#ifndef ENTT_ENTITY_GROUP_HPP +#define ENTT_ENTITY_GROUP_HPP + +#include "../config/config.h" +#include "../core/algorithm.hpp" +#include "../core/fwd.hpp" +#include "../core/iterator.hpp" +#include "../core/type_info.hpp" +#include "../core/type_traits.hpp" +#include "../stl/array.hpp" +#include "../stl/concepts.hpp" +#include "../stl/cstddef.hpp" +#include "../stl/iterator.hpp" +#include "../stl/tuple.hpp" +#include "../stl/type_traits.hpp" +#include "../stl/utility.hpp" +#include "entity.hpp" +#include "fwd.hpp" + +namespace entt { + +/*! @cond ENTT_INTERNAL */ +namespace internal { + +template +class extended_group_iterator; + +template +class extended_group_iterator, get_t> { + template + [[nodiscard]] auto index_to_element([[maybe_unused]] Type &cpool) const { + if constexpr(stl::is_void_v) { + return stl::make_tuple(); + } else { + return stl::forward_as_tuple(cpool.rbegin()[it.index()]); + } + } + +public: + using iterator_type = It; + using value_type = decltype(stl::tuple_cat(stl::make_tuple(*stl::declval()), stl::declval().get_as_tuple({})..., stl::declval().get_as_tuple({})...)); + using pointer = input_iterator_pointer; + using reference = value_type; + using difference_type = stl::ptrdiff_t; + using iterator_category = stl::input_iterator_tag; + using iterator_concept = stl::forward_iterator_tag; + + constexpr extended_group_iterator() + : it{}, + pools{} {} + + extended_group_iterator(iterator_type from, stl::tuple cpools) + : it{from}, + pools{stl::move(cpools)} {} + + extended_group_iterator &operator++() noexcept { + return ++it, *this; + } + + extended_group_iterator operator++(int) noexcept { + const extended_group_iterator orig = *this; + return ++(*this), orig; + } + + [[nodiscard]] reference operator*() const noexcept { + return stl::tuple_cat(stl::make_tuple(*it), index_to_element(*stl::get(pools))..., stl::get(pools)->get_as_tuple(*it)...); + } + + [[nodiscard]] pointer operator->() const noexcept { + return operator*(); + } + + [[nodiscard]] constexpr iterator_type base() const noexcept { + return it; + } + + template + [[nodiscard]] constexpr bool operator==(const extended_group_iterator &other) const noexcept { + return it == other.it; + } + +private: + It it; + stl::tuple pools; +}; + +struct group_descriptor { + using size_type = stl::size_t; + virtual ~group_descriptor() = default; + [[nodiscard]] virtual bool owned(const id_type) const noexcept { + return false; + } +}; + +template +class group_handler final: public group_descriptor { + using entity_type = Type::entity_type; + + void swap_elements(const stl::size_t pos, const entity_type entt) { + for(size_type next{}; next < Owned; ++next) { + pools[next]->swap_elements((*pools[next])[pos], entt); + } + } + + void push_on_construct(const entity_type entt) { + if(stl::apply([entt, pos = len](auto *cpool, auto *...other) { return cpool->contains(entt) && !(cpool->index(entt) < pos) && (other->contains(entt) && ...); }, pools) + && stl::apply([entt](auto *...cpool) { return (!cpool->contains(entt) && ...); }, filter)) { + swap_elements(len++, entt); + } + } + + void push_on_destroy(const entity_type entt) { + if(stl::apply([entt, pos = len](auto *cpool, auto *...other) { return cpool->contains(entt) && !(cpool->index(entt) < pos) && (other->contains(entt) && ...); }, pools) + && stl::apply([entt](auto *...cpool) { return (0u + ... + cpool->contains(entt)) == 1u; }, filter)) { + swap_elements(len++, entt); + } + } + + void remove_if(const entity_type entt) { + if(pools[0u]->contains(entt) && (pools[0u]->index(entt) < len)) { + swap_elements(--len, entt); + } + } + + void common_setup() { + // we cannot iterate backwards because we want to leave behind valid entities in case of owned types + for(auto first = pools[0u]->rbegin(), last = first + static_cast(pools[0u]->size()); first != last; ++first) { + push_on_construct(*first); + } + } + +public: + using common_type = Type; + using size_type = Type::size_type; + + template + group_handler(stl::tuple ogpool, stl::tuple epool) + : pools{stl::apply([](auto &&...cpool) { return stl::array{&cpool...}; }, ogpool)}, + filter{stl::apply([](auto &&...cpool) { return stl::array{&cpool...}; }, epool)} { + stl::apply([this](auto &...cpool) { ((cpool.on_construct().template connect<&group_handler::push_on_construct>(*this), cpool.on_destroy().template connect<&group_handler::remove_if>(*this)), ...); }, ogpool); + stl::apply([this](auto &...cpool) { ((cpool.on_construct().template connect<&group_handler::remove_if>(*this), cpool.on_destroy().template connect<&group_handler::push_on_destroy>(*this)), ...); }, epool); + common_setup(); + } + + [[nodiscard]] bool owned(const id_type hash) const noexcept override { + for(size_type pos{}; pos < Owned; ++pos) { + if(pools[pos]->info().hash() == hash) { + return true; + } + } + + return false; + } + + [[nodiscard]] size_type length() const noexcept { + return len; + } + + template + [[nodiscard]] common_type *storage() const noexcept { + if constexpr(Index < (Owned + Get)) { + return pools[Index]; + } else { + return filter[Index - (Owned + Get)]; + } + } + +private: + stl::array pools; + stl::array filter; + stl::size_t len{}; +}; + +template +class group_handler final: public group_descriptor { + using entity_type = Type::entity_type; + + void push_on_construct(const entity_type entt) { + if(!elem.contains(entt) + && stl::apply([entt](auto *...cpool) { return (cpool->contains(entt) && ...); }, pools) + && stl::apply([entt](auto *...cpool) { return (!cpool->contains(entt) && ...); }, filter)) { + elem.push(entt); + } + } + + void push_on_destroy(const entity_type entt) { + if(!elem.contains(entt) + && stl::apply([entt](auto *...cpool) { return (cpool->contains(entt) && ...); }, pools) + && stl::apply([entt](auto *...cpool) { return (0u + ... + cpool->contains(entt)) == 1u; }, filter)) { + elem.push(entt); + } + } + + void remove_if(const entity_type entt) { + elem.remove(entt); + } + + void common_setup() { + for(const auto entity: *pools[0u]) { + push_on_construct(entity); + } + } + +public: + using common_type = Type; + + template + group_handler(const Allocator &allocator, stl::tuple gpool, stl::tuple epool) + : pools{stl::apply([](auto &&...cpool) { return stl::array{&cpool...}; }, gpool)}, + filter{stl::apply([](auto &&...cpool) { return stl::array{&cpool...}; }, epool)}, + elem{allocator} { + stl::apply([this](auto &...cpool) { ((cpool.on_construct().template connect<&group_handler::push_on_construct>(*this), cpool.on_destroy().template connect<&group_handler::remove_if>(*this)), ...); }, gpool); + stl::apply([this](auto &...cpool) { ((cpool.on_construct().template connect<&group_handler::remove_if>(*this), cpool.on_destroy().template connect<&group_handler::push_on_destroy>(*this)), ...); }, epool); + common_setup(); + } + + [[nodiscard]] common_type &handle() noexcept { + return elem; + } + + [[nodiscard]] const common_type &handle() const noexcept { + return elem; + } + + template + [[nodiscard]] common_type *storage() const noexcept { + if constexpr(Index < Get) { + return pools[Index]; + } else { + return filter[Index - Get]; + } + } + +private: + stl::array pools; + stl::array filter; + common_type elem; +}; + +} // namespace internal +/*! @endcond */ + +/** + * @brief Group. + * + * Primary template isn't defined on purpose. All the specializations give a + * compile-time error, but for a few reasonable cases. + */ +template +class basic_group; + +/** + * @brief Non-owning group. + * + * A non-owning group returns all entities and only the entities that are at + * least in the given storage. Moreover, it's guaranteed that the entity list is + * tightly packed in memory for fast iterations. + * + * @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 pools iterated by the group in any way + * invalidates all the iterators. + * + * @tparam Get Types of storage _observed_ by the group. + * @tparam Exclude Types of storage used to filter the group. + */ +template +class basic_group, get_t, exclude_t> { + using base_type = stl::common_type_t; + using underlying_type = base_type::entity_type; + + template + static constexpr stl::size_t index_of = type_list_index_v, type_list>; + + template + [[nodiscard]] auto pools_for(stl::index_sequence) const noexcept { + using return_type = stl::tuple; + return descriptor ? return_type{static_cast(descriptor->template storage())...} : return_type{}; + } + +public: + /*! @brief Underlying entity identifier. */ + using entity_type = underlying_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 = base_type; + /*! @brief Random access iterator type. */ + using iterator = common_type::iterator; + /*! @brief Reverse iterator type. */ + using reverse_iterator = common_type::reverse_iterator; + /*! @brief Iterable group type. */ + using iterable = iterable_adaptor, get_t>>; + /*! @brief Group handler type. */ + using handler = internal::group_handler; + + /** + * @brief Group opaque identifier. + * @return Group opaque identifier. + */ + static id_type group_id() noexcept { + return type_hash, get_t...>, exclude_t...>>>::value(); + } + + /*! @brief Default constructor to use to create empty, invalid groups. */ + basic_group() noexcept + : descriptor{} {} + + /** + * @brief Constructs a group from a set of storage classes. + * @param ref A reference to a group handler. + */ + basic_group(handler &ref) noexcept + : descriptor{&ref} {} + + /** + * @brief Returns the leading storage of a group. + * @return The leading storage of the group. + */ + [[nodiscard]] const common_type &handle() const noexcept { + return descriptor->handle(); + } + + /** + * @brief Returns the storage for a given element type, if any. + * @tparam Type Type of element of which to return the storage. + * @return The storage for the given element type. + */ + template + [[nodiscard]] auto *storage() const noexcept { + return storage>(); + } + + /** + * @brief Returns the storage for a given index, if any. + * @tparam Index Index of the storage to return. + * @return The storage for the given index. + */ + template + [[nodiscard]] auto *storage() const noexcept { + using type = type_list_element_t>; + return *this ? static_cast(descriptor->template storage()) : nullptr; + } + + /** + * @brief Returns the number of entities that are part of the group. + * @return Number of entities that are part of the group. + */ + [[nodiscard]] size_type size() const noexcept { + return *this ? handle().size() : size_type{}; + } + + /** + * @brief Returns the number of elements that a group has currently + * allocated space for. + * @return Capacity of the group. + */ + [[nodiscard]] size_type capacity() const noexcept { + return *this ? handle().capacity() : size_type{}; + } + + /*! @brief Requests the removal of unused capacity. */ + void shrink_to_fit() { + if(*this) { + descriptor->handle().shrink_to_fit(); + } + } + + /** + * @brief Checks whether a group is empty. + * @return True if the group is empty, false otherwise. + */ + [[nodiscard]] bool empty() const noexcept { + return !*this || handle().empty(); + } + + /** + * @brief Returns an iterator to the first entity of the group. + * + * If the group is empty, the returned iterator will be equal to `end()`. + * + * @return An iterator to the first entity of the group. + */ + [[nodiscard]] iterator begin() const noexcept { + return *this ? handle().begin() : iterator{}; + } + + /** + * @brief Returns an iterator that is past the last entity of the group. + * @return An iterator to the entity following the last entity of the + * group. + */ + [[nodiscard]] iterator end() const noexcept { + return *this ? handle().end() : iterator{}; + } + + /** + * @brief Returns an iterator to the first entity of the reversed group. + * + * If the group is empty, the returned iterator will be equal to `rend()`. + * + * @return An iterator to the first entity of the reversed group. + */ + [[nodiscard]] reverse_iterator rbegin() const noexcept { + return *this ? handle().rbegin() : reverse_iterator{}; + } + + /** + * @brief Returns an iterator that is past the last entity of the reversed + * group. + * @return An iterator to the entity following the last entity of the + * reversed group. + */ + [[nodiscard]] reverse_iterator rend() const noexcept { + return *this ? handle().rend() : reverse_iterator{}; + } + + /** + * @brief Returns the first entity of the group, if any. + * @return The first entity of the group if one exists, the null entity + * otherwise. + */ + [[nodiscard]] entity_type front() const noexcept { + const auto it = begin(); + return it != end() ? *it : null; + } + + /** + * @brief Returns the last entity of the group, if any. + * @return The last entity of the group if one exists, the null entity + * otherwise. + */ + [[nodiscard]] entity_type back() const noexcept { + const auto it = rbegin(); + return it != rend() ? *it : null; + } + + /** + * @brief Finds an entity. + * @param entt A valid identifier. + * @return An iterator to the given entity if it's found, past the end + * iterator otherwise. + */ + [[nodiscard]] iterator find(const entity_type entt) const noexcept { + return *this ? handle().find(entt) : iterator{}; + } + + /** + * @brief Returns the identifier that occupies the given position. + * @param pos Position of the element to return. + * @return The identifier that occupies the given position. + */ + [[nodiscard]] entity_type operator[](const size_type pos) const { + return begin()[static_cast(pos)]; + } + + /** + * @brief Checks if a group is properly initialized. + * @return True if the group is properly initialized, false otherwise. + */ + [[nodiscard]] explicit operator bool() const noexcept { + return descriptor != nullptr; + } + + /** + * @brief Checks if a group contains an entity. + * @param entt A valid identifier. + * @return True if the group contains the given entity, false otherwise. + */ + [[nodiscard]] bool contains(const entity_type entt) const noexcept { + return *this && handle().contains(entt); + } + + /** + * @brief Returns the elements assigned to the given entity. + * @tparam Type Type of the element to get. + * @tparam Other Other types of elements to get. + * @param entt A valid identifier. + * @return The elements assigned to the entity. + */ + template + [[nodiscard]] decltype(auto) get(const entity_type entt) const { + return get, index_of...>(entt); + } + + /** + * @brief Returns the elements assigned to the given entity. + * @tparam Index Indexes of the elements to get. + * @param entt A valid identifier. + * @return The elements assigned to the entity. + */ + template + [[nodiscard]] decltype(auto) get(const entity_type entt) const { + const auto cpools = pools_for(stl::index_sequence_for{}); + + if constexpr(sizeof...(Index) == 0) { + return stl::apply([entt](auto *...curr) { return stl::tuple_cat(curr->get_as_tuple(entt)...); }, cpools); + } else if constexpr(sizeof...(Index) == 1) { + return (stl::get(cpools)->get(entt), ...); + } else { + return stl::tuple_cat(stl::get(cpools)->get_as_tuple(entt)...); + } + } + + /** + * @brief Iterates entities and elements and applies the given function + * object to them. + * + * The function object is invoked for each entity. It is provided with the + * entity itself and a set of references to non-empty elements. The + * _constness_ of the elements is as requested.
+ * The signature of the function must be equivalent to one of the following + * forms: + * + * @code{.cpp} + * void(const entity_type, Type &...); + * void(Type &...); + * @endcode + * + * @note + * Empty types aren't explicitly instantiated and therefore they are never + * returned during iterations. + * + * @tparam Func Type of the function object to invoke. + * @param func A valid function object. + */ + template + void each(Func func) const { + for(const auto entt: *this) { + if constexpr(is_applicable_v{}, stl::declval().get({})))>) { + stl::apply(func, stl::tuple_cat(stl::make_tuple(entt), get(entt))); + } else { + stl::apply(func, get(entt)); + } + } + } + + /** + * @brief Returns an iterable object to use to _visit_ a group. + * + * The iterable object returns tuples that contain the current entity and a + * set of references to its non-empty elements. The _constness_ of the + * elements is as requested. + * + * @note + * Empty types aren't explicitly instantiated and therefore they are never + * returned during iterations. + * + * @return An iterable object to use to _visit_ the group. + */ + [[nodiscard]] iterable each() const noexcept { + const auto cpools = pools_for(stl::index_sequence_for{}); + return iterable{{begin(), cpools}, {end(), cpools}}; + } + + /** + * @brief Sort a group according to the given comparison function. + * + * The comparison function object must return `true` if the first element + * is _less_ than the second one, `false` otherwise. The signature of the + * comparison function should be equivalent to one of the following: + * + * @code{.cpp} + * bool(stl::tuple, stl::tuple); + * bool(const Type &..., const Type &...); + * bool(const Entity, const Entity); + * @endcode + * + * Where `Type` are such that they are iterated by the group.
+ * Moreover, the comparison function object shall induce a + * _strict weak ordering_ on the values. + * + * The sort function object must offer a member function template + * `operator()` that accepts three arguments: + * + * * An iterator to the first element of the range to sort. + * * An iterator past the last element of the range to sort. + * * A comparison function to use to compare the elements. + * + * @tparam Type Optional type of element to compare. + * @tparam Other Other optional types of elements to compare. + * @tparam Compare Type of comparison function object. + * @tparam Sort Type of sort function object. + * @tparam Args Types of arguments to forward to the sort function object. + * @param compare A valid comparison function object. + * @param algo A valid sort function object. + * @param args Arguments to forward to the sort function object, if any. + */ + template + void sort(Compare compare, Sort algo = Sort{}, Args &&...args) { + sort, index_of...>(stl::move(compare), stl::move(algo), stl::forward(args)...); + } + + /** + * @brief Sort a group according to the given comparison function. + * + * @sa sort + * + * @tparam Index Optional indexes of elements to compare. + * @tparam Compare Type of comparison function object. + * @tparam Sort Type of sort function object. + * @tparam Args Types of arguments to forward to the sort function object. + * @param compare A valid comparison function object. + * @param algo A valid sort function object. + * @param args Arguments to forward to the sort function object, if any. + */ + template + void sort(Compare compare, Sort algo = Sort{}, Args &&...args) { + if(*this) { + if constexpr(sizeof...(Index) == 0) { + static_assert(stl::is_invocable_v, "Invalid comparison function"); + descriptor->handle().sort(stl::move(compare), stl::move(algo), stl::forward(args)...); + } else { + auto comp = [&compare, cpools = pools_for(stl::index_sequence_for{})](const entity_type lhs, const entity_type rhs) { + if constexpr(sizeof...(Index) == 1) { + return compare((stl::get(cpools)->get(lhs), ...), (stl::get(cpools)->get(rhs), ...)); + } else { + return compare(stl::forward_as_tuple(stl::get(cpools)->get(lhs)...), stl::forward_as_tuple(stl::get(cpools)->get(rhs)...)); + } + }; + + descriptor->handle().sort(stl::move(comp), stl::move(algo), stl::forward(args)...); + } + } + } + + /** + * @brief Sort entities according to their order in a range. + * + * The shared pool of entities and thus its order is affected by the changes + * to each and every pool that it tracks. + * + * @param first An iterator to the first element of the range of entities. + * @param last An iterator past the last element of the range of entities. + */ + void sort_as(stl::input_iterator auto first, stl::input_iterator auto last) const { + if(*this) { + descriptor->handle().sort_as(first, last); + } + } + +private: + handler *descriptor; +}; + +/** + * @brief Owning group. + * + * Owning groups returns all entities and only the entities that are at + * least in the given storage. Moreover: + * + * * It's guaranteed that the entity list is tightly packed in memory for fast + * iterations. + * * It's guaranteed that all elements in the owned storage are tightly packed + * in memory for even faster iterations and to allow direct access. + * * They stay true to the order of the owned storage and all instances have the + * same order in memory. + * + * The more types of storage are owned, the faster it is to iterate a group. + * + * @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 pools iterated by the group in any way + * invalidates all the iterators. + * + * @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 +class basic_group, get_t, exclude_t> { + static_assert(((Owned::storage_policy != deletion_policy::in_place) && ...), "Groups do not support in-place delete"); + + using base_type = stl::common_type_t; + using underlying_type = base_type::entity_type; + + template + static constexpr stl::size_t index_of = type_list_index_v, type_list>; + + template + [[nodiscard]] auto pools_for(stl::index_sequence, stl::index_sequence) const noexcept { + using return_type = stl::tuple; + return descriptor ? return_type{static_cast(descriptor->template storage())..., static_cast(descriptor->template storage())...} : return_type{}; + } + +public: + /*! @brief Underlying entity identifier. */ + using entity_type = underlying_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 = base_type; + /*! @brief Random access iterator type. */ + using iterator = common_type::iterator; + /*! @brief Reverse iterator type. */ + using reverse_iterator = common_type::reverse_iterator; + /*! @brief Iterable group type. */ + using iterable = iterable_adaptor, get_t>>; + /*! @brief Group handler type. */ + using handler = internal::group_handler; + + /** + * @brief Group opaque identifier. + * @return Group opaque identifier. + */ + static id_type group_id() noexcept { + return type_hash...>, get_t...>, exclude_t...>>>::value(); + } + + /*! @brief Default constructor to use to create empty, invalid groups. */ + basic_group() noexcept + : descriptor{} {} + + /** + * @brief Constructs a group from a set of storage classes. + * @param ref A reference to a group handler. + */ + basic_group(handler &ref) noexcept + : descriptor{&ref} {} + + /** + * @brief Returns the leading storage of a group. + * @return The leading storage of the group. + */ + [[nodiscard]] const common_type &handle() const noexcept { + return *storage<0>(); + } + + /** + * @brief Returns the storage for a given element type, if any. + * @tparam Type Type of element of which to return the storage. + * @return The storage for the given element type. + */ + template + [[nodiscard]] auto *storage() const noexcept { + return storage>(); + } + + /** + * @brief Returns the storage for a given index, if any. + * @tparam Index Index of the storage to return. + * @return The storage for the given index. + */ + template + [[nodiscard]] auto *storage() const noexcept { + using type = type_list_element_t>; + return *this ? static_cast(descriptor->template storage()) : nullptr; + } + + /** + * @brief Returns the number of entities that that are part of the group. + * @return Number of entities that that are part of the group. + */ + [[nodiscard]] size_type size() const noexcept { + return *this ? descriptor->length() : size_type{}; + } + + /** + * @brief Checks whether a group is empty. + * @return True if the group is empty, false otherwise. + */ + [[nodiscard]] bool empty() const noexcept { + return !*this || !descriptor->length(); + } + + /** + * @brief Returns an iterator to the first entity of the group. + * + * If the group is empty, the returned iterator will be equal to `end()`. + * + * @return An iterator to the first entity of the group. + */ + [[nodiscard]] iterator begin() const noexcept { + return *this ? (handle().end() - static_cast(descriptor->length())) : iterator{}; + } + + /** + * @brief Returns an iterator that is past the last entity of the group. + * @return An iterator to the entity following the last entity of the + * group. + */ + [[nodiscard]] iterator end() const noexcept { + return *this ? handle().end() : iterator{}; + } + + /** + * @brief Returns an iterator to the first entity of the reversed group. + * + * If the group is empty, the returned iterator will be equal to `rend()`. + * + * @return An iterator to the first entity of the reversed group. + */ + [[nodiscard]] reverse_iterator rbegin() const noexcept { + return *this ? handle().rbegin() : reverse_iterator{}; + } + + /** + * @brief Returns an iterator that is past the last entity of the reversed + * group. + * @return An iterator to the entity following the last entity of the + * reversed group. + */ + [[nodiscard]] reverse_iterator rend() const noexcept { + return *this ? (handle().rbegin() + static_cast(descriptor->length())) : reverse_iterator{}; + } + + /** + * @brief Returns the first entity of the group, if any. + * @return The first entity of the group if one exists, the null entity + * otherwise. + */ + [[nodiscard]] entity_type front() const noexcept { + const auto it = begin(); + return it != end() ? *it : null; + } + + /** + * @brief Returns the last entity of the group, if any. + * @return The last entity of the group if one exists, the null entity + * otherwise. + */ + [[nodiscard]] entity_type back() const noexcept { + const auto it = rbegin(); + return it != rend() ? *it : null; + } + + /** + * @brief Finds an entity. + * @param entt A valid identifier. + * @return An iterator to the given entity if it's found, past the end + * iterator otherwise. + */ + [[nodiscard]] iterator find(const entity_type entt) const noexcept { + const auto it = *this ? handle().find(entt) : iterator{}; + return it >= begin() ? it : iterator{}; + } + + /** + * @brief Returns the identifier that occupies the given position. + * @param pos Position of the element to return. + * @return The identifier that occupies the given position. + */ + [[nodiscard]] entity_type operator[](const size_type pos) const { + return begin()[static_cast(pos)]; + } + + /** + * @brief Checks if a group is properly initialized. + * @return True if the group is properly initialized, false otherwise. + */ + [[nodiscard]] explicit operator bool() const noexcept { + return descriptor != nullptr; + } + + /** + * @brief Checks if a group contains an entity. + * @param entt A valid identifier. + * @return True if the group contains the given entity, false otherwise. + */ + [[nodiscard]] bool contains(const entity_type entt) const noexcept { + return *this && handle().contains(entt) && (handle().index(entt) < (descriptor->length())); + } + + /** + * @brief Returns the elements assigned to the given entity. + * @tparam Type Type of the element to get. + * @tparam Other Other types of elements to get. + * @param entt A valid identifier. + * @return The elements assigned to the entity. + */ + template + [[nodiscard]] decltype(auto) get(const entity_type entt) const { + return get, index_of...>(entt); + } + + /** + * @brief Returns the elements assigned to the given entity. + * @tparam Index Indexes of the elements to get. + * @param entt A valid identifier. + * @return The elements assigned to the entity. + */ + template + [[nodiscard]] decltype(auto) get(const entity_type entt) const { + const auto cpools = pools_for(stl::index_sequence_for{}, stl::index_sequence_for{}); + + if constexpr(sizeof...(Index) == 0) { + return stl::apply([entt](auto *...curr) { return stl::tuple_cat(curr->get_as_tuple(entt)...); }, cpools); + } else if constexpr(sizeof...(Index) == 1) { + return (stl::get(cpools)->get(entt), ...); + } else { + return stl::tuple_cat(stl::get(cpools)->get_as_tuple(entt)...); + } + } + + /** + * @brief Iterates entities and elements and applies the given function + * object to them. + * + * The function object is invoked for each entity. It is provided with the + * entity itself and a set of references to non-empty elements. The + * _constness_ of the elements is as requested.
+ * The signature of the function must be equivalent to one of the following + * forms: + * + * @code{.cpp} + * void(const entity_type, Type &...); + * void(Type &...); + * @endcode + * + * @note + * Empty types aren't explicitly instantiated and therefore they are never + * returned during iterations. + * + * @tparam Func Type of the function object to invoke. + * @param func A valid function object. + */ + template + void each(Func func) const { + for(auto args: each()) { + if constexpr(is_applicable_v{}, stl::declval().get({})))>) { + stl::apply(func, args); + } else { + stl::apply([&func](auto, auto &&...less) { func(stl::forward(less)...); }, args); + } + } + } + + /** + * @brief Returns an iterable object to use to _visit_ a group. + * + * The iterable object returns tuples that contain the current entity and a + * set of references to its non-empty elements. The _constness_ of the + * elements is as requested. + * + * @note + * Empty types aren't explicitly instantiated and therefore they are never + * returned during iterations. + * + * @return An iterable object to use to _visit_ the group. + */ + [[nodiscard]] iterable each() const noexcept { + const auto cpools = pools_for(stl::index_sequence_for{}, stl::index_sequence_for{}); + return iterable{{begin(), cpools}, {end(), cpools}}; + } + + /** + * @brief Sort a group according to the given comparison function. + * + * The comparison function object must return `true` if the first element + * is _less_ than the second one, `false` otherwise. The signature of the + * comparison function should be equivalent to one of the following: + * + * @code{.cpp} + * bool(stl::tuple, stl::tuple); + * bool(const Type &, const Type &); + * bool(const Entity, const Entity); + * @endcode + * + * Where `Type` are either owned types or not but still such that they are + * iterated by the group.
+ * Moreover, the comparison function object shall induce a + * _strict weak ordering_ on the values. + * + * The sort function object must offer a member function template + * `operator()` that accepts three arguments: + * + * * An iterator to the first element of the range to sort. + * * An iterator past the last element of the range to sort. + * * A comparison function to use to compare the elements. + * + * @tparam Type Optional type of element to compare. + * @tparam Other Other optional types of elements to compare. + * @tparam Compare Type of comparison function object. + * @tparam Sort Type of sort function object. + * @tparam Args Types of arguments to forward to the sort function object. + * @param compare A valid comparison function object. + * @param algo A valid sort function object. + * @param args Arguments to forward to the sort function object, if any. + */ + template + void sort(Compare compare, Sort algo = Sort{}, Args &&...args) const { + sort, index_of...>(stl::move(compare), stl::move(algo), stl::forward(args)...); + } + + /** + * @brief Sort a group according to the given comparison function. + * + * @sa sort + * + * @tparam Index Optional indexes of elements to compare. + * @tparam Compare Type of comparison function object. + * @tparam Sort Type of sort function object. + * @tparam Args Types of arguments to forward to the sort function object. + * @param compare A valid comparison function object. + * @param algo A valid sort function object. + * @param args Arguments to forward to the sort function object, if any. + */ + template + void sort(Compare compare, Sort algo = Sort{}, Args &&...args) const { + const auto cpools = pools_for(stl::index_sequence_for{}, stl::index_sequence_for{}); + + if constexpr(sizeof...(Index) == 0) { + static_assert(stl::is_invocable_v, "Invalid comparison function"); + storage<0>()->sort_n(descriptor->length(), stl::move(compare), stl::move(algo), stl::forward(args)...); + } else { + auto comp = [&compare, &cpools](const entity_type lhs, const entity_type rhs) { + if constexpr(sizeof...(Index) == 1) { + return compare((stl::get(cpools)->get(lhs), ...), (stl::get(cpools)->get(rhs), ...)); + } else { + return compare(stl::forward_as_tuple(stl::get(cpools)->get(lhs)...), stl::forward_as_tuple(stl::get(cpools)->get(rhs)...)); + } + }; + + storage<0>()->sort_n(descriptor->length(), stl::move(comp), stl::move(algo), stl::forward(args)...); + } + + auto cb = [this](auto *head, auto *...other) { + for(auto next = descriptor->length(); next; --next) { + const auto pos = next - 1; + [[maybe_unused]] const auto entt = head->data()[pos]; + (other->swap_elements(other->data()[pos], entt), ...); + } + }; + + stl::apply(cb, cpools); + } + +private: + handler *descriptor; +}; + +} // namespace entt + +#endif diff --git a/include/entt/entity/handle.hpp b/include/entt/entity/handle.hpp new file mode 100644 index 0000000..a14016f --- /dev/null +++ b/include/entt/entity/handle.hpp @@ -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 +class handle_storage_iterator final { + template + friend class handle_storage_iterator; + + using underlying_type = stl::remove_reference_t; + using entity_type = underlying_type::entity_type; + +public: + using value_type = stl::iterator_traits::value_type; + using pointer = input_iterator_pointer; + 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 + [[nodiscard]] constexpr bool operator==(const handle_storage_iterator &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 +class basic_handle { + using traits_type = entt_traits; + + [[nodiscard]] auto &owner_or_assert() const noexcept { + ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry"); + return static_cast(*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().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.
+ * 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(*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 + // NOLINTNEXTLINE(modernize-use-nodiscard) + decltype(auto) emplace(Args &&...args) const { + static_assert(((sizeof...(Scope) == 0) || ... || stl::is_same_v), "Invalid type"); + return owner_or_assert().template emplace(entt, stl::forward(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 + decltype(auto) emplace_or_replace(Args &&...args) const { + static_assert(((sizeof...(Scope) == 0) || ... || stl::is_same_v), "Invalid type"); + return owner_or_assert().template emplace_or_replace(entt, stl::forward(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 + decltype(auto) patch(Func &&...func) const { + static_assert(((sizeof...(Scope) == 0) || ... || stl::is_same_v), "Invalid type"); + return owner_or_assert().template patch(entt, stl::forward(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 + decltype(auto) replace(Args &&...args) const { + static_assert(((sizeof...(Scope) == 0) || ... || stl::is_same_v), "Invalid type"); + return owner_or_assert().template replace(entt, stl::forward(args)...); + } + + /** + * @brief Removes the given elements from a handle. + * @tparam Type Types of elements to remove. + * @return The number of elements actually removed. + */ + template + // NOLINTNEXTLINE(modernize-use-nodiscard) + size_type remove() const { + static_assert(sizeof...(Scope) == 0 || (type_list_contains_v, Type> && ...), "Invalid type"); + return owner_or_assert().template remove(entt); + } + + /** + * @brief Erases the given elements from a handle. + * @tparam Type Types of elements to erase. + */ + template + void erase() const { + static_assert(sizeof...(Scope) == 0 || (type_list_contains_v, Type> && ...), "Invalid type"); + owner_or_assert().template erase(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 + [[nodiscard]] decltype(auto) all_of() const { + return owner_or_assert().template all_of(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 + [[nodiscard]] decltype(auto) any_of() const { + return owner_or_assert().template any_of(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 + [[nodiscard]] decltype(auto) get() const { + static_assert(sizeof...(Scope) == 0 || (type_list_contains_v, Type> && ...), "Invalid type"); + return owner_or_assert().template get(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 + [[nodiscard]] decltype(auto) get_or_emplace(Args &&...args) const { + static_assert(((sizeof...(Scope) == 0) || ... || stl::is_same_v), "Invalid type"); + return owner_or_assert().template get_or_emplace(entt, stl::forward(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 + [[nodiscard]] auto try_get() const { + static_assert(sizeof...(Scope) == 0 || (type_list_contains_v, Type> && ...), "Invalid type"); + return owner_or_assert().template try_get(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 + [[nodiscard]] bool operator==(const basic_handle &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 + operator basic_handle() const noexcept { + static_assert(stl::is_same_v || stl::is_same_v, Registry>, "Invalid conversion between different handles"); + static_assert((sizeof...(Scope) == 0 || ((sizeof...(Args) != 0 && sizeof...(Args) <= sizeof...(Scope)) && ... && (type_list_contains_v, Args>))), "Invalid conversion between different handles"); + return owner ? basic_handle{*owner, entt} : basic_handle{}; + } + +private: + registry_type *owner; + entity_type entt; +}; + +} // namespace entt + +#endif diff --git a/include/entt/entity/helper.hpp b/include/entt/entity/helper.hpp new file mode 100644 index 0000000..3a89c2f --- /dev/null +++ b/include/entt/entity/helper.hpp @@ -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 +class as_view { + template + [[nodiscard]] auto dispatch(get_t, exclude_t) const { + return reg->template view...>(exclude_t...>{}); + } + +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 + operator basic_view() const { + return dispatch(Get{}, Exclude{}); + } + +private: + registry_type *reg; +}; + +/** + * @brief Converts a registry to a group. + * @tparam Registry Basic registry type. + */ +template +class as_group { + template + [[nodiscard]] auto dispatch(owned_t, get_t, exclude_t) const { + if constexpr(stl::is_const_v) { + return reg->template group_if_exists(get_t{}, exclude_t{}); + } else { + return reg->template group...>(get_t...>{}, exclude_t...>{}); + } + } + +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 + operator basic_group() 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>> +void invoke(Registry ®, const typename Registry::entity_type entt) { + static_assert(stl::is_member_function_pointer_v, "Invalid pointer to non-static member function"); + (reg.template get>(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 +basic_storage::entity_type to_entity(const basic_storage &storage, const typename basic_storage::value_type &instance) { + using traits_type = component_traits::value_type, typename basic_storage::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(traits_type::page_size)) { + return *(static_cast::base_type &>(storage).rbegin() + static_cast(pos) + dist); + } + } + // NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic) + + return null; +} + +/*! @brief Primary template isn't defined on purpose. */ +template +struct sigh_helper; + +/** + * @brief Signal connection helper for registries. + * @tparam Registry Basic registry type. + */ +template +struct sigh_helper { + /*! @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 + auto with(const id_type id = type_hash::value()) noexcept { + return sigh_helper{*bucket, id}; + } + + /** + * @brief Returns a reference to the underlying registry. + * @return A reference to the underlying registry. + */ + [[nodiscard]] registry_type ®istry() 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 +struct sigh_helper final: sigh_helper { + /*! @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::value()) + : sigh_helper{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 on_construct(Args &&...args) { + this->registry().template on_construct(name).template connect(stl::forward(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 on_update(Args &&...args) { + this->registry().template on_update(name).template connect(stl::forward(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 on_destroy(Args &&...args) { + this->registry().template on_destroy(name).template connect(stl::forward(args)...); + return *this; + } + +private: + id_type name; +}; + +/** + * @brief Deduction guide. + * @tparam Registry Basic registry type. + */ +template +sigh_helper(Registry &) -> sigh_helper; + +} // namespace entt + +#endif diff --git a/include/entt/entity/mixin.hpp b/include/entt/entity/mixin.hpp new file mode 100644 index 0000000..d5a15b9 --- /dev/null +++ b/include/entt/entity/mixin.hpp @@ -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 +struct has_on_construct final: stl::false_type {}; + +template +requires stl::invocable +struct has_on_construct: stl::true_type {}; + +template +struct has_on_update final: stl::false_type {}; + +template +requires stl::invocable +struct has_on_update: stl::true_type {}; + +template +struct has_on_destroy final: stl::false_type {}; + +template +requires stl::invocable +struct has_on_destroy: 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); + * @endcode + * + * This applies to all signals made available. + * + * @tparam Type Underlying storage type. + * @tparam Registry Basic registry type. + */ +template +class basic_sigh_mixin final: public Type { + using underlying_type = Type; + using owner_type = Registry; + + using basic_registry_type = basic_registry; + using sigh_type = sigh; + using underlying_iterator = underlying_type::base_type::basic_iterator; + + static_assert(stl::is_base_of_v, "Invalid registry type"); + + [[nodiscard]] auto &owner_or_assert() const noexcept { + ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry"); + return static_cast(*owner); + } + +private: + void pop(underlying_iterator first, underlying_iterator last) final { + if(auto ® = 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 ® = owner_or_assert(); !destruction.empty()) { + if constexpr(stl::is_same_v) { + 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(*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 ® = 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(&value); + + if constexpr(!stl::is_same_v) { + if(owner == nullptr) { + owner = any_cast(&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::value) { + sink{construction}.template connect<&underlying_type::element_type::on_construct>(); + } + + if constexpr(internal::has_on_update::value) { + sink{update}.template connect<&underlying_type::element_type::on_update>(); + } + + if constexpr(internal::has_on_destroy::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(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(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.
+ * 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.
+ * 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.
+ * 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 ®istry() const noexcept { + return owner_or_assert(); + } + + /*! @copydoc registry */ + [[nodiscard]] registry_type ®istry() 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 It> + void generate(It first, It last) { + underlying_type::generate(first, last); + + if(auto ® = 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 + decltype(auto) emplace(const entity_type entt, Args &&...args) { + underlying_type::emplace(entt, stl::forward(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 + decltype(auto) patch(const entity_type entt, Func &&...func) { + underlying_type::patch(entt, stl::forward(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 + 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)...); + + if(auto ® = 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 +class basic_reactive_mixin final: public Type { + using underlying_type = Type; + using owner_type = Registry; + + using alloc_traits = stl::allocator_traits; + using basic_registry_type = basic_registry; + using container_type = stl::vector>; + + static_assert(stl::is_base_of_v, "Invalid registry type"); + + [[nodiscard]] auto &owner_or_assert() const noexcept { + ENTT_ASSERT(owner != nullptr, "Invalid pointer to registry"); + return static_cast(*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(&value); + + if constexpr(!stl::is_same_v) { + if(owner == nullptr) { + owner = any_cast(&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(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(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 + basic_reactive_mixin &on_construct(const id_type id = type_hash::value()) { + auto curr = owner_or_assert().template storage(id).on_construct().template connect(*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 + basic_reactive_mixin &on_update(const id_type id = type_hash::value()) { + auto curr = owner_or_assert().template storage(id).on_update().template connect(*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 + basic_reactive_mixin &on_destroy(const id_type id = type_hash::value()) { + auto curr = owner_or_assert().template storage(id).on_destroy().template connect(*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 ®istry() const noexcept { + return owner_or_assert(); + } + + /*! @copydoc registry */ + [[nodiscard]] registry_type ®istry() 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 + [[nodiscard]] basic_view...>, exclude_t...>> + view(exclude_t = exclude_t{}) const { + const owner_type &parent = owner_or_assert(); + basic_view...>, exclude_t...>> elem{}; + [&elem](const auto *...curr) { ((curr ? elem.storage(*curr) : void()), ...); }(parent.template storage>()..., parent.template storage>()..., this); + return elem; + } + + /*! @copydoc view */ + template + [[nodiscard]] basic_view...>, exclude_t...>> + view(exclude_t = exclude_t{}) { + stl::conditional_t<((stl::is_const_v && ...) && (stl::is_const_v && ...)), const owner_type, owner_type> &parent = owner_or_assert(); + return {*this, parent.template storage>()..., parent.template storage>()...}; + } + + /*! @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 diff --git a/include/entt/entity/organizer.hpp b/include/entt/entity/organizer.hpp new file mode 100644 index 0000000..711d4b4 --- /dev/null +++ b/include/entt/entity/organizer.hpp @@ -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 +struct is_view: stl::false_type {}; + +template +struct is_view>: stl::true_type {}; + +template +inline constexpr bool is_view_v = is_view::value; + +template +struct is_group: stl::false_type {}; + +template +struct is_group>: stl::true_type {}; + +template +inline constexpr bool is_group_v = is_group::value; + +template +struct unpack_type { + using ro = stl::conditional_t< + type_list_contains_v || (stl::is_const_v && !type_list_contains_v>), + type_list>, + type_list<>>; + + using rw = stl::conditional_t< + type_list_contains_v> || (!stl::is_const_v && !type_list_contains_v), + type_list, + type_list<>>; +}; + +template +struct unpack_type, type_list> { + using ro = type_list<>; + using rw = type_list<>; +}; + +template +struct unpack_type, type_list> + : unpack_type, type_list> {}; + +template +struct unpack_type, exclude_t>, type_list> { + using ro = type_list_cat_t, typename unpack_type, type_list>::ro...>; + using rw = type_list_cat_t, type_list>::rw...>; +}; + +template +struct unpack_type, exclude_t>, type_list> + : unpack_type, exclude_t>, type_list> {}; + +template +struct unpack_type, get_t, exclude_t>, type_list> { + using ro = type_list_cat_t, typename unpack_type, type_list>::ro..., typename unpack_type, type_list>::ro...>; + using rw = type_list_cat_t, type_list>::rw..., typename unpack_type, type_list>::rw...>; +}; + +template +struct unpack_type, get_t, exclude_t>, type_list> + : unpack_type, get_t, exclude_t>, type_list> {}; + +template +struct resource_traits; + +template +struct resource_traits, type_list> { + using args = type_list...>; + using ro = type_list_cat_t>::ro..., typename unpack_type>::ro...>; + using rw = type_list_cat_t>::rw..., typename unpack_type>::rw...>; + static constexpr auto sync_point = (stl::is_same_v || ...); +}; + +template +resource_traits...>, type_list> free_function_to_resource_traits(Ret (*)(Args...)); + +template +resource_traits...>, type_list> constrained_function_to_resource_traits(Ret (*)(Type &, Args...)); + +template +resource_traits...>, type_list> constrained_function_to_resource_traits(Ret (Class::*)(Args...)); + +template +resource_traits...>, type_list> 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.
+ * 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 +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 + [[nodiscard]] static decltype(auto) extract(Registry ®) { + if constexpr(stl::is_same_v) { + return reg; + } else if constexpr(internal::is_view_v) { + return static_cast(as_view{reg}); + } else if constexpr(internal::is_group_v) { + return static_cast(as_group{reg}); + } else { + return reg.ctx().template emplace>(); + } + } + + template + [[nodiscard]] static auto to_args(Registry ®, type_list) { + return stl::tuple(reg))...>(extract(reg)...); + } + + template + [[nodiscard]] static stl::size_t fill_dependencies(type_list, [[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()...}; + 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 + void track_dependencies(stl::size_t index, const bool sync_point, type_list, type_list) { + builder.bind(static_cast(index)); + builder.set(type_hash::value(), sync_point || (sizeof...(RO) + sizeof...(RW) == 0u)); + (builder.ro(type_hash::value()), ...); + (builder.rw(type_hash::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 from, stl::vector 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 &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 &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 ®) const { + node.prepare ? node.prepare(reg) : void(); + } + + private: + vertex_data node; + stl::vector in; + stl::vector 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 + void emplace(const char *name = nullptr) { + using resource_type = decltype(internal::free_function_to_resource_traits(Candidate)); + + callback_type *callback = +[](const void *, registry_type ®) { + 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 ®) { void(to_args(reg, typename resource_type::args{})); }, + &type_id>()}; + + 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 + void emplace(Type &value_or_instance, const char *name = nullptr) { + using resource_type = decltype(internal::constrained_function_to_resource_traits(Candidate)); + + callback_type *callback = +[](const void *payload, registry_type ®) { + Type *curr = static_cast(const_cast *>(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 ®) { void(to_args(reg, typename resource_type::args{})); }, + &type_id>()}; + + 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 + void emplace(function_type *func, const void *payload = nullptr, const char *name = nullptr) { + using resource_type = internal::resource_traits, type_list>; + 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()}; + + 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 graph() const { + stl::vector adjacency_list{}; + adjacency_list.reserve(vertices.size()); + + for(auto adjacency_matrix = builder.graph(); auto curr: adjacency_matrix.vertices()) { + stl::vector in{}; + stl::vector 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 vertices; + flow builder; +}; + +} // namespace entt + +#endif diff --git a/include/entt/entity/ranges.hpp b/include/entt/entity/ranges.hpp new file mode 100644 index 0000000..284e5f5 --- /dev/null +++ b/include/entt/entity/ranges.hpp @@ -0,0 +1,28 @@ +#ifndef ENTT_ENTITY_RANGES_HPP +#define ENTT_ENTITY_RANGES_HPP + +#include + +#if defined(__cpp_lib_ranges) +# include +# include "fwd.hpp" + +namespace std::ranges { + +template +inline constexpr bool enable_borrowed_range>{true}; + +template +inline constexpr bool enable_borrowed_range>{true}; + +template +inline constexpr bool enable_view>{true}; + +template +inline constexpr bool enable_view>{true}; + +} // namespace std::ranges + +#endif + +#endif diff --git a/include/entt/entity/registry.hpp b/include/entt/entity/registry.hpp new file mode 100644 index 0000000..4123b5c --- /dev/null +++ b/include/entt/entity/registry.hpp @@ -0,0 +1,1181 @@ +#ifndef ENTT_ENTITY_REGISTRY_HPP +#define ENTT_ENTITY_REGISTRY_HPP + +#include +#include "../config/config.h" +#include "../container/dense_map.hpp" +#include "../core/algorithm.hpp" +#include "../core/any.hpp" +#include "../core/concepts.hpp" +#include "../core/fwd.hpp" +#include "../core/iterator.hpp" +#include "../core/memory.hpp" +#include "../core/type_info.hpp" +#include "../core/type_traits.hpp" +#include "../stl/algorithm.hpp" +#include "../stl/array.hpp" +#include "../stl/concepts.hpp" +#include "../stl/cstddef.hpp" +#include "../stl/functional.hpp" +#include "../stl/iterator.hpp" +#include "../stl/memory.hpp" +#include "../stl/tuple.hpp" +#include "../stl/type_traits.hpp" +#include "../stl/utility.hpp" +#include "entity.hpp" +#include "fwd.hpp" +#include "group.hpp" +#include "mixin.hpp" +#include "sparse_set.hpp" +#include "storage.hpp" +#include "view.hpp" + +namespace entt { + +/*! @cond ENTT_INTERNAL */ +namespace internal { + +template +class registry_storage_iterator final { + template + friend class registry_storage_iterator; + + using mapped_type = stl::remove_reference_t()->second)>; + +public: + using value_type = stl::pair &>; + using pointer = input_iterator_pointer; + using reference = value_type; + using difference_type = stl::ptrdiff_t; + using iterator_category = stl::input_iterator_tag; + using iterator_concept = stl::random_access_iterator_tag; + + constexpr registry_storage_iterator() noexcept + : it{} {} + + constexpr registry_storage_iterator(It iter) noexcept + : it{iter} {} + + template + requires (!stl::same_as && stl::constructible_from) + constexpr registry_storage_iterator(const registry_storage_iterator &other) noexcept + : registry_storage_iterator{other.it} {} + + constexpr registry_storage_iterator &operator++() noexcept { + return ++it, *this; + } + + constexpr registry_storage_iterator operator++(int) noexcept { + const registry_storage_iterator orig = *this; + return ++(*this), orig; + } + + constexpr registry_storage_iterator &operator--() noexcept { + return --it, *this; + } + + constexpr registry_storage_iterator operator--(int) noexcept { + const registry_storage_iterator orig = *this; + return operator--(), orig; + } + + constexpr registry_storage_iterator &operator+=(const difference_type value) noexcept { + it += value; + return *this; + } + + constexpr registry_storage_iterator operator+(const difference_type value) const noexcept { + registry_storage_iterator copy = *this; + return (copy += value); + } + + constexpr registry_storage_iterator &operator-=(const difference_type value) noexcept { + return (*this += -value); + } + + constexpr registry_storage_iterator operator-(const difference_type value) const noexcept { + return (*this + -value); + } + + [[nodiscard]] constexpr reference operator[](const difference_type value) const noexcept { + return {it[value].first, *it[value].second}; + } + + [[nodiscard]] constexpr reference operator*() const noexcept { + return operator[](0); + } + + [[nodiscard]] constexpr pointer operator->() const noexcept { + return operator*(); + } + + template + [[nodiscard]] constexpr stl::ptrdiff_t operator-(const registry_storage_iterator &other) const noexcept { + return it - other.it; + } + + template + [[nodiscard]] constexpr bool operator==(const registry_storage_iterator &other) const noexcept { + return it == other.it; + } + + template + [[nodiscard]] constexpr auto operator<=>(const registry_storage_iterator &other) const noexcept { + return it <=> other.it; + } + +private: + It it; +}; + +template +class registry_context { + using alloc_traits = stl::allocator_traits; + using allocator_type = alloc_traits::template rebind_alloc>>; + +public: + explicit registry_context(const allocator_type &allocator) + : ctx{allocator} {} + + void clear() noexcept { + ctx.clear(); + } + + template + Type &emplace_as(const id_type id, Args &&...args) { + return any_cast(ctx.try_emplace(id, stl::in_place_type, stl::forward(args)...).first->second); + } + + template + Type &emplace(Args &&...args) { + return emplace_as(type_id().hash(), stl::forward(args)...); + } + + template + Type &insert_or_assign(const id_type id, Type &&value) { + return any_cast &>(ctx.insert_or_assign(id, stl::forward(value)).first->second); + } + + template + Type &insert_or_assign(Type &&value) { + return insert_or_assign(type_id().hash(), stl::forward(value)); + } + + template + bool erase(const id_type id = type_id().hash()) { + const auto it = ctx.find(id); + return it != ctx.end() && it->second.info() == type_id() ? (ctx.erase(it), true) : false; + } + + template + [[nodiscard]] const Type &get(const id_type id = type_id().hash()) const { + return any_cast(ctx.at(id)); + } + + template + [[nodiscard]] Type &get(const id_type id = type_id().hash()) { + return any_cast(ctx.at(id)); + } + + template + [[nodiscard]] const Type *find(const id_type id = type_id().hash()) const { + const auto it = ctx.find(id); + return it != ctx.cend() ? any_cast(&it->second) : nullptr; + } + + template + [[nodiscard]] Type *find(const id_type id = type_id().hash()) { + const auto it = ctx.find(id); + return it != ctx.end() ? any_cast(&it->second) : nullptr; + } + + template + [[nodiscard]] bool contains(const id_type id = type_id().hash()) const { + const auto it = ctx.find(id); + return it != ctx.cend() && it->second.info() == type_id(); + } + +private: + dense_map, stl::identity, stl::equal_to<>, allocator_type> ctx; +}; + +} // namespace internal +/*! @endcond */ + +/** + * @brief Fast and reliable entity-component system. + * @tparam Entity A valid entity type. + * @tparam Allocator Type of allocator used to manage memory and elements. + */ +template +class basic_registry { + using base_type = basic_sparse_set; + using alloc_traits = stl::allocator_traits; + static_assert(stl::is_same_v, "Invalid value type"); + // stl::shared_ptr because of its type erased allocator which is useful here + using pool_container_type = dense_map, stl::identity, stl::equal_to<>, typename alloc_traits::template rebind_alloc>>>; + using group_container_type = dense_map, stl::identity, stl::equal_to<>, typename alloc_traits::template rebind_alloc>>>; + using traits_type = entt_traits; + + template + [[nodiscard]] auto &assure([[maybe_unused]] const id_type id = type_hash::value()) { + if constexpr(stl::is_same_v) { + ENTT_ASSERT(id == type_hash::value(), "User entity storage not allowed"); + return entities; + } else { + using storage_type = storage_for_type; + + if(auto it = pools.find(id); it != pools.cend()) { + ENTT_ASSERT(it->second->info() == type_id(), "Unexpected type"); + return static_cast(*it->second); + } + + typename pool_container_type::mapped_type cpool = stl::allocate_shared(get_allocator(), get_allocator()); + pools.emplace(id, cpool); + cpool->bind(*this); + + return static_cast(*cpool); + } + } + + template + [[nodiscard]] const auto *assure([[maybe_unused]] const id_type id = type_hash::value()) const { + if constexpr(stl::is_same_v) { + ENTT_ASSERT(id == type_hash::value(), "User entity storage not allowed"); + return &entities; + } else { + if(const auto it = pools.find(id); it != pools.cend()) { + ENTT_ASSERT(it->second->info() == type_id(), "Unexpected type"); + return static_cast *>(it->second.get()); + } + + return static_cast *>(nullptr); + } + } + + void rebind() { + entities.bind(*this); + + for(auto &&curr: pools) { + curr.second->bind(*this); + } + } + +public: + /*! @brief Allocator type. */ + using allocator_type = Allocator; + /*! @brief Underlying entity identifier. */ + using entity_type = traits_type::value_type; + /*! @brief Underlying version type. */ + using version_type = traits_type::version_type; + /*! @brief Unsigned integer type. */ + using size_type = stl::size_t; + /*! @brief Common type among all storage types. */ + using common_type = base_type; + /*! @brief Context type. */ + using context = internal::registry_context; + /*! @brief Iterable registry type. */ + using iterable = iterable_adaptor>; + /*! @brief Constant iterable registry type. */ + using const_iterable = iterable_adaptor>; + + /** + * @copybrief storage_for + * @tparam Type Storage value type, eventually const. + */ + template + using storage_for_type = storage_for>>::type; + + /*! @brief Default constructor. */ + basic_registry() + : basic_registry{allocator_type{}} {} + + /** + * @brief Constructs an empty registry with a given allocator. + * @param allocator The allocator to use. + */ + explicit basic_registry(const allocator_type &allocator) + : basic_registry{0u, allocator} {} + + /** + * @brief Allocates enough memory upon construction to store `count` pools. + * @param count The number of pools to allocate memory for. + * @param allocator The allocator to use. + */ + basic_registry(const size_type count, const allocator_type &allocator = allocator_type{}) + : vars{allocator}, + pools{allocator}, + groups{allocator}, + entities{allocator} { + pools.reserve(count); + rebind(); + } + + /*! @brief Default copy constructor, deleted on purpose. */ + basic_registry(const basic_registry &) = delete; + + /** + * @brief Move constructor. + * @param other The instance to move from. + */ + basic_registry(basic_registry &&other) noexcept + : vars{stl::move(other.vars)}, + pools{stl::move(other.pools)}, + groups{stl::move(other.groups)}, + entities{stl::move(other.entities)} { + rebind(); + } + + /*! @brief Default destructor. */ + ~basic_registry() = default; + + /** + * @brief Default copy assignment operator, deleted on purpose. + * @return This mixin. + */ + basic_registry &operator=(const basic_registry &) = delete; + + /** + * @brief Move assignment operator. + * @param other The instance to move from. + * @return This registry. + */ + basic_registry &operator=(basic_registry &&other) noexcept { + swap(other); + return *this; + } + + /** + * @brief Exchanges the contents with those of a given registry. + * @param other Registry to exchange the content with. + */ + void swap(basic_registry &other) noexcept { + using stl::swap; + + swap(vars, other.vars); + swap(pools, other.pools); + swap(groups, other.groups); + swap(entities, other.entities); + + rebind(); + other.rebind(); + } + + /** + * @brief Returns the associated allocator. + * @return The associated allocator. + */ + [[nodiscard]] constexpr allocator_type get_allocator() const noexcept { + return entities.get_allocator(); + } + + /** + * @brief Returns an iterable object to use to _visit_ a registry. + * + * The iterable object returns a pair that contains the name and a reference + * to the current storage. + * + * @return An iterable object to use to _visit_ the registry. + */ + [[nodiscard]] iterable storage() noexcept { + return iterable{pools.begin(), pools.end()}; + } + + /*! @copydoc storage */ + [[nodiscard]] const_iterable storage() const noexcept { + return const_iterable{pools.cbegin(), pools.cend()}; + } + + /** + * @brief Finds the storage associated with a given name, if any. + * @param id Name used to map the storage within the registry. + * @return A pointer to the storage if it exists, a null pointer otherwise. + */ + [[nodiscard]] common_type *storage(const id_type id) { + return const_cast(stl::as_const(*this).storage(id)); + } + + /** + * @brief Finds the storage associated with a given name, if any. + * @param id Name used to map the storage within the registry. + * @return A pointer to the storage if it exists, a null pointer otherwise. + */ + [[nodiscard]] const common_type *storage(const id_type id) const { + const auto it = pools.find(id); + return it == pools.cend() ? nullptr : it->second.get(); + } + + /** + * @brief Returns the storage for a given element type. + * @tparam Type Type of element of which to return the storage. + * @param id Optional name used to map the storage within the registry. + * @return The storage for the given element type. + */ + template + storage_for_type &storage(const id_type id = type_hash::value()) { + return assure>(id); + } + + /** + * @brief Returns the storage for a given element type, if any. + * @tparam Type Type of element of which to return the storage. + * @param id Optional name used to map the storage within the registry. + * @return The storage for the given element type. + */ + template + [[nodiscard]] const storage_for_type *storage(const id_type id = type_hash::value()) const { + return assure>(id); + } + + /** + * @brief Discards the storage associated with a given name, if any. + * @param id Name used to map the storage within the registry. + * @return True in case of success, false otherwise. + */ + bool reset(const id_type id) { + ENTT_ASSERT(id != type_hash::value(), "Cannot reset entity storage"); + return !(pools.erase(id) == 0u); + } + + /** + * @brief Checks if an identifier refers to a valid entity. + * @param entt An identifier, either valid or not. + * @return True if the identifier is valid, false otherwise. + */ + [[nodiscard]] bool valid(const entity_type entt) const { + return static_cast(entities.find(entt).index()) < entities.free_list(); + } + + /** + * @brief Returns the actual version for an identifier. + * @param entt A valid identifier. + * @return The version for the given identifier if valid, the tombstone + * version otherwise. + */ + [[nodiscard]] version_type current(const entity_type entt) const { + return entities.current(entt); + } + + /** + * @brief Creates a new entity or recycles a destroyed one. + * @return A valid identifier. + */ + [[nodiscard]] entity_type create() { + return entities.generate(); + } + + /** + * @copybrief create + * + * If the requested entity isn't in use, the suggested identifier is used. + * Otherwise, a new identifier is generated. + * + * @param hint Required identifier. + * @return A valid identifier. + */ + [[nodiscard]] entity_type create(const entity_type hint) { + return entities.generate(hint); + } + + /** + * @brief Assigns each element in a range an identifier. + * + * @sa create + * + * @tparam It Type of output iterator. + * @param first An iterator to the first element of the range to generate. + * @param last An iterator past the last element of the range to generate. + */ + template It> + void create(It first, It last) { + entities.generate(stl::move(first), stl::move(last)); + } + + /** + * @brief Destroys an entity and releases its identifier. + * + * @warning + * Adding or removing elements to an entity that is being destroyed can + * result in undefined behavior. + * + * @param entt A valid identifier. + * @return The version of the recycled entity. + */ + version_type destroy(const entity_type entt) { + for(size_type pos = pools.size(); pos != 0u; --pos) { + pools.begin()[static_cast(pos - 1u)].second->remove(entt); + } + + entities.erase(entt); + return entities.current(entt); + } + + /** + * @brief Destroys an entity and releases its identifier. + * + * The suggested version or the valid version closest to the suggested one + * is used instead of the implicitly generated version. + * + * @sa destroy + * + * @param entt A valid identifier. + * @param version A desired version upon destruction. + * @return The version actually assigned to the entity. + */ + version_type destroy(const entity_type entt, const version_type version) { + destroy(entt); + const auto elem = traits_type::construct(traits_type::to_entity(entt), version); + return entities.bump((elem == tombstone) ? traits_type::next(elem) : elem); + } + + /** + * @brief Destroys all entities in a range and releases their identifiers. + * + * @sa destroy + * + * @param first An iterator to the first element of the range of entities. + * @param last An iterator past the last element of the range of entities. + */ + void destroy(stl::input_iterator auto first, stl::input_iterator auto last) { + const auto to = entities.sort_as(first, last); + const auto from = entities.cend() - static_cast(entities.free_list()); + + for(auto &&curr: pools) { + curr.second->remove(from, to); + } + + entities.erase(from, to); + } + + /** + * @brief Assigns the given element to an entity. + * + * The element must have a proper constructor or be of aggregate type. + * + * @warning + * Attempting to assign an element to an entity that already owns it results + * in undefined behavior. + * + * @tparam Type Type of element to create. + * @tparam Args Types of arguments to use to construct the element. + * @param entt A valid identifier. + * @param args Parameters to use to initialize the element. + * @return A reference to the newly created element. + */ + template + decltype(auto) emplace(const entity_type entt, Args &&...args) { + ENTT_ASSERT(valid(entt), "Invalid entity"); + return assure().emplace(entt, stl::forward(args)...); + } + + /** + * @brief Assigns each entity in a range the given element. + * + * @sa emplace + * + * @tparam Type Type of element to create. + * @param first An iterator to the first element of the range of entities. + * @param last An iterator past the last element of the range of entities. + * @param value An instance of the element to assign. + */ + template + void insert(stl::input_iterator auto first, stl::input_iterator auto last, const Type &value = {}) { + ENTT_ASSERT(stl::all_of(first, last, [this](const auto entt) { return valid(entt); }), "Invalid entity"); + assure().insert(stl::move(first), stl::move(last), value); + } + + /** + * @brief Assigns each entity in a range the given elements. + * + * @sa emplace + * + * @tparam Type Type of element to create. + * @tparam EIt Type of input iterator. + * @tparam CIt Type of input iterator. + * @param first An iterator to the first element of the range of entities. + * @param last An iterator past the last element of the range of entities. + * @param from An iterator to the first element of the range of elements. + */ + template + requires stl::same_as::value_type, Type> + void insert(EIt first, EIt last, CIt from) { + ENTT_ASSERT(stl::all_of(first, last, [this](const auto entt) { return valid(entt); }), "Invalid entity"); + assure().insert(first, last, from); + } + + /** + * @brief Assigns or replaces the given element for an entity. + * + * @sa emplace + * @sa replace + * + * @tparam Type Type of element to assign or replace. + * @tparam Args Types of arguments to use to construct the element. + * @param entt A valid identifier. + * @param args Parameters to use to initialize the element. + * @return A reference to the newly created element. + */ + template + decltype(auto) emplace_or_replace(const entity_type entt, Args &&...args) { + auto &cpool = assure(); + ENTT_ASSERT(valid(entt), "Invalid entity"); + return cpool.contains(entt) ? cpool.patch(entt, [&args...](auto &...curr) { ((curr = Type{stl::forward(args)...}), ...); }) : cpool.emplace(entt, stl::forward(args)...); + } + + /** + * @brief Patches the given element for an entity. + * + * The signature of the function should be equivalent to the following: + * + * @code{.cpp} + * void(Type &); + * @endcode + * + * @warning + * Attempting to patch an element of an entity that doesn't own it results + * in undefined behavior. + * + * @tparam Type Type of element to patch. + * @tparam Func Types of the function objects to invoke. + * @param entt A valid identifier. + * @param func Valid function objects. + * @return A reference to the patched element. + */ + template + decltype(auto) patch(const entity_type entt, Func &&...func) { + return assure().patch(entt, stl::forward(func)...); + } + + /** + * @brief Replaces the given element for an entity. + * + * The element must have a proper constructor or be of aggregate type. + * + * @warning + * Attempting to replace an element of an entity that doesn't own it results + * in undefined behavior. + * + * @tparam Type Type of element to replace. + * @tparam Args Types of arguments to use to construct the element. + * @param entt A valid identifier. + * @param args Parameters to use to initialize the element. + * @return A reference to the element being replaced. + */ + template + decltype(auto) replace(const entity_type entt, Args &&...args) { + return patch(entt, [&args...](auto &...curr) { ((curr = Type{stl::forward(args)...}), ...); }); + } + + /** + * @brief Removes the given elements from an entity. + * @tparam Type Type of element to remove. + * @tparam Other Other types of elements to remove. + * @param entt A valid identifier. + * @return The number of elements actually removed. + */ + template + size_type remove(const entity_type entt) { + return (assure().remove(entt) + ... + assure().remove(entt)); + } + + /** + * @brief Removes the given elements from all the entities in a range. + * + * @sa remove + * + * @tparam Type Type of element to remove. + * @tparam Other Other types of elements to remove. + * @tparam It Type of input iterator. + * @param first An iterator to the first element of the range of entities. + * @param last An iterator past the last element of the range of entities. + * @return The number of elements actually removed. + */ + template + size_type remove(It first, It last) { + size_type count{}; + + if constexpr(stl::is_same_v) { + stl::array cpools{static_cast(&assure()), static_cast(&assure())...}; + + for(auto from = cpools.begin(), to = cpools.end(); from != to; ++from) { + if constexpr(sizeof...(Other) != 0u) { + if((*from)->data() == first.data()) { + stl::swap((*from), cpools.back()); + } + } + + count += (*from)->remove(first, last); + } + + } else { + for(auto cpools = stl::forward_as_tuple(assure(), assure()...); first != last; ++first) { + count += stl::apply([entt = *first](auto &...curr) { return (curr.remove(entt) + ... + 0u); }, cpools); + } + } + + return count; + } + + /** + * @brief Erases the given elements from an entity. + * + * @warning + * Attempting to erase an element from an entity that doesn't own it results + * in undefined behavior. + * + * @tparam Type Types of elements to erase. + * @tparam Other Other types of elements to erase. + * @param entt A valid identifier. + */ + template + void erase(const entity_type entt) { + (assure().erase(entt), (assure().erase(entt), ...)); + } + + /** + * @brief Erases the given elements from all the entities in a range. + * + * @sa erase + * + * @tparam Type Types of elements to erase. + * @tparam Other Other types of elements to erase. + * @tparam It Type of input iterator. + * @param first An iterator to the first element of the range of entities. + * @param last An iterator past the last element of the range of entities. + */ + template + void erase(It first, It last) { + if constexpr(stl::is_same_v) { + stl::array cpools{static_cast(&assure()), static_cast(&assure())...}; + + for(auto from = cpools.begin(), to = cpools.end(); from != to; ++from) { + if constexpr(sizeof...(Other) != 0u) { + if((*from)->data() == first.data()) { + stl::swap(*from, cpools.back()); + } + } + + (*from)->erase(first, last); + } + } else { + for(auto cpools = stl::forward_as_tuple(assure(), assure()...); first != last; ++first) { + stl::apply([entt = *first](auto &...curr) { (curr.erase(entt), ...); }, cpools); + } + } + } + + /** + * @brief Erases elements satisfying specific criteria from an entity. + * + * The function type is equivalent to: + * + * @code{.cpp} + * void(const id_type, typename basic_registry::common_type &); + * @endcode + * + * Only storage where the entity exists are passed to the function. + * + * @tparam Func Type of the function object to invoke. + * @param entt A valid identifier. + * @param func A valid function object. + */ + template + void erase_if(const entity_type entt, Func func) { + for(auto [id, cpool]: storage()) { + if(cpool.contains(entt) && func(id, stl::as_const(cpool))) { + cpool.erase(entt); + } + } + } + + /** + * @brief Removes all tombstones from a registry or only the pools for the + * given elements. + * @tparam Type Types of elements for which to clear all tombstones. + */ + template + void compact() { + if constexpr(sizeof...(Type) == 0u) { + for(auto &&curr: pools) { + curr.second->compact(); + } + } else { + (assure().compact(), ...); + } + } + + /** + * @brief Check if an entity is part of all the given storage. + * @tparam Type Type of storage to check for. + * @param entt A valid identifier. + * @return True if the entity is part of all the storage, false otherwise. + */ + template + [[nodiscard]] bool all_of([[maybe_unused]] const entity_type entt) const { + if constexpr(sizeof...(Type) == 1u) { + auto *cpool = assure...>(); + return cpool && cpool->contains(entt); + } else { + return (all_of(entt) && ...); + } + } + + /** + * @brief Check if an entity is part of at least one given storage. + * @tparam Type Type of storage to check for. + * @param entt A valid identifier. + * @return True if the entity is part of at least one storage, false + * otherwise. + */ + template + [[nodiscard]] bool any_of([[maybe_unused]] const entity_type entt) const { + return (all_of(entt) || ...); + } + + /** + * @brief Returns references to the given elements for an entity. + * + * @warning + * Attempting to get an element from an entity that doesn't own it results + * in undefined behavior. + * + * @tparam Type Types of elements to get. + * @param entt A valid identifier. + * @return References to the elements owned by the entity. + */ + template + [[nodiscard]] decltype(auto) get([[maybe_unused]] const entity_type entt) const { + if constexpr(sizeof...(Type) == 1u) { + return (assure>()->get(entt), ...); + } else { + return stl::forward_as_tuple(get(entt)...); + } + } + + /*! @copydoc get */ + template + [[nodiscard]] decltype(auto) get([[maybe_unused]] const entity_type entt) { + if constexpr(sizeof...(Type) == 1u) { + return (static_cast &>(assure>()).get(entt), ...); + } else { + return stl::forward_as_tuple(get(entt)...); + } + } + + /** + * @brief Returns a reference to the given element for an entity. + * + * In case the entity doesn't own the element, the parameters provided are + * used to construct it. + * + * @sa get + * @sa emplace + * + * @tparam Type Type of element to get. + * @tparam Args Types of arguments to use to construct the element. + * @param entt A valid identifier. + * @param args Parameters to use to initialize the element. + * @return Reference to the element owned by the entity. + */ + template + [[nodiscard]] decltype(auto) get_or_emplace(const entity_type entt, Args &&...args) { + auto &cpool = assure(); + ENTT_ASSERT(valid(entt), "Invalid entity"); + return cpool.contains(entt) ? cpool.get(entt) : cpool.emplace(entt, stl::forward(args)...); + } + + /** + * @brief Returns pointers to the given elements for an entity. + * + * @note + * The registry retains ownership of the pointed-to elements. + * + * @tparam Type Types of elements to get. + * @param entt A valid identifier. + * @return Pointers to the elements owned by the entity. + */ + template + [[nodiscard]] auto try_get([[maybe_unused]] const entity_type entt) const { + if constexpr(sizeof...(Type) == 1u) { + const auto *cpool = assure...>(); + return (cpool && cpool->contains(entt)) ? stl::addressof(cpool->get(entt)) : nullptr; + } else { + return stl::make_tuple(try_get(entt)...); + } + } + + /*! @copydoc try_get */ + template + [[nodiscard]] auto try_get([[maybe_unused]] const entity_type entt) { + if constexpr(sizeof...(Type) == 1u) { + return (const_cast(stl::as_const(*this).template try_get(entt)), ...); + } else { + return stl::make_tuple(try_get(entt)...); + } + } + + /** + * @brief Clears a whole registry or the pools for the given elements. + * @tparam Type Types of elements to remove from their entities. + */ + template + void clear() { + if constexpr(sizeof...(Type) == 0u) { + for(size_type pos = pools.size(); pos; --pos) { + pools.begin()[static_cast(pos - 1u)].second->clear(); + } + + const auto elem = entities.each(); + entities.erase(elem.begin().base(), elem.end().base()); + } else { + (assure().clear(), ...); + } + } + + /** + * @brief Checks if an entity has elements assigned. + * @param entt A valid identifier. + * @return True if the entity has no elements assigned, false otherwise. + */ + [[nodiscard]] bool orphan(const entity_type entt) const { + return stl::none_of(pools.cbegin(), pools.cend(), [entt](auto &&curr) { return curr.second->contains(entt); }); + } + + /** + * @brief Returns a sink object for the given element. + * + * Use this function to receive notifications whenever a new instance of the + * given element is created and assigned to an entity.
+ * The function type for a listener is equivalent to: + * + * @code{.cpp} + * void(basic_registry &, Entity); + * @endcode + * + * Listeners are invoked **after** assigning the element to the entity. + * + * @sa sink + * + * @tparam Type Type of element of which to get the sink. + * @param id Optional name used to map the storage within the registry. + * @return A temporary sink object. + */ + template + [[nodiscard]] auto on_construct(const id_type id = type_hash::value()) { + return assure(id).on_construct(); + } + + /** + * @brief Returns a sink object for the given element. + * + * Use this function to receive notifications whenever an instance of the + * given element is explicitly updated.
+ * The function type for a listener is equivalent to: + * + * @code{.cpp} + * void(basic_registry &, Entity); + * @endcode + * + * Listeners are invoked **after** updating the element. + * + * @sa sink + * + * @tparam Type Type of element of which to get the sink. + * @param id Optional name used to map the storage within the registry. + * @return A temporary sink object. + */ + template + [[nodiscard]] auto on_update(const id_type id = type_hash::value()) { + return assure(id).on_update(); + } + + /** + * @brief Returns a sink object for the given element. + * + * Use this function to receive notifications whenever an instance of the + * given element is removed from an entity and thus destroyed.
+ * The function type for a listener is equivalent to: + * + * @code{.cpp} + * void(basic_registry &, Entity); + * @endcode + * + * Listeners are invoked **before** removing the element from the entity. + * + * @sa sink + * + * @tparam Type Type of element of which to get the sink. + * @param id Optional name used to map the storage within the registry. + * @return A temporary sink object. + */ + template + [[nodiscard]] auto on_destroy(const id_type id = type_hash::value()) { + return assure(id).on_destroy(); + } + + /** + * @brief Returns a view for the given elements. + * @tparam Type Type of element used to construct the view. + * @tparam Other Other types of elements used to construct the view. + * @tparam Exclude Types of elements used to filter the view. + * @return A newly created view. + */ + template + [[nodiscard]] basic_view, storage_for_type...>, exclude_t...>> + view(exclude_t = exclude_t{}) const { + basic_view, storage_for_type...>, exclude_t...>> elem{}; + [&elem](const auto *...curr) { ((curr ? elem.storage(*curr) : void()), ...); }(assure>()..., assure>()..., assure>()); + return elem; + } + + /*! @copydoc view */ + template + [[nodiscard]] basic_view, storage_for_type...>, exclude_t...>> + view(exclude_t = exclude_t{}) { + return {assure>(), assure>()..., assure>()...}; + } + + /** + * @brief Returns a group for the given elements. + * @tparam Owned Types of storage _owned_ by the group. + * @tparam Get Types of storage _observed_ by the group, if any. + * @tparam Exclude Types of storage used to filter the group, if any. + * @return A newly created group. + */ + template + basic_group...>, get_t...>, exclude_t...>> + group(get_t = get_t{}, exclude_t = exclude_t{}) { + using group_type = basic_group...>, get_t...>, exclude_t...>>; + using handler_type = group_type::handler; + + if(auto it = groups.find(group_type::group_id()); it != groups.cend()) { + return {*stl::static_pointer_cast(it->second)}; + } + + stl::shared_ptr handler{}; + + if constexpr(sizeof...(Owned) == 0u) { + handler = stl::allocate_shared(get_allocator(), get_allocator(), stl::forward_as_tuple(assure>()...), stl::forward_as_tuple(assure>()...)); + } else { + handler = stl::allocate_shared(get_allocator(), stl::forward_as_tuple(assure>()..., assure>()...), stl::forward_as_tuple(assure>()...)); + ENTT_ASSERT(stl::all_of(groups.cbegin(), groups.cend(), [](const auto &data) { return !(data.second->owned(type_id().hash()) || ...); }), "Conflicting groups"); + } + + groups.emplace(group_type::group_id(), handler); + return {*handler}; + } + + /*! @copydoc group */ + template + [[nodiscard]] basic_group...>, get_t...>, exclude_t...>> + group_if_exists(get_t = get_t{}, exclude_t = exclude_t{}) const { + using group_type = basic_group...>, get_t...>, exclude_t...>>; + using handler_type = group_type::handler; + + if(auto it = groups.find(group_type::group_id()); it != groups.cend()) { + return {*stl::static_pointer_cast(it->second)}; + } + + return {}; + } + + /** + * @brief Checks whether the given elements belong to any group. + * @tparam Type Types of elements in which one is interested. + * @return True if the pools of the given elements are _free_, false + * otherwise. + */ + template + [[nodiscard]] bool owned() const { + return stl::any_of(groups.cbegin(), groups.cend(), [](auto &&data) { return (data.second->owned(type_id().hash()) || ...); }); + } + + /** + * @brief Sorts the elements of a given element. + * + * The comparison function object returns `true` if the first element is + * _less_ than the second one, `false` otherwise. Its signature is also + * equivalent to one of the following: + * + * @code{.cpp} + * bool(const Entity, const Entity); + * bool(const Type &, const Type &); + * @endcode + * + * Moreover, it shall induce a _strict weak ordering_ on the values.
+ * The sort function object offers an `operator()` that accepts: + * + * * An iterator to the first element of the range to sort. + * * An iterator past the last element of the range to sort. + * * A comparison function object to use to compare the elements. + * + * The comparison function object hasn't necessarily the type of the one + * passed along with the other parameters to this member function. + * + * @warning + * Pools of elements owned by a group cannot be sorted. + * + * @tparam Type Type of elements to sort. + * @tparam Compare Type of comparison function object. + * @tparam Sort Type of sort function object. + * @tparam Args Types of arguments to forward to the sort function object. + * @param compare A valid comparison function object. + * @param algo A valid sort function object. + * @param args Arguments to forward to the sort function object, if any. + */ + template + void sort(Compare compare, Sort algo = Sort{}, Args &&...args) { + ENTT_ASSERT(!owned(), "Cannot sort owned storage"); + auto &cpool = assure(); + + if constexpr(stl::is_invocable_v) { + auto comp = [&cpool, compare = stl::move(compare)](const auto lhs, const auto rhs) { return compare(stl::as_const(cpool.get(lhs)), stl::as_const(cpool.get(rhs))); }; + cpool.sort(stl::move(comp), stl::move(algo), stl::forward(args)...); + } else { + cpool.sort(stl::move(compare), stl::move(algo), stl::forward(args)...); + } + } + + /** + * @brief Sorts two pools of elements in the same way. + * + * Entities and elements in `To` which are part of both storage are sorted + * internally with the order they have in `From`. The others follow in no + * particular order. + * + * @warning + * Pools of elements owned by a group cannot be sorted. + * + * @tparam To Type of elements to sort. + * @tparam From Type of elements to use to sort. + */ + template + void sort() { + ENTT_ASSERT(!owned(), "Cannot sort owned storage"); + const base_type &cpool = assure(); + assure().sort_as(cpool.begin(), cpool.end()); + } + + /** + * @brief Returns the context object, that is, a general purpose container. + * @return The context object, that is, a general purpose container. + */ + [[nodiscard]] context &ctx() noexcept { + return vars; + } + + /*! @copydoc ctx */ + [[nodiscard]] const context &ctx() const noexcept { + return vars; + } + +private: + context vars; + pool_container_type pools; + group_container_type groups; + storage_for_type entities; +}; + +} // namespace entt + +#endif diff --git a/include/entt/entity/runtime_view.hpp b/include/entt/entity/runtime_view.hpp new file mode 100644 index 0000000..1c69c8d --- /dev/null +++ b/include/entt/entity/runtime_view.hpp @@ -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 +class runtime_view_iterator final { + using iterator_type = Set::iterator; + using iterator_traits = stl::iterator_traits; + + [[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 &cpools, iterator_type curr, const stl::vector &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 *pools; + const stl::vector *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 +class basic_runtime_view { + using alloc_traits = stl::allocator_traits; + static_assert(stl::is_same_v, "Invalid value type"); + using container_type = stl::vector; + + [[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; + + /*! @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(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.
+ * 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 + void each(Func func) const { + for(const auto entity: *this) { + func(entity); + } + } + +private: + container_type pools; + container_type filter; +}; + +} // namespace entt + +#endif diff --git a/include/entt/entity/snapshot.hpp b/include/entt/entity/snapshot.hpp new file mode 100644 index 0000000..fd14b26 --- /dev/null +++ b/include/entt/entity/snapshot.hpp @@ -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 +void orphans(Registry ®istry) { + for(auto &storage = registry.template storage(); 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.
+ * This type can be used in both cases if provided with a correctly configured + * output archive. + * + * @tparam Registry Basic registry type. + */ +template +class basic_snapshot { + static_assert(!stl::is_const_v, "Non-const registry type required"); + using traits_type = entt_traits; + +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 + const basic_snapshot &get(Archive &archive, const id_type id = type_hash::value()) const { + if(const auto *storage = reg->template storage(id); storage) { + const typename registry_type::common_type &base = *storage; + + archive(static_cast(storage->size())); + + if constexpr(stl::is_same_v) { + archive(static_cast(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::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(null)); + } else { + archive(entt); + stl::apply([&archive](auto &&...args) { (archive(stl::forward(args)), ...); }, storage->get_as_tuple(entt)); + } + } + } else { + for(auto elem: storage->reach()) { + stl::apply([&archive](auto &&...args) { (archive(stl::forward(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 + const basic_snapshot &get(Archive &archive, stl::input_iterator auto first, stl::input_iterator auto last, const id_type id = type_hash::value()) const { + static_assert(!stl::is_same_v, "Entity types not supported"); + + if(const auto *storage = reg->template storage(id); storage && !storage->empty()) { + archive(static_cast(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(args)), ...); }, storage->get_as_tuple(entt)); + } else { + archive(static_cast(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.
+ * An example of use is the implementation of a save/restore utility. + * + * @tparam Registry Basic registry type. + */ +template +class basic_snapshot_loader { + static_assert(!stl::is_const_v, "Non-const registry type required"); + using traits_type = entt_traits; + +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().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 + basic_snapshot_loader &get(Archive &archive, const id_type id = type_hash::value()) { + auto &storage = reg->template storage(id); + typename traits_type::entity_type length{}; + + archive(length); + + if constexpr(stl::is_same_v) { + 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 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 == 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.
+ * 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.
+ * Identifiers that entities originally had are not transferred to the target. + * Instead, the loader maps remote identifiers to local ones while restoring a + * snapshot.
+ * 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 +class basic_continuous_loader { + static_assert(!stl::is_const_v, "Non-const registry type required"); + using traits_type = entt_traits; + + 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 + 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::first_type>; + using second_type = stl::decay_t::second_type; + + if constexpr(stl::is_same_v && stl::is_same_v) { + other.emplace(map(pair.first), map(pair.second)); + } else if constexpr(stl::is_same_v) { + other.emplace(map(pair.first), stl::move(pair.second)); + } else { + static_assert(stl::is_same_v, "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 + auto update(char, Container &container) -> decltype(typename Container::value_type{}, void()) { + // vector like container + static_assert(stl::is_same_v, "Invalid value type"); + + for(auto &&entt: container) { + entt = map(entt); + } + } + + template + void update([[maybe_unused]] Component &instance, [[maybe_unused]] Member Other::*member) { + if constexpr(!stl::is_same_v) { + return; + } else if constexpr(stl::is_same_v) { + 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.
+ * 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 + basic_continuous_loader &get(Archive &archive, const id_type id = type_hash::value()) { + auto &storage = reg->template storage(id); + typename traits_type::entity_type length{}; + entity_type entt{null}; + + archive(length); + + if constexpr(stl::is_same_v) { + 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 == 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.
+ * 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> remloc; + registry_type *reg; +}; + +} // namespace entt + +#endif diff --git a/include/entt/entity/sparse_set.hpp b/include/entt/entity/sparse_set.hpp new file mode 100644 index 0000000..39540e3 --- /dev/null +++ b/include/entt/entity/sparse_set.hpp @@ -0,0 +1,1076 @@ +#ifndef ENTT_ENTITY_SPARSE_SET_HPP +#define ENTT_ENTITY_SPARSE_SET_HPP + +#include +#include "../config/config.h" +#include "../core/algorithm.hpp" +#include "../core/any.hpp" +#include "../core/bit.hpp" +#include "../core/type_info.hpp" +#include "../stl/concepts.hpp" +#include "../stl/cstddef.hpp" +#include "../stl/iterator.hpp" +#include "../stl/memory.hpp" +#include "../stl/type_traits.hpp" +#include "../stl/utility.hpp" +#include "../stl/vector.hpp" +#include "entity.hpp" +#include "fwd.hpp" + +namespace entt { + +/*! @cond ENTT_INTERNAL */ +namespace internal { + +template +struct sparse_set_iterator final { + using value_type = Container::value_type; + using pointer = Container::const_pointer; + using reference = Container::const_reference; + using difference_type = Container::difference_type; + using iterator_category = stl::random_access_iterator_tag; + + constexpr sparse_set_iterator() noexcept + : packed{}, + offset{} {} + + constexpr sparse_set_iterator(const Container &ref, const difference_type idx) noexcept + : packed{&ref}, + offset{idx} {} + + constexpr sparse_set_iterator &operator++() noexcept { + return --offset, *this; + } + + constexpr sparse_set_iterator operator++(int) noexcept { + const sparse_set_iterator orig = *this; + return ++(*this), orig; + } + + constexpr sparse_set_iterator &operator--() noexcept { + return ++offset, *this; + } + + constexpr sparse_set_iterator operator--(int) noexcept { + const sparse_set_iterator orig = *this; + return operator--(), orig; + } + + constexpr sparse_set_iterator &operator+=(const difference_type value) noexcept { + offset -= value; + return *this; + } + + constexpr sparse_set_iterator operator+(const difference_type value) const noexcept { + sparse_set_iterator copy = *this; + return (copy += value); + } + + constexpr sparse_set_iterator &operator-=(const difference_type value) noexcept { + return (*this += -value); + } + + constexpr sparse_set_iterator operator-(const difference_type value) const noexcept { + return (*this + -value); + } + + [[nodiscard]] constexpr reference operator[](const difference_type value) const noexcept { + return (*packed)[static_cast(index() - value)]; + } + + [[nodiscard]] constexpr pointer operator->() const noexcept { + return stl::addressof(operator[](0)); + } + + [[nodiscard]] constexpr reference operator*() const noexcept { + return operator[](0); + } + + [[nodiscard]] constexpr stl::ptrdiff_t operator-(const sparse_set_iterator &other) const noexcept { + // intentionally reversed due to backward iteration + return other.offset - offset; + } + + [[nodiscard]] constexpr bool operator==(const sparse_set_iterator &other) const noexcept { + return offset == other.offset; + } + + [[nodiscard]] constexpr auto operator<=>(const sparse_set_iterator &other) const noexcept { + // intentionally reversed due to backward iteration + return other.offset <=> offset; + } + + [[nodiscard]] constexpr pointer data() const noexcept { + return packed ? packed->data() : nullptr; + } + + [[nodiscard]] constexpr difference_type index() const noexcept { + return offset - 1; + } + +private: + const Container *packed; + difference_type offset; +}; + +} // namespace internal +/*! @endcond */ + +/** + * @brief Sparse set implementation. + * + * Sparse set or packed array or whatever is the name users give it.
+ * Two arrays: an _external_ one and an _internal_ one; a _sparse_ one and a + * _packed_ one; one used for direct access through contiguous memory, the other + * one used to get the data through an extra level of indirection.
+ * This type of data structure is widely documented in the literature and on the + * web. This is nothing more than a customized implementation suitable for the + * purpose of the framework. + * + * @note + * Internal data structures arrange elements to maximize performance. There are + * no guarantees that entities are returned in the insertion order when iterate + * a sparse set. Do not make assumption on the order in any case. + * + * @tparam Entity A valid entity type. + * @tparam Allocator Type of allocator used to manage memory and elements. + */ +template +class basic_sparse_set { + using alloc_traits = stl::allocator_traits; + static_assert(stl::is_same_v, "Invalid value type"); + using sparse_container_type = stl::vector>; + using packed_container_type = stl::vector; + using traits_type = entt_traits; + + static constexpr auto max_size = static_cast(traits_type::to_entity(null)); + + // it could be auto but gcc complains and emits a warning due to a false positive + [[nodiscard]] stl::size_t policy_to_head() const noexcept { + return static_cast(max_size * static_cast>(mode != deletion_policy::swap_only)); + } + + [[nodiscard]] auto entity_to_pos(const Entity entt) const noexcept { + return static_cast(traits_type::to_entity(entt)); + } + + [[nodiscard]] auto pos_to_page(const stl::size_t pos) const noexcept { + return static_cast(pos / traits_type::page_size); + } + + [[nodiscard]] auto sparse_ptr(const Entity entt) const { + const auto pos = entity_to_pos(entt); + const auto page = pos_to_page(pos); + return (page < sparse.size() && sparse[page]) ? (sparse[page] + fast_mod(pos, traits_type::page_size)) : nullptr; + } + + [[nodiscard]] auto &sparse_ref(const Entity entt) const { + ENTT_ASSERT(sparse_ptr(entt), "Invalid element"); + const auto pos = entity_to_pos(entt); + return sparse[pos_to_page(pos)][fast_mod(pos, traits_type::page_size)]; + } + + [[nodiscard]] auto to_iterator(const Entity entt) const { + return --(end() - static_cast(index(entt))); + } + + [[nodiscard]] auto &assure_at_least(const Entity entt) { + const auto pos = entity_to_pos(entt); + const auto page = pos_to_page(pos); + + if(!(page < sparse.size())) { + sparse.resize(page + 1u, nullptr); + } + + if(!sparse[page]) { + constexpr entity_type init = null; + auto page_allocator{packed.get_allocator()}; + sparse[page] = alloc_traits::allocate(page_allocator, traits_type::page_size); + stl::uninitialized_fill(sparse[page], sparse[page] + traits_type::page_size, init); + } + + return sparse[page][fast_mod(pos, traits_type::page_size)]; + } + + void release_sparse_pages() { + for(auto page_allocator{packed.get_allocator()}; auto &&page: sparse) { + if(page != nullptr) { + stl::destroy(page, page + traits_type::page_size); + alloc_traits::deallocate(page_allocator, page, traits_type::page_size); + page = nullptr; + } + } + } + + void swap_at(const stl::size_t lhs, const stl::size_t rhs) { + auto &from = packed[lhs]; + auto &to = packed[rhs]; + + sparse_ref(from) = traits_type::combine(static_cast(rhs), traits_type::to_integral(from)); + sparse_ref(to) = traits_type::combine(static_cast(lhs), traits_type::to_integral(to)); + + stl::swap(from, to); + } + +private: + [[nodiscard]] virtual const void *get_at(const stl::size_t) const { + return nullptr; + } + + virtual void swap_or_move([[maybe_unused]] const stl::size_t lhs, [[maybe_unused]] const stl::size_t rhs) { + ENTT_ASSERT((mode != deletion_policy::swap_only) || ((lhs < head) == (rhs < head)), "Cross swapping is not supported"); + } + +protected: + /*! @brief Random access iterator type. */ + using basic_iterator = internal::sparse_set_iterator; + + /** + * @brief Erases an entity from a sparse set. + * @param entt A valid identifier for the element to pop. + */ + void swap_only(const Entity entt) { + ENTT_ASSERT(mode == deletion_policy::swap_only, "Deletion policy mismatch"); + const auto pos = index(entt); + bump(traits_type::next(entt)); + swap_at(pos, head -= (pos < head)); + } + + /** + * @brief Erases an entity from a sparse set. + * @param entt A valid identifier for the element to pop. + */ + void swap_and_pop(const Entity entt) { + ENTT_ASSERT(mode == deletion_policy::swap_and_pop, "Deletion policy mismatch"); + auto &self = sparse_ref(entt); + const auto pos = traits_type::to_entity(self); + sparse_ref(packed.back()) = traits_type::combine(pos, traits_type::to_integral(packed.back())); + packed[static_cast(pos)] = packed.back(); + // unnecessary but it helps to detect nasty bugs + // NOLINTNEXTLINE(bugprone-assert-side-effect) + ENTT_ASSERT((packed.back() = null, true), ""); + // lazy self-assignment guard + self = null; + packed.pop_back(); + } + + /** + * @brief Erases an entity from a sparse set. + * @param entt A valid identifier for the element to pop. + */ + void in_place_pop(const Entity entt) { + ENTT_ASSERT(mode == deletion_policy::in_place, "Deletion policy mismatch"); + const auto pos = entity_to_pos(stl::exchange(sparse_ref(entt), null)); + packed[pos] = traits_type::combine(static_cast(stl::exchange(head, pos)), tombstone); + } + + /** + * @brief Erases entities from a sparse set. + * @param first An iterator to the first element of the range of entities. + * @param last An iterator past the last element of the range of entities. + */ + virtual void pop(basic_iterator first, basic_iterator last) { + switch(mode) { + case deletion_policy::swap_and_pop: + for(; first != last; ++first) { + swap_and_pop(*first); + } + break; + case deletion_policy::in_place: + for(; first != last; ++first) { + in_place_pop(*first); + } + break; + case deletion_policy::swap_only: + for(; first != last; ++first) { + swap_only(*first); + } + break; + } + } + + /*! @brief Erases all entities of a sparse set. */ + virtual void pop_all() { + if(!packed.empty()) { + // suboptimal with few entities, but exploits cache way more with many + for(auto &&elem: sparse) { + if(elem) { + for(size_type pos{}; pos < traits_type::page_size; ++pos) { + elem[pos] = null; + } + } + } + } + + head = policy_to_head(); + packed.clear(); + } + + /** + * @brief Assigns an entity to a sparse set. + * @param entt A valid identifier. + * @param force_back Force back insertion. + * @return Iterator pointing to the emplaced element. + */ + virtual basic_iterator try_emplace(const Entity entt, const bool force_back, const void * = nullptr) { + ENTT_ASSERT(entt != null && entt != tombstone, "Invalid element"); + auto &elem = assure_at_least(entt); + auto pos = size(); + + switch(mode) { + case deletion_policy::in_place: + if(head != max_size && !force_back) { + pos = head; + ENTT_ASSERT(elem == null, "Slot not available"); + elem = traits_type::combine(static_cast(head), traits_type::to_integral(entt)); + head = entity_to_pos(stl::exchange(packed[pos], entt)); + break; + } + [[fallthrough]]; + case deletion_policy::swap_and_pop: + packed.push_back(entt); + ENTT_ASSERT(elem == null, "Slot not available"); + elem = traits_type::combine(static_cast(packed.size() - 1u), traits_type::to_integral(entt)); + break; + case deletion_policy::swap_only: + if(elem == null) { + packed.push_back(entt); + elem = traits_type::combine(static_cast(packed.size() - 1u), traits_type::to_integral(entt)); + } else { + ENTT_ASSERT(!(entity_to_pos(elem) < head), "Slot not available"); + bump(entt); + } + + pos = head++; + swap_at(entity_to_pos(elem), pos); + break; + } + + return iterator{packed, static_cast(++pos)}; + } + + /*! @brief Forwards variables to derived classes, if any. */ + // NOLINTNEXTLINE(performance-unnecessary-value-param) + virtual void bind_any(any) noexcept {} + +public: + /*! @brief Allocator type. */ + using allocator_type = Allocator; + /*! @brief Underlying entity identifier. */ + using entity_type = traits_type::value_type; + /*! @brief Underlying version type. */ + using version_type = traits_type::version_type; + /*! @brief Unsigned integer type. */ + using size_type = stl::size_t; + /*! @brief Signed integer type. */ + using difference_type = stl::ptrdiff_t; + /*! @brief Pointer type to contained entities. */ + using pointer = packed_container_type::const_pointer; + /*! @brief Random access iterator type. */ + using iterator = basic_iterator; + /*! @brief Constant random access iterator type. */ + using const_iterator = iterator; + /*! @brief Reverse iterator type. */ + using reverse_iterator = stl::reverse_iterator; + /*! @brief Constant reverse iterator type. */ + using const_reverse_iterator = stl::reverse_iterator; + + /*! @brief Default constructor. */ + basic_sparse_set() + : basic_sparse_set{type_id()} {} + + /** + * @brief Constructs an empty container with a given allocator. + * @param allocator The allocator to use. + */ + explicit basic_sparse_set(const allocator_type &allocator) + : basic_sparse_set{deletion_policy::swap_and_pop, allocator} {} + + /** + * @brief Constructs an empty container with the given policy and allocator. + * @param pol Type of deletion policy. + * @param allocator The allocator to use (possibly default-constructed). + */ + explicit basic_sparse_set(deletion_policy pol, const allocator_type &allocator = {}) + : basic_sparse_set{type_id(), pol, allocator} {} + + /** + * @brief Constructs an empty container with the given value type, policy + * and allocator. + * @param elem Returned value type, if any. + * @param pol Type of deletion policy. + * @param allocator The allocator to use (possibly default-constructed). + */ + explicit basic_sparse_set(const type_info &elem, deletion_policy pol = deletion_policy::swap_and_pop, const allocator_type &allocator = {}) + : sparse{allocator}, + packed{allocator}, + descriptor{&elem}, + mode{pol}, + head{policy_to_head()} { + ENTT_ASSERT(traits_type::version_mask || mode != deletion_policy::in_place, "Policy does not support zero-sized versions"); + } + + /*! @brief Default copy constructor, deleted on purpose. */ + basic_sparse_set(const basic_sparse_set &) = delete; + + /** + * @brief Move constructor. + * @param other The instance to move from. + */ + basic_sparse_set(basic_sparse_set &&other) noexcept + : sparse{stl::move(other.sparse)}, + packed{stl::move(other.packed)}, + descriptor{other.descriptor}, + mode{other.mode}, + head{stl::exchange(other.head, policy_to_head())} {} + + /** + * @brief Allocator-extended move constructor. + * @param other The instance to move from. + * @param allocator The allocator to use. + */ + basic_sparse_set(basic_sparse_set &&other, const allocator_type &allocator) + : sparse{stl::move(other.sparse), allocator}, + packed{stl::move(other.packed), allocator}, + descriptor{other.descriptor}, + mode{other.mode}, + head{stl::exchange(other.head, policy_to_head())} { + ENTT_ASSERT(alloc_traits::is_always_equal::value || get_allocator() == other.get_allocator(), "Copying a sparse set is not allowed"); + } + + /*! @brief Default destructor. */ + virtual ~basic_sparse_set() { + release_sparse_pages(); + } + + /** + * @brief Default copy assignment operator, deleted on purpose. + * @return This sparse set. + */ + basic_sparse_set &operator=(const basic_sparse_set &) = delete; + + /** + * @brief Move assignment operator. + * @param other The instance to move from. + * @return This sparse set. + */ + basic_sparse_set &operator=(basic_sparse_set &&other) noexcept { + ENTT_ASSERT(alloc_traits::is_always_equal::value || get_allocator() == other.get_allocator(), "Copying a sparse set is not allowed"); + swap(other); + return *this; + } + + /** + * @brief Exchanges the contents with those of a given sparse set. + * @param other Sparse set to exchange the content with. + */ + void swap(basic_sparse_set &other) noexcept { + using stl::swap; + swap(sparse, other.sparse); + swap(packed, other.packed); + swap(descriptor, other.descriptor); + swap(mode, other.mode); + swap(head, other.head); + } + + /** + * @brief Returns the associated allocator. + * @return The associated allocator. + */ + [[nodiscard]] constexpr allocator_type get_allocator() const noexcept { + return packed.get_allocator(); + } + + /** + * @brief Returns the deletion policy of a sparse set. + * @return The deletion policy of the sparse set. + */ + [[nodiscard]] deletion_policy policy() const noexcept { + return mode; + } + + /** + * @brief Returns data on the free list whose meaning depends on the mode. + * @return Free list information that is mode dependent. + */ + [[nodiscard]] size_type free_list() const noexcept { + return head; + } + + /** + * @brief Sets data on the free list whose meaning depends on the mode. + * @param value Free list information that is mode dependent. + */ + void free_list(const size_type value) noexcept { + ENTT_ASSERT((mode == deletion_policy::swap_only) && !(value > packed.size()), "Invalid value"); + head = value; + } + + /** + * @brief Increases the capacity of a sparse set. + * + * If the new capacity is greater than the current capacity, new storage is + * allocated, otherwise the method does nothing. + * + * @param cap Desired capacity. + */ + virtual void reserve(const size_type cap) { + packed.reserve(cap); + } + + /** + * @brief Returns the number of elements that a sparse set has currently + * allocated space for. + * @return Capacity of the sparse set. + */ + [[nodiscard]] virtual size_type capacity() const noexcept { + return packed.capacity(); + } + + /*! @brief Requests the removal of unused capacity. */ + virtual void shrink_to_fit() { + sparse_container_type other{sparse.get_allocator()}; + const auto len = sparse.size(); + other.reserve(len); + + for(size_type cnt{}; auto &&elem: stl::as_const(packed)) { + if(elem != tombstone) { + if(const auto page = pos_to_page(entity_to_pos(elem)); sparse[page] != nullptr) { + if(const auto sz = page + 1u; sz > other.size()) { + other.resize(sz, nullptr); + } + + other[page] = stl::exchange(sparse[page], nullptr); + + if(++cnt == len) { + // early exit due to lack of pages + break; + } + } + } + } + + release_sparse_pages(); + sparse.swap(other); + + sparse.shrink_to_fit(); + packed.shrink_to_fit(); + } + + /** + * @brief Returns the extent of a sparse set. + * + * The extent of a sparse set is also the size of the internal sparse array. + * There is no guarantee that all pages have been allocated, nor that the + * internal packed array is be the same size. + * + * @return Extent of the sparse set. + */ + [[nodiscard]] size_type extent() const noexcept { + return sparse.size() * traits_type::page_size; + } + + /** + * @brief Returns the number of elements in a sparse set. + * + * The number of elements is also the size of the internal packed array. + * There is no guarantee that the internal sparse array has the same size. + * Usually the size of the internal sparse array is equal or greater than + * the one of the internal packed array. + * + * @return Number of elements. + */ + [[nodiscard]] size_type size() const noexcept { + return packed.size(); + } + + /** + * @brief Checks whether a sparse set is empty. + * @return True if the sparse set is empty, false otherwise. + */ + [[nodiscard]] bool empty() const noexcept { + return packed.empty(); + } + + /** + * @brief Checks whether a sparse set is fully packed. + * @return True if the sparse set is fully packed, false otherwise. + */ + [[nodiscard]] bool contiguous() const noexcept { + return (mode != deletion_policy::in_place) || (head == max_size); + } + + /** + * @brief Direct access to the internal packed array. + * @return A pointer to the internal packed array. + */ + [[nodiscard]] pointer data() const noexcept { + return packed.data(); + } + + /** + * @brief Returns an iterator to the beginning. + * + * If the sparse set is empty, the returned iterator will be equal to + * `end()`. + * + * @return An iterator to the first entity of the sparse set. + */ + [[nodiscard]] iterator begin() const noexcept { + const auto pos = static_cast(packed.size()); + return iterator{packed, pos}; + } + + /*! @copydoc begin */ + [[nodiscard]] const_iterator cbegin() const noexcept { + return begin(); + } + + /** + * @brief Returns an iterator to the end. + * @return An iterator to the element following the last entity of a sparse + * set. + */ + [[nodiscard]] iterator end() const noexcept { + return iterator{packed, {}}; + } + + /*! @copydoc end */ + [[nodiscard]] const_iterator cend() const noexcept { + return end(); + } + + /** + * @brief Returns a reverse iterator to the beginning. + * + * If the sparse set is empty, the returned iterator will be equal to + * `rend()`. + * + * @return An iterator to the first entity of the reversed internal packed + * array. + */ + [[nodiscard]] reverse_iterator rbegin() const noexcept { + return stl::make_reverse_iterator(end()); + } + + /*! @copydoc rbegin */ + [[nodiscard]] const_reverse_iterator crbegin() const noexcept { + return rbegin(); + } + + /** + * @brief Returns a reverse iterator to the end. + * @return An iterator to the element following the last entity of the + * reversed sparse set. + */ + [[nodiscard]] reverse_iterator rend() const noexcept { + return stl::make_reverse_iterator(begin()); + } + + /*! @copydoc rend */ + [[nodiscard]] const_reverse_iterator crend() const noexcept { + return rend(); + } + + /** + * @brief Finds an entity. + * @param entt A valid identifier. + * @return An iterator to the given entity if it's found, past the end + * iterator otherwise. + */ + [[nodiscard]] const_iterator find(const entity_type entt) const noexcept { + return contains(entt) ? to_iterator(entt) : end(); + } + + /** + * @brief Checks if a sparse set contains an entity. + * @param entt A valid identifier. + * @return True if the sparse set contains the entity, false otherwise. + */ + [[nodiscard]] bool contains(const entity_type entt) const noexcept { + const auto *elem = sparse_ptr(entt); + constexpr auto cap = traits_type::entity_mask; + constexpr auto mask = traits_type::to_integral(null) & ~cap; + // testing versions permits to avoid accessing the packed array + return elem && (((mask & traits_type::to_integral(entt)) ^ traits_type::to_integral(*elem)) < cap); + } + + /** + * @brief Returns the contained version for an identifier. + * @param entt A valid identifier. + * @return The version for the given identifier if present, the tombstone + * version otherwise. + */ + [[nodiscard]] version_type current(const entity_type entt) const noexcept { + const auto *elem = sparse_ptr(entt); + constexpr auto fallback = traits_type::to_version(tombstone); + return elem ? traits_type::to_version(*elem) : fallback; + } + + /** + * @brief Returns the position of an entity in a sparse set. + * + * @warning + * Attempting to get the position of an entity that doesn't belong to the + * sparse set results in undefined behavior. + * + * @param entt A valid identifier. + * @return The position of the entity in the sparse set. + */ + [[nodiscard]] size_type index(const entity_type entt) const noexcept { + ENTT_ASSERT(contains(entt), "Set does not contain entity"); + return entity_to_pos(sparse_ref(entt)); + } + + /** + * @brief Returns the entity at specified location. + * @param pos The position for which to return the entity. + * @return The entity at specified location. + */ + [[nodiscard]] entity_type operator[](const size_type pos) const noexcept { + ENTT_ASSERT(pos < packed.size(), "Index out of bounds"); + return packed[pos]; + } + + /** + * @brief Returns the element assigned to an entity, if any. + * + * @warning + * Attempting to use an entity that doesn't belong to the sparse set results + * in undefined behavior. + * + * @param entt A valid identifier. + * @return An opaque pointer to the element assigned to the entity, if any. + */ + [[nodiscard]] const void *value(const entity_type entt) const noexcept { + return get_at(index(entt)); + } + + /*! @copydoc value */ + [[nodiscard]] void *value(const entity_type entt) noexcept { + return const_cast(stl::as_const(*this).value(entt)); + } + + /** + * @brief Assigns an entity to a sparse set. + * + * @warning + * Attempting to assign an entity that already belongs to the sparse set + * results in undefined behavior. + * + * @param entt A valid identifier. + * @param elem Optional opaque element to forward to mixins, if any. + * @return Iterator pointing to the emplaced element in case of success, the + * `end()` iterator otherwise. + */ + iterator push(const entity_type entt, const void *elem = nullptr) { + return try_emplace(entt, false, elem); + } + + /** + * @brief Assigns one or more entities to a sparse set. + * + * @warning + * Attempting to assign an entity that already belongs to the sparse set + * results in undefined behavior. + * + * @param first An iterator to the first element of the range of entities. + * @param last An iterator past the last element of the range of entities. + * @return Iterator pointing to the first element inserted in case of + * success, the `end()` iterator otherwise. + */ + iterator push(stl::input_iterator auto first, stl::input_iterator auto last) { + auto curr = end(); + + for(; first != last; ++first) { + curr = try_emplace(*first, true); + } + + return curr; + } + + /** + * @brief Bump the version number of an entity. + * + * @warning + * Attempting to bump the version of an entity that doesn't belong to the + * sparse set results in undefined behavior. + * + * @param entt A valid identifier. + * @return The version of the given identifier. + */ + version_type bump(const entity_type entt) { + auto &elem = sparse_ref(entt); + ENTT_ASSERT(entt != null && elem != tombstone, "Cannot set the required version"); + elem = traits_type::combine(traits_type::to_integral(elem), traits_type::to_integral(entt)); + packed[entity_to_pos(elem)] = entt; + return traits_type::to_version(entt); + } + + /** + * @brief Erases an entity from a sparse set. + * + * @warning + * Attempting to erase an entity that doesn't belong to the sparse set + * results in undefined behavior. + * + * @param entt A valid identifier. + */ + void erase(const entity_type entt) { + const auto it = to_iterator(entt); + pop(it, it + 1u); + } + + /** + * @brief Erases entities from a set. + * + * @sa erase + * + * @tparam It Type of input iterator. + * @param first An iterator to the first element of the range of entities. + * @param last An iterator past the last element of the range of entities. + */ + template + void erase(It first, It last) { + if constexpr(stl::is_same_v) { + pop(first, last); + } else { + for(; first != last; ++first) { + erase(*first); + } + } + } + + /** + * @brief Removes an entity from a sparse set if it exists. + * @param entt A valid identifier. + * @return True if the entity is actually removed, false otherwise. + */ + bool remove(const entity_type entt) { + return contains(entt) && (erase(entt), true); + } + + /** + * @brief Removes entities from a sparse set if they exist. + * @tparam It Type of input iterator. + * @param first An iterator to the first element of the range of entities. + * @param last An iterator past the last element of the range of entities. + * @return The number of entities actually removed. + */ + template + size_type remove(It first, It last) { + size_type count{}; + + if constexpr(stl::is_same_v) { + while(first != last) { + while(first != last && !contains(*first)) { + ++first; + } + + const auto it = first; + + while(first != last && contains(*first)) { + ++first; + } + + count += static_cast(stl::distance(it, first)); + erase(it, first); + } + } else { + for(; first != last; ++first) { + count += remove(*first); + } + } + + return count; + } + + /*! @brief Removes all tombstones from a sparse set. */ + void compact() { + if(mode == deletion_policy::in_place) { + size_type from = packed.size(); + size_type pos = stl::exchange(head, max_size); + + for(; from && packed[from - 1u] == tombstone; --from) {} + + while(pos != max_size) { + if(const auto to = stl::exchange(pos, entity_to_pos(packed[pos])); to < from) { + --from; + swap_or_move(from, to); + + packed[to] = packed[from]; + const auto elem = static_cast(to); + sparse_ref(packed[to]) = traits_type::combine(elem, traits_type::to_integral(packed[to])); + + for(; from && packed[from - 1u] == tombstone; --from) {} + } + } + + packed.erase(packed.begin() + static_cast(from), packed.end()); + } + } + + /** + * @brief Swaps two entities in a sparse set. + * + * For what it's worth, this function affects both the internal sparse array + * and the internal packed array. Users should not care of that anyway. + * + * @warning + * Attempting to swap entities that don't belong to the sparse set results + * in undefined behavior. + * + * @param lhs A valid identifier. + * @param rhs A valid identifier. + */ + void swap_elements(const entity_type lhs, const entity_type rhs) { + const auto from = index(lhs); + const auto to = index(rhs); + + // basic no-leak guarantee if swapping throws + swap_or_move(from, to); + swap_at(from, to); + } + + /** + * @brief Sort the first count elements according to the given comparison + * function. + * + * The comparison function object must return `true` if the first element + * is _less_ than the second one, `false` otherwise. The signature of the + * comparison function should be equivalent to the following: + * + * @code{.cpp} + * bool(const Entity, const Entity); + * @endcode + * + * Moreover, the comparison function object shall induce a + * _strict weak ordering_ on the values. + * + * The sort function object must offer a member function template + * `operator()` that accepts three arguments: + * + * * An iterator to the first element of the range to sort. + * * An iterator past the last element of the range to sort. + * * A comparison function to use to compare the elements. + * + * @tparam Compare Type of comparison function object. + * @tparam Sort Type of sort function object. + * @tparam Args Types of arguments to forward to the sort function object. + * @param length Number of elements to sort. + * @param compare A valid comparison function object. + * @param algo A valid sort function object. + * @param args Arguments to forward to the sort function object, if any. + */ + template + void sort_n(const size_type length, Compare compare, Sort algo = Sort{}, Args &&...args) { + ENTT_ASSERT((mode != deletion_policy::in_place) || (head == max_size), "Sorting with tombstones not allowed"); + ENTT_ASSERT(!(length > packed.size()), "Length exceeds the number of elements"); + + algo(packed.rend() - static_cast(length), packed.rend(), stl::move(compare), stl::forward(args)...); + + for(size_type pos{}; pos < length; ++pos) { + auto curr = pos; + auto next = index(packed[curr]); + + while(curr != next) { + const auto idx = index(packed[next]); + const auto entt = packed[curr]; + + swap_or_move(next, idx); + const auto elem = static_cast(curr); + sparse_ref(entt) = traits_type::combine(elem, traits_type::to_integral(packed[curr])); + curr = stl::exchange(next, idx); + } + } + } + + /** + * @brief Sort all elements according to the given comparison function. + * + * @sa sort_n + * + * @tparam Compare Type of comparison function object. + * @tparam Sort Type of sort function object. + * @tparam Args Types of arguments to forward to the sort function object. + * @param compare A valid comparison function object. + * @param algo A valid sort function object. + * @param args Arguments to forward to the sort function object, if any. + */ + template + void sort(Compare compare, Sort algo = Sort{}, Args &&...args) { + const size_type len = (mode == deletion_policy::swap_only) ? head : packed.size(); + sort_n(len, stl::move(compare), stl::move(algo), stl::forward(args)...); + } + + /** + * @brief Sort entities according to their order in a range. + * + * Entities that are part of both the sparse set and the range are ordered + * internally according to the order they have in the range.
+ * All other entities goes to the end of the sparse set and there are no + * guarantees on their order. + * + * @tparam It Type of input iterator. + * @param first An iterator to the first element of the range of entities. + * @param last An iterator past the last element of the range of entities. + * @return An iterator past the last of the elements actually shared. + */ + template + iterator sort_as(It first, It last) { + ENTT_ASSERT((mode != deletion_policy::in_place) || (head == max_size), "Sorting with tombstones not allowed"); + const size_type len = (mode == deletion_policy::swap_only) ? head : packed.size(); + auto it = end() - static_cast(len); + + for(const auto other = end(); (it != other) && (first != last); ++first) { + if(const auto curr = *first; contains(curr)) { + if(const auto entt = *it; entt != curr) { + // basic no-leak guarantee (with invalid state) if swapping throws + swap_elements(entt, curr); + } + + ++it; + } + } + + return it; + } + + /*! @brief Clears a sparse set. */ + void clear() { + pop_all(); + // sanity check to avoid subtle issues due to storage classes + ENTT_ASSERT((compact(), size()) == 0u, "Non-empty set"); + head = policy_to_head(); + packed.clear(); + } + + /** + * @brief Returns a type info object for the value type, if any. + * @return A type info object for the value type, if any. + */ + [[nodiscard]] const type_info &info() const noexcept { + return *descriptor; + } + + /** + * @brief Forwards variables to derived classes, if any. + * @tparam Type Type of the element to forward. + * @param value The element to forward. + */ + template + void bind(Type &&value) noexcept { + bind_any(forward_as_any(stl::forward(value))); + } + +private: + sparse_container_type sparse; + packed_container_type packed; + const type_info *descriptor; + deletion_policy mode; + size_type head; +}; + +} // namespace entt + +#endif diff --git a/include/entt/entity/storage.hpp b/include/entt/entity/storage.hpp new file mode 100644 index 0000000..d12bf7d --- /dev/null +++ b/include/entt/entity/storage.hpp @@ -0,0 +1,1222 @@ +#ifndef ENTT_ENTITY_STORAGE_HPP +#define ENTT_ENTITY_STORAGE_HPP + +#include +#include "../config/config.h" +#include "../core/bit.hpp" +#include "../core/iterator.hpp" +#include "../core/memory.hpp" +#include "../core/type_info.hpp" +#include "../stl/concepts.hpp" +#include "../stl/cstddef.hpp" +#include "../stl/iterator.hpp" +#include "../stl/memory.hpp" +#include "../stl/tuple.hpp" +#include "../stl/type_traits.hpp" +#include "../stl/utility.hpp" +#include "../stl/vector.hpp" +#include "component.hpp" +#include "entity.hpp" +#include "fwd.hpp" +#include "sparse_set.hpp" + +namespace entt { + +/*! @cond ENTT_INTERNAL */ +namespace internal { + +template +class storage_iterator final { + template + friend class storage_iterator; + + using container_type = stl::remove_const_t; + using alloc_traits = stl::allocator_traits; + + using iterator_traits = stl::iterator_traits, + typename alloc_traits::template rebind_traits::element_type>::const_pointer, + typename alloc_traits::template rebind_traits::element_type>::pointer>>; + +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::random_access_iterator_tag; + + constexpr storage_iterator() noexcept = default; + + constexpr storage_iterator(Container *ref, const difference_type idx) noexcept + : payload{ref}, + offset{idx} {} + + template> Other> + requires stl::is_const_v + constexpr storage_iterator(const storage_iterator &other) noexcept + : storage_iterator{other.payload, other.offset} {} + + constexpr storage_iterator &operator++() noexcept { + return --offset, *this; + } + + constexpr storage_iterator operator++(int) noexcept { + const storage_iterator orig = *this; + return ++(*this), orig; + } + + constexpr storage_iterator &operator--() noexcept { + return ++offset, *this; + } + + constexpr storage_iterator operator--(int) noexcept { + const storage_iterator orig = *this; + return operator--(), orig; + } + + constexpr storage_iterator &operator+=(const difference_type value) noexcept { + offset -= value; + return *this; + } + + constexpr storage_iterator operator+(const difference_type value) const noexcept { + storage_iterator copy = *this; + return (copy += value); + } + + constexpr storage_iterator &operator-=(const difference_type value) noexcept { + return (*this += -value); + } + + constexpr storage_iterator operator-(const difference_type value) const noexcept { + return (*this + -value); + } + + [[nodiscard]] constexpr reference operator[](const difference_type value) const noexcept { + const auto pos = static_cast(index() - value); + return (*payload)[pos / Page][fast_mod(static_cast(pos), Page)]; + } + + [[nodiscard]] constexpr pointer operator->() const noexcept { + return stl::addressof(operator[](0)); + } + + [[nodiscard]] constexpr reference operator*() const noexcept { + return operator[](0); + } + + template + [[nodiscard]] constexpr stl::ptrdiff_t operator-(const storage_iterator &other) const noexcept { + // intentionally reversed due to backward iteration + return other.offset - offset; + } + + template + [[nodiscard]] constexpr bool operator==(const storage_iterator &other) const noexcept { + return offset == other.offset; + } + + template + [[nodiscard]] constexpr auto operator<=>(const storage_iterator &other) const noexcept { + // intentionally reversed due to backward iteration + return other.offset <=> offset; + } + + [[nodiscard]] constexpr difference_type index() const noexcept { + return offset - 1; + } + +private: + Container *payload; + difference_type offset; +}; + +template +class extended_storage_iterator final { + template + friend class extended_storage_iterator; + +public: + using iterator_type = It; + using value_type = decltype(stl::tuple_cat(stl::make_tuple(*stl::declval()), stl::forward_as_tuple(*stl::declval()...))); + using pointer = input_iterator_pointer; + using reference = value_type; + using difference_type = stl::ptrdiff_t; + using iterator_category = stl::input_iterator_tag; + using iterator_concept = stl::forward_iterator_tag; + + constexpr extended_storage_iterator() + : it{} {} + + constexpr extended_storage_iterator(iterator_type base, Other... other) + : it{base, other...} {} + + template + requires (!stl::same_as && ...) && (stl::constructible_from && ...) + constexpr extended_storage_iterator(const extended_storage_iterator &other) + : it{other.it} {} + + constexpr extended_storage_iterator &operator++() noexcept { + return ++stl::get(it), (++stl::get(it), ...), *this; + } + + constexpr extended_storage_iterator operator++(int) noexcept { + const extended_storage_iterator orig = *this; + return ++(*this), orig; + } + + [[nodiscard]] constexpr pointer operator->() const noexcept { + return operator*(); + } + + [[nodiscard]] constexpr reference operator*() const noexcept { + return {*stl::get(it), *stl::get(it)...}; + } + + [[nodiscard]] constexpr iterator_type base() const noexcept { + return stl::get(it); + } + + template + [[nodiscard]] constexpr bool operator==(const extended_storage_iterator &other) const noexcept { + return stl::get<0>(it) == stl::get<0>(other.it); + } + +private: + stl::tuple it; +}; + +} // namespace internal +/*! @endcond */ + +/** + * @brief Storage implementation. + * + * Internal data structures arrange elements to maximize performance. There are + * no guarantees that objects are returned in the insertion order when iterate + * a storage. Do not make assumption on the order in any case. + * + * @warning + * Empty types aren't explicitly instantiated. Therefore, many of the functions + * normally available for non-empty types will not be available for empty ones. + * + * @tparam Type Element type. + * @tparam Entity A valid entity type. + * @tparam Allocator Type of allocator used to manage memory and elements. + */ +template +class basic_storage: public basic_sparse_set::template rebind_alloc> { + using alloc_traits = stl::allocator_traits; + static_assert(stl::is_same_v, "Invalid value type"); + using container_type = stl::vector>; + using underlying_type = basic_sparse_set>; + using underlying_iterator = underlying_type::basic_iterator; + using traits_type = component_traits; + + [[nodiscard]] auto &element_at(const stl::size_t pos) const { + return payload[pos / traits_type::page_size][fast_mod(pos, traits_type::page_size)]; + } + + auto assure_at_least(const stl::size_t pos) { + const auto idx = pos / traits_type::page_size; + + if(!(idx < payload.size())) { + auto curr = payload.size(); + allocator_type allocator{get_allocator()}; + payload.resize(idx + 1u, nullptr); + + ENTT_TRY { + for(const auto last = payload.size(); curr < last; ++curr) { + payload[curr] = alloc_traits::allocate(allocator, traits_type::page_size); + } + } + ENTT_CATCH { + payload.resize(curr); + ENTT_THROW; + } + } + + return payload[idx] + fast_mod(pos, traits_type::page_size); + } + + template + auto emplace_element(const Entity entt, const bool force_back, Args &&...args) { + const auto it = base_type::try_emplace(entt, force_back); + + ENTT_TRY { + auto *elem = stl::to_address(assure_at_least(static_cast(it.index()))); + entt::uninitialized_construct_using_allocator(elem, get_allocator(), stl::forward(args)...); + } + ENTT_CATCH { + base_type::pop(it, it + 1u); + ENTT_THROW; + } + + return it; + } + + void shrink_to_size(const stl::size_t sz) { + const auto from = (sz + traits_type::page_size - 1u) / traits_type::page_size; + allocator_type allocator{get_allocator()}; + + if constexpr(!stl::is_trivially_destructible_v) { + for(auto pos = sz, length = base_type::size(); pos < length; ++pos) { + if constexpr(traits_type::in_place_delete) { + if(base_type::data()[pos] != tombstone) { + alloc_traits::destroy(allocator, stl::addressof(element_at(pos))); + } + } else { + alloc_traits::destroy(allocator, stl::addressof(element_at(pos))); + } + } + } + + for(auto pos = from, last = payload.size(); pos < last; ++pos) { + alloc_traits::deallocate(allocator, payload[pos], traits_type::page_size); + } + + payload.resize(from); + payload.shrink_to_fit(); + } + + void swap_at(const stl::size_t lhs, const stl::size_t rhs) { + using stl::swap; + swap(element_at(lhs), element_at(rhs)); + } + + void move_to(const stl::size_t lhs, const stl::size_t rhs) { + auto &elem = element_at(lhs); + allocator_type allocator{get_allocator()}; + entt::uninitialized_construct_using_allocator(stl::to_address(assure_at_least(rhs)), allocator, stl::move(elem)); + alloc_traits::destroy(allocator, stl::addressof(elem)); + } + +private: + [[nodiscard]] const void *get_at(const stl::size_t pos) const final { + return stl::addressof(element_at(pos)); + } + + void swap_or_move([[maybe_unused]] const stl::size_t from, [[maybe_unused]] const stl::size_t to) override { + static constexpr bool is_pinned_type = !(stl::is_move_constructible_v && stl::is_move_assignable_v); + // use a runtime value to avoid compile-time suppression that drives the code coverage tool crazy + ENTT_ASSERT((from + 1u) && !is_pinned_type, "Pinned type"); + + if constexpr(!is_pinned_type) { + if constexpr(traits_type::in_place_delete) { + (base_type::operator[](to) == tombstone) ? move_to(from, to) : swap_at(from, to); + } else { + swap_at(from, to); + } + } + } + +protected: + /** + * @brief Erases entities from a 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. + */ + void pop(underlying_iterator first, underlying_iterator last) override { + for(allocator_type allocator{get_allocator()}; first != last; ++first) { + // cannot use first.index() because it would break with cross iterators + auto &elem = element_at(base_type::index(*first)); + + if constexpr(traits_type::in_place_delete) { + base_type::in_place_pop(*first); + alloc_traits::destroy(allocator, stl::addressof(elem)); + } else if constexpr(stl::is_trivially_destructible_v) { + elem = stl::move(element_at(base_type::size() - 1u)); + base_type::swap_and_pop(*first); + } else { + auto &other = element_at(base_type::size() - 1u); + // destroying on exit allows reentrant destructors + [[maybe_unused]] auto unused = stl::exchange(elem, stl::move(other)); + alloc_traits::destroy(allocator, stl::addressof(other)); + base_type::swap_and_pop(*first); + } + } + } + + /*! @brief Erases all entities of a storage. */ + void pop_all() override { + if constexpr(stl::is_trivially_destructible_v) { + base_type::pop_all(); + } else { + allocator_type allocator{get_allocator()}; + + for(auto first = base_type::begin(); !(first.index() < 0); ++first) { + if constexpr(traits_type::in_place_delete) { + if(*first != tombstone) { + base_type::in_place_pop(*first); + alloc_traits::destroy(allocator, stl::addressof(element_at(static_cast(first.index())))); + } + } else { + base_type::swap_and_pop(*first); + alloc_traits::destroy(allocator, stl::addressof(element_at(static_cast(first.index())))); + } + } + } + } + + /** + * @brief Assigns an entity to a storage. + * @param entt A valid identifier. + * @param value Optional opaque value. + * @param force_back Force back insertion. + * @return Iterator pointing to the emplaced element. + */ + underlying_iterator try_emplace([[maybe_unused]] const Entity entt, [[maybe_unused]] const bool force_back, const void *value) override { + if(value != nullptr) { + if constexpr(stl::is_copy_constructible_v) { + return emplace_element(entt, force_back, *static_cast(value)); + } else { + return base_type::end(); + } + } else { + if constexpr(stl::is_default_constructible_v) { + return emplace_element(entt, force_back); + } else { + return base_type::end(); + } + } + } + +public: + /*! @brief Allocator type. */ + using allocator_type = Allocator; + /*! @brief Base type. */ + using base_type = underlying_type; + /*! @brief Element type. */ + using element_type = Type; + /*! @brief Type of the objects assigned to entities. */ + using value_type = element_type; + /*! @brief Underlying entity identifier. */ + using entity_type = Entity; + /*! @brief Unsigned integer type. */ + using size_type = stl::size_t; + /*! @brief Signed integer type. */ + using difference_type = stl::ptrdiff_t; + /*! @brief Pointer type to contained elements. */ + using pointer = container_type::pointer; + /*! @brief Constant pointer type to contained elements. */ + using const_pointer = alloc_traits::template rebind_traits::const_pointer; + /*! @brief Random access iterator type. */ + using iterator = internal::storage_iterator; + /*! @brief Constant random access iterator type. */ + using const_iterator = internal::storage_iterator; + /*! @brief Reverse iterator type. */ + using reverse_iterator = stl::reverse_iterator; + /*! @brief Constant reverse iterator type. */ + using const_reverse_iterator = stl::reverse_iterator; + /*! @brief Extended iterable storage proxy. */ + using iterable = iterable_adaptor>; + /*! @brief Constant extended iterable storage proxy. */ + using const_iterable = iterable_adaptor>; + /*! @brief Extended reverse iterable storage proxy. */ + using reverse_iterable = iterable_adaptor>; + /*! @brief Constant extended reverse iterable storage proxy. */ + using const_reverse_iterable = iterable_adaptor>; + /*! @brief Storage deletion policy. */ + static constexpr deletion_policy storage_policy{traits_type::in_place_delete}; + + /*! @brief Default constructor. */ + basic_storage() + : basic_storage{allocator_type{}} {} + + /** + * @brief Constructs an empty storage with a given allocator. + * @param allocator The allocator to use. + */ + explicit basic_storage(const allocator_type &allocator) + : base_type{type_id(), storage_policy, allocator}, + payload{allocator} {} + + /*! @brief Default copy constructor, deleted on purpose. */ + basic_storage(const basic_storage &) = delete; + + /** + * @brief Move constructor. + * @param other The instance to move from. + */ + basic_storage(basic_storage &&other) noexcept + : base_type{static_cast(other)}, + payload{stl::move(other.payload)} {} + + /** + * @brief Allocator-extended move constructor. + * @param other The instance to move from. + * @param allocator The allocator to use. + */ + basic_storage(basic_storage &&other, const allocator_type &allocator) + : base_type{static_cast(other), allocator}, + payload{stl::move(other.payload), allocator} { + ENTT_ASSERT(alloc_traits::is_always_equal::value || get_allocator() == other.get_allocator(), "Copying a storage is not allowed"); + } + + /*! @brief Default destructor. */ + // NOLINTNEXTLINE(bugprone-exception-escape) + ~basic_storage() override { + shrink_to_size(0u); + } + + /** + * @brief Default copy assignment operator, deleted on purpose. + * @return This storage. + */ + basic_storage &operator=(const basic_storage &) = delete; + + /** + * @brief Move assignment operator. + * @param other The instance to move from. + * @return This storage. + */ + basic_storage &operator=(basic_storage &&other) noexcept { + ENTT_ASSERT(alloc_traits::is_always_equal::value || get_allocator() == other.get_allocator(), "Copying a storage is not allowed"); + 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_storage &other) noexcept { + using stl::swap; + swap(payload, other.payload); + base_type::swap(other); + } + + /** + * @brief Returns the associated allocator. + * @return The associated allocator. + */ + [[nodiscard]] constexpr allocator_type get_allocator() const noexcept { + return payload.get_allocator(); + } + + /** + * @brief Increases the capacity of a storage. + * + * 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) override { + if(cap != 0u) { + base_type::reserve(cap); + assure_at_least(cap - 1u); + } + } + + /** + * @brief Returns the number of elements that a storage has currently + * allocated space for. + * @return Capacity of the storage. + */ + [[nodiscard]] size_type capacity() const noexcept override { + return payload.size() * traits_type::page_size; + } + + /*! @brief Requests the removal of unused capacity. */ + void shrink_to_fit() override { + base_type::shrink_to_fit(); + shrink_to_size(base_type::size()); + } + + /** + * @brief Direct access to the array of objects. + * @return A pointer to the array of objects. + */ + [[nodiscard]] const_pointer raw() const noexcept { + return payload.data(); + } + + /*! @copydoc raw */ + [[nodiscard]] pointer raw() noexcept { + return payload.data(); + } + + /** + * @brief Returns an iterator to the beginning. + * + * If the storage 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 { + const auto pos = static_cast(base_type::size()); + return const_iterator{&payload, pos}; + } + + /*! @copydoc cbegin */ + [[nodiscard]] const_iterator begin() const noexcept { + return cbegin(); + } + + /*! @copydoc begin */ + [[nodiscard]] iterator begin() noexcept { + const auto pos = static_cast(base_type::size()); + return iterator{&payload, pos}; + } + + /** + * @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 const_iterator{&payload, {}}; + } + + /*! @copydoc cend */ + [[nodiscard]] const_iterator end() const noexcept { + return cend(); + } + + /*! @copydoc end */ + [[nodiscard]] iterator end() noexcept { + return iterator{&payload, {}}; + } + + /** + * @brief Returns a reverse iterator to the beginning. + * + * If the storage 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 Returns the object assigned to an entity. + * + * @warning + * Attempting to use an entity that doesn't belong to the storage results in + * undefined behavior. + * + * @param entt A valid identifier. + * @return The object assigned to the entity. + */ + [[nodiscard]] const value_type &get(const entity_type entt) const noexcept { + return element_at(base_type::index(entt)); + } + + /*! @copydoc get */ + [[nodiscard]] value_type &get(const entity_type entt) noexcept { + return const_cast(stl::as_const(*this).get(entt)); + } + + /** + * @brief Returns the object assigned to an entity as a tuple. + * @param entt A valid identifier. + * @return The object assigned to the entity as a tuple. + */ + [[nodiscard]] stl::tuple get_as_tuple(const entity_type entt) const noexcept { + return stl::forward_as_tuple(get(entt)); + } + + /*! @copydoc get_as_tuple */ + [[nodiscard]] stl::tuple get_as_tuple(const entity_type entt) noexcept { + return stl::forward_as_tuple(get(entt)); + } + + /** + * @brief Assigns an entity to a storage and constructs its object. + * + * @warning + * Attempting to use an entity that already belongs to the storage results + * in undefined behavior. + * + * @tparam Args Types of arguments to use to construct the object. + * @param entt A valid identifier. + * @param args Parameters to use to construct an object for the entity. + * @return A reference to the newly created object. + */ + template + value_type &emplace(const entity_type entt, Args &&...args) { + if constexpr(stl::is_aggregate_v && (sizeof...(Args) != 0u || !stl::is_default_constructible_v)) { + const auto it = emplace_element(entt, false, Type{stl::forward(args)...}); + return element_at(static_cast(it.index())); + } else { + const auto it = emplace_element(entt, false, stl::forward(args)...); + return element_at(static_cast(it.index())); + } + } + + /** + * @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 updated instance. + */ + template + value_type &patch(const entity_type entt, Func &&...func) { + const auto idx = base_type::index(entt); + auto &elem = element_at(idx); + (stl::forward(func)(elem), ...); + return elem; + } + + /** + * @brief Assigns one or more entities to a storage and constructs their + * objects from a given instance. + * + * @warning + * Attempting to assign an entity that already belongs to the storage + * results in undefined behavior. + * + * @param first An iterator to the first element of the range of entities. + * @param last An iterator past the last element of the range of entities. + * @param value An instance of the object to construct. + * @return Iterator pointing to the first element inserted, if any. + */ + iterator insert(stl::input_iterator auto first, stl::input_iterator auto last, const value_type &value = {}) { + for(; first != last; ++first) { + emplace_element(*first, true, value); + } + + return begin(); + } + + /** + * @brief Assigns one or more entities to a storage and constructs their + * objects from a given range. + * + * @sa construct + * + * @tparam It Type of input iterator. + * @param first An iterator to the first element of the range of entities. + * @param last An iterator past the last element of the range of entities. + * @param from An iterator to the first element of the range of objects. + * @return Iterator pointing to the first element inserted, if any. + */ + template + requires stl::same_as::value_type, value_type> + iterator insert(stl::input_iterator auto first, stl::input_iterator auto last, It from) { + for(; first != last; ++first, ++from) { + emplace_element(*first, true, *from); + } + + return begin(); + } + + /** + * @brief Returns an iterable object to use to _visit_ a storage. + * + * The iterable object returns a tuple that contains the current entity and + * a reference to its element. + * + * @return An iterable object to use to _visit_ the storage. + */ + [[nodiscard]] iterable each() noexcept { + return iterable{{base_type::begin(), begin()}, {base_type::end(), end()}}; + } + + /*! @copydoc each */ + [[nodiscard]] const_iterable each() const noexcept { + return const_iterable{{base_type::cbegin(), cbegin()}, {base_type::cend(), cend()}}; + } + + /** + * @brief Returns a reverse iterable object to use to _visit_ a storage. + * + * @sa each + * + * @return A reverse iterable object to use to _visit_ the storage. + */ + [[nodiscard]] reverse_iterable reach() noexcept { + return reverse_iterable{{base_type::rbegin(), rbegin()}, {base_type::rend(), rend()}}; + } + + /*! @copydoc reach */ + [[nodiscard]] const_reverse_iterable reach() const noexcept { + return const_reverse_iterable{{base_type::crbegin(), crbegin()}, {base_type::crend(), crend()}}; + } + +private: + container_type payload; +}; + +/*! @copydoc basic_storage */ +template +requires (component_traits::page_size == 0u) +class basic_storage + : public basic_sparse_set::template rebind_alloc> { + using alloc_traits = stl::allocator_traits; + static_assert(stl::is_same_v, "Invalid value type"); + using traits_type = component_traits; + +public: + /*! @brief Allocator type. */ + using allocator_type = Allocator; + /*! @brief Base type. */ + using base_type = basic_sparse_set>; + /*! @brief Element type. */ + using element_type = Type; + /*! @brief Type of the objects assigned to entities. */ + using value_type = void; + /*! @brief Underlying entity identifier. */ + using entity_type = Entity; + /*! @brief Unsigned integer type. */ + using size_type = stl::size_t; + /*! @brief Signed integer type. */ + using difference_type = stl::ptrdiff_t; + /*! @brief Extended iterable storage proxy. */ + using iterable = iterable_adaptor>; + /*! @brief Constant extended iterable storage proxy. */ + using const_iterable = iterable_adaptor>; + /*! @brief Extended reverse iterable storage proxy. */ + using reverse_iterable = iterable_adaptor>; + /*! @brief Constant extended reverse iterable storage proxy. */ + using const_reverse_iterable = iterable_adaptor>; + /*! @brief Storage deletion policy. */ + static constexpr deletion_policy storage_policy{traits_type::in_place_delete}; + + /*! @brief Default constructor. */ + basic_storage() + : basic_storage{allocator_type{}} {} + + /** + * @brief Constructs an empty container with a given allocator. + * @param allocator The allocator to use. + */ + explicit basic_storage(const allocator_type &allocator) + : base_type{type_id(), storage_policy, allocator} {} + + /*! @brief Default copy constructor, deleted on purpose. */ + basic_storage(const basic_storage &) = delete; + + /** + * @brief Move constructor. + * @param other The instance to move from. + */ + basic_storage(basic_storage &&other) noexcept = default; + + /** + * @brief Allocator-extended move constructor. + * @param other The instance to move from. + * @param allocator The allocator to use. + */ + basic_storage(basic_storage &&other, const allocator_type &allocator) + : base_type{stl::move(other), allocator} {} + + /*! @brief Default destructor. */ + ~basic_storage() override = default; + + /** + * @brief Default copy assignment operator, deleted on purpose. + * @return This storage. + */ + basic_storage &operator=(const basic_storage &) = delete; + + /** + * @brief Move assignment operator. + * @param other The instance to move from. + * @return This storage. + */ + basic_storage &operator=(basic_storage &&other) noexcept = default; + + /** + * @brief Returns the associated allocator. + * @return The associated allocator. + */ + [[nodiscard]] constexpr allocator_type get_allocator() const noexcept { + return allocator_type{base_type::get_allocator()}; + } + + /** + * @brief Returns the object assigned to an entity, that is `void`. + * + * @warning + * Attempting to use an entity that doesn't belong to the storage results in + * undefined behavior. + * + * @param entt A valid identifier. + */ + void get([[maybe_unused]] const entity_type entt) const noexcept { + ENTT_ASSERT(base_type::contains(entt), "Invalid entity"); + } + + /** + * @brief Returns an empty tuple. + * @param entt A valid identifier. + * @return Returns an empty tuple. + */ + [[nodiscard]] stl::tuple<> get_as_tuple([[maybe_unused]] const entity_type entt) const noexcept { + ENTT_ASSERT(base_type::contains(entt), "Invalid entity"); + return stl::tuple{}; + } + + /** + * @brief Assigns an entity to a storage and constructs its object. + * + * @warning + * Attempting to use an entity that already belongs to the storage results + * in undefined behavior. + * + * @param entt A valid identifier. + */ + void emplace(const entity_type entt, const auto &...) { + base_type::try_emplace(entt, false); + } + + /** + * @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. + */ + template + void patch([[maybe_unused]] const entity_type entt, Func &&...func) { + ENTT_ASSERT(base_type::contains(entt), "Invalid entity"); + (stl::forward(func)(), ...); + } + + /** + * @brief Assigns entities to a 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. + */ + void insert(stl::input_iterator auto first, stl::input_iterator auto last, const auto &...) { + for(; first != last; ++first) { + base_type::try_emplace(*first, true); + } + } + + /** + * @brief Returns an iterable object to use to _visit_ a storage. + * + * The iterable object returns a tuple that contains the current entity. + * + * @return An iterable object to use to _visit_ the storage. + */ + [[nodiscard]] iterable each() noexcept { + return iterable{base_type::begin(), base_type::end()}; + } + + /*! @copydoc each */ + [[nodiscard]] const_iterable each() const noexcept { + return const_iterable{base_type::cbegin(), base_type::cend()}; + } + + /** + * @brief Returns a reverse iterable object to use to _visit_ a storage. + * + * @sa each + * + * @return A reverse iterable object to use to _visit_ the storage. + */ + [[nodiscard]] reverse_iterable reach() noexcept { + return reverse_iterable{{base_type::rbegin()}, {base_type::rend()}}; + } + + /*! @copydoc reach */ + [[nodiscard]] const_reverse_iterable reach() const noexcept { + return const_reverse_iterable{{base_type::crbegin()}, {base_type::crend()}}; + } +}; + +/** + * @brief Swap-only entity storage specialization. + * @tparam Entity A valid entity type. + * @tparam Allocator Type of allocator used to manage memory and elements. + */ +template +class basic_storage + : public basic_sparse_set { + using alloc_traits = stl::allocator_traits; + static_assert(stl::is_same_v, "Invalid value type"); + using underlying_iterator = basic_sparse_set::basic_iterator; + using traits_type = entt_traits; + + auto from_placeholder() noexcept { + const auto entt = traits_type::combine(static_cast(placeholder), {}); + ENTT_ASSERT(entt != null, "No more entities available"); + placeholder += static_cast(entt != null); + return entt; + } + + auto next() noexcept { + entity_type entt = from_placeholder(); + + while(base_type::current(entt) != traits_type::to_version(tombstone) && entt != null) { + entt = from_placeholder(); + } + + return entt; + } + +protected: + /*! @brief Erases all entities of a storage. */ + void pop_all() override { + base_type::pop_all(); + placeholder = {}; + } + + /** + * @brief Assigns an entity to a storage. + * @param hint A valid identifier. + * @return Iterator pointing to the emplaced element. + */ + underlying_iterator try_emplace(const Entity hint, const bool, const void *) override { + return base_type::find(generate(hint)); + } + +public: + /*! @brief Allocator type. */ + using allocator_type = Allocator; + /*! @brief Base type. */ + using base_type = basic_sparse_set; + /*! @brief Element type. */ + using element_type = Entity; + /*! @brief Type of the objects assigned to entities. */ + using value_type = void; + /*! @brief Underlying entity identifier. */ + using entity_type = Entity; + /*! @brief Unsigned integer type. */ + using size_type = stl::size_t; + /*! @brief Signed integer type. */ + using difference_type = stl::ptrdiff_t; + /*! @brief Extended iterable storage proxy. */ + using iterable = iterable_adaptor>; + /*! @brief Constant extended iterable storage proxy. */ + using const_iterable = iterable_adaptor>; + /*! @brief Extended reverse iterable storage proxy. */ + using reverse_iterable = iterable_adaptor>; + /*! @brief Constant extended reverse iterable storage proxy. */ + using const_reverse_iterable = iterable_adaptor>; + /*! @brief Storage deletion policy. */ + static constexpr deletion_policy storage_policy = deletion_policy::swap_only; + + /*! @brief Default constructor. */ + basic_storage() + : basic_storage{allocator_type{}} { + } + + /** + * @brief Constructs an empty container with a given allocator. + * @param allocator The allocator to use. + */ + explicit basic_storage(const allocator_type &allocator) + : base_type{type_id(), storage_policy, allocator} {} + + /*! @brief Default copy constructor, deleted on purpose. */ + basic_storage(const basic_storage &) = delete; + + /** + * @brief Move constructor. + * @param other The instance to move from. + */ + // NOLINTNEXTLINE(cppcoreguidelines-rvalue-reference-param-not-moved) + basic_storage(basic_storage &&other) noexcept + : base_type{static_cast(other)}, + placeholder{other.placeholder} {} + + /** + * @brief Allocator-extended move constructor. + * @param other The instance to move from. + * @param allocator The allocator to use. + */ + // NOLINTNEXTLINE(cppcoreguidelines-rvalue-reference-param-not-moved) + basic_storage(basic_storage &&other, const allocator_type &allocator) + : base_type{static_cast(other), allocator}, + placeholder{other.placeholder} {} + + /*! @brief Default destructor. */ + ~basic_storage() override = default; + + /** + * @brief Default copy assignment operator, deleted on purpose. + * @return This storage. + */ + basic_storage &operator=(const basic_storage &) = delete; + + /** + * @brief Move assignment operator. + * @param other The instance to move from. + * @return This storage. + */ + basic_storage &operator=(basic_storage &&other) noexcept { + placeholder = other.placeholder; + base_type::operator=(stl::move(other)); + return *this; + } + + /** + * @brief Exchanges the contents with those of a given storage. + * @param other Storage to exchange the content with. + */ + void swap(basic_storage &other) noexcept { + using stl::swap; + swap(placeholder, other.placeholder); + base_type::swap(other); + } + + /** + * @brief Returns the object assigned to an entity, that is `void`. + * + * @warning + * Attempting to use an entity that doesn't belong to the storage results in + * undefined behavior. + * + * @param entt A valid identifier. + */ + void get([[maybe_unused]] const entity_type entt) const noexcept { + ENTT_ASSERT(base_type::index(entt) < base_type::free_list(), "The requested entity is not a live one"); + } + + /** + * @brief Returns an empty tuple. + * @param entt A valid identifier. + * @return Returns an empty tuple. + */ + [[nodiscard]] stl::tuple<> get_as_tuple([[maybe_unused]] const entity_type entt) const noexcept { + ENTT_ASSERT(base_type::index(entt) < base_type::free_list(), "The requested entity is not a live one"); + return stl::tuple{}; + } + + /** + * @brief Creates a new identifier or recycles a destroyed one. + * @return A valid identifier. + */ + entity_type generate() { + const auto len = base_type::free_list(); + const auto entt = (len == base_type::size()) ? next() : base_type::data()[len]; + return *base_type::try_emplace(entt, true); + } + + /** + * @brief Creates a new identifier or recycles a destroyed one. + * + * If the requested identifier isn't in use, the suggested one is used. + * Otherwise, a new identifier is returned. + * + * @param hint Required identifier. + * @return A valid identifier. + */ + entity_type generate(const entity_type hint) { + if(hint != null && hint != tombstone) { + if(const auto curr = traits_type::construct(traits_type::to_entity(hint), base_type::current(hint)); curr == tombstone || !(base_type::index(curr) < base_type::free_list())) { + return *base_type::try_emplace(hint, true); + } + } + + return generate(); + } + + /** + * @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 It> + void generate(It first, It last) { + for(const auto sz = base_type::size(); first != last && base_type::free_list() != sz; ++first) { + *first = *base_type::try_emplace(base_type::data()[base_type::free_list()], true); + } + + for(; first != last; ++first) { + *first = *base_type::try_emplace(next(), true); + } + } + + /** + * @brief Updates a given identifier. + * @tparam Func Types of the function objects to invoke. + * @param entt A valid identifier. + * @param func Valid function objects. + */ + template + void patch([[maybe_unused]] const entity_type entt, Func &&...func) { + ENTT_ASSERT(base_type::index(entt) < base_type::free_list(), "The requested entity is not a live one"); + (stl::forward(func)(), ...); + } + + /** + * @brief Returns an iterable object to use to _visit_ a storage. + * + * The iterable object returns a tuple that contains the current entity. + * + * @return An iterable object to use to _visit_ the storage. + */ + [[nodiscard]] iterable each() noexcept { + return stl::as_const(*this).each(); + } + + /*! @copydoc each */ + [[nodiscard]] const_iterable each() const noexcept { + const auto it = base_type::cend(); + const auto offset = static_cast(base_type::free_list()); + return const_iterable{it - offset, it}; + } + + /** + * @brief Returns a reverse iterable object to use to _visit_ a storage. + * + * @sa each + * + * @return A reverse iterable object to use to _visit_ the storage. + */ + [[nodiscard]] reverse_iterable reach() noexcept { + return stl::as_const(*this).reach(); + } + + /*! @copydoc reach */ + [[nodiscard]] const_reverse_iterable reach() const noexcept { + const auto it = base_type::crbegin(); + const auto offset = static_cast(base_type::free_list()); + return const_reverse_iterable{it, it + offset}; + } + + /** + * @brief Sets the starting identifier for generation. + * + * The version is ignored, regardless of the value. + * + * @param hint A valid identifier. + */ + void start_from(const entity_type hint) { + placeholder = static_cast(traits_type::to_entity(hint)); + } + +private: + size_type placeholder{}; +}; + +} // namespace entt + +#endif diff --git a/include/entt/entity/view.hpp b/include/entt/entity/view.hpp new file mode 100644 index 0000000..feb54c4 --- /dev/null +++ b/include/entt/entity/view.hpp @@ -0,0 +1,1142 @@ +#ifndef ENTT_ENTITY_VIEW_HPP +#define ENTT_ENTITY_VIEW_HPP + +#include "../config/config.h" +#include "../core/concepts.hpp" +#include "../core/iterator.hpp" +#include "../core/type_traits.hpp" +#include "../stl/array.hpp" +#include "../stl/concepts.hpp" +#include "../stl/cstddef.hpp" +#include "../stl/iterator.hpp" +#include "../stl/tuple.hpp" +#include "../stl/type_traits.hpp" +#include "../stl/utility.hpp" +#include "entity.hpp" +#include "fwd.hpp" + +namespace entt { + +/*! @cond ENTT_INTERNAL */ +namespace internal { + +template +// NOLINTNEXTLINE(misc-redundant-expression) +static constexpr bool tombstone_check_v = ((sizeof...(Type) == 1u) && ... && (Type::storage_policy == deletion_policy::in_place)); + +template +const Type *view_placeholder() { + static const Type placeholder{}; + return &placeholder; +} + +[[nodiscard]] bool all_of(auto first, const auto last, const auto entt) noexcept { + for(; (first != last) && (*first)->contains(entt); ++first) {} + return first == last; +} + +[[nodiscard]] bool none_of(auto first, const auto last, const auto entt) noexcept { + for(; (first != last) && !(*first)->contains(entt); ++first) {} + return first == last; +} + +template +[[nodiscard]] bool fully_initialized(It first, const It last, const stl::remove_pointer_t::value_type> *placeholder) noexcept { + for(; (first != last) && *first != placeholder; ++first) {} + return first == last; +} + +template +[[nodiscard]] Result view_pack(const View &view, const Other &other, stl::index_sequence, stl::index_sequence, stl::index_sequence, stl::index_sequence) { + Result elem{}; + // friend-initialization, avoid multiple calls to refresh + elem.pools = {view.template storage()..., other.template storage()...}; + [[maybe_unused]] const auto filter_or_placeholder = [placeholder = elem.placeholder](auto *value) { return (value == nullptr) ? placeholder : value; }; + elem.filter = {filter_or_placeholder(view.template storage())..., filter_or_placeholder(other.template storage())...}; + elem.refresh(); + return elem; +} + +template +class view_iterator final { + template + friend struct extended_view_iterator; + + using iterator_type = Type::const_iterator; + using iterator_traits = stl::iterator_traits; + + [[nodiscard]] bool valid(const iterator_traits::value_type entt) const noexcept { + return (!Checked || (entt != tombstone)) + && ((Get == 1u) || (internal::all_of(pools.begin(), pools.begin() + index, entt) && internal::all_of(pools.begin() + index + 1, pools.end(), entt))) + && ((Exclude == 0u) || internal::none_of(filter.begin(), filter.end(), entt)); + } + + void seek_next() { + for(constexpr iterator_type sentinel{}; it != sentinel && !valid(*it); ++it) {} + } + +public: + using value_type = iterator_traits::value_type; + using pointer = iterator_traits::pointer; + using reference = iterator_traits::reference; + using difference_type = iterator_traits::difference_type; + using iterator_category = stl::forward_iterator_tag; + + constexpr view_iterator() noexcept + : it{}, + pools{}, + filter{}, + index{} {} + + view_iterator(iterator_type first, stl::array value, stl::array excl, const stl::size_t idx) noexcept + : it{first}, + pools{value}, + filter{excl}, + index{static_cast(idx)} { + ENTT_ASSERT((Get != 1u) || (Exclude != 0u) || pools[0u]->policy() == deletion_policy::in_place, "Non in-place storage view iterator"); + seek_next(); + } + + view_iterator &operator++() noexcept { + ++it; + seek_next(); + return *this; + } + + view_iterator operator++(int) noexcept { + const view_iterator orig = *this; + return ++(*this), orig; + } + + [[nodiscard]] pointer operator->() const noexcept { + return &*it; + } + + [[nodiscard]] reference operator*() const noexcept { + return *operator->(); + } + + template + [[nodiscard]] constexpr bool operator==(const view_iterator &other) const noexcept { + return it == other.it; + } + +private: + iterator_type it; + stl::array pools; + stl::array filter; + difference_type index; +}; + +template +struct extended_view_iterator final { + using iterator_type = It; + using value_type = decltype(stl::tuple_cat(stl::make_tuple(*stl::declval()), stl::declval().get_as_tuple({})...)); + using pointer = input_iterator_pointer; + using reference = value_type; + using difference_type = stl::ptrdiff_t; + using iterator_category = stl::input_iterator_tag; + using iterator_concept = stl::forward_iterator_tag; + + constexpr extended_view_iterator() + : it{} {} + + extended_view_iterator(iterator_type from) + : it{from} {} + + extended_view_iterator &operator++() noexcept { + return ++it, *this; + } + + extended_view_iterator operator++(int) noexcept { + const extended_view_iterator orig = *this; + return ++(*this), orig; + } + + [[nodiscard]] reference operator*() const noexcept { + return [this](stl::index_sequence) { + return stl::tuple_cat(stl::make_tuple(*it), static_cast(const_cast *>(stl::get(it.pools)))->get_as_tuple(*it)...); + }(stl::index_sequence_for{}); + } + + [[nodiscard]] pointer operator->() const noexcept { + return operator*(); + } + + [[nodiscard]] constexpr iterator_type base() const noexcept { + return it; + } + + template + [[nodiscard]] constexpr bool operator==(const extended_view_iterator &other) const noexcept { + return it == other.it; + } + +private: + It it; +}; + +} // namespace internal +/*! @endcond */ + +/** + * @brief View implementation. + * + * Primary template isn't defined on purpose. All the specializations give a + * compile-time error, but for a few reasonable cases. + * + * @b Important + * + * View iterators aren't invalidated if: + * + * * New elements are added to the storage iterated by the view. + * * The entity currently returned is modified (for example, elements are added + * or removed from it). + * * The entity currently returned is destroyed. + * + * In all other cases, modifying the storage iterated by a view in any way can + * invalidate all iterators. + */ +template +class basic_view; + +/** + * @brief Basic storage view implementation. + * @warning For internal use only, backward compatibility not guaranteed. + * @tparam Type Common type among all storage types. + * @tparam Checked True to enable the tombstone check, false otherwise. + * @tparam Get Number of storage iterated by the view. + * @tparam Exclude Number of storage used to filter the view. + */ +template +class basic_common_view { + template + friend Return internal::view_pack(const View &, const Other &, stl::index_sequence, stl::index_sequence, stl::index_sequence, stl::index_sequence); + + [[nodiscard]] auto offset() const noexcept { + ENTT_ASSERT(index != Get, "Invalid view"); + return (pools[index]->policy() == deletion_policy::swap_only) ? pools[index]->free_list() : pools[index]->size(); + } + + void unchecked_refresh() noexcept { + index = 0u; + + if constexpr(Get > 1u) { + for(size_type pos{1u}; pos < Get; ++pos) { + if(pools[pos]->size() < pools[index]->size()) { + index = pos; + } + } + } + } + +protected: + /*! @cond ENTT_INTERNAL */ + basic_common_view() noexcept { + for(size_type pos{}, last = filter.size(); pos < last; ++pos) { + filter[pos] = placeholder; + } + } + + basic_common_view(stl::array value, stl::array excl) noexcept + : pools{value}, + filter{excl}, + index{Get} { + unchecked_refresh(); + } + + [[nodiscard]] const Type *pool_at(const stl::size_t pos) const noexcept { + return pools[pos]; + } + + void pool_at(const stl::size_t pos, const Type *elem) noexcept { + ENTT_ASSERT(elem != nullptr, "Unexpected element"); + pools[pos] = elem; + refresh(); + } + + [[nodiscard]] const Type *filter_at(const stl::size_t pos) const noexcept { + return (filter[pos] == placeholder) ? nullptr : filter[pos]; + } + + void filter_at(const stl::size_t pos, const Type *elem) noexcept { + ENTT_ASSERT(elem != nullptr, "Unexpected element"); + filter[pos] = elem; + } + + [[nodiscard]] bool none_of(const Type::entity_type entt) const noexcept { + return internal::none_of(filter.begin(), filter.end(), entt); + } + + void use(const stl::size_t pos) noexcept { + index = (index != Get) ? pos : Get; + } + /*! @endcond */ + +public: + /*! @brief Common type among all storage types. */ + using common_type = Type; + /*! @brief Underlying entity identifier. */ + using entity_type = Type::entity_type; + /*! @brief Unsigned integer type. */ + using size_type = stl::size_t; + /*! @brief Signed integer type. */ + using difference_type = stl::ptrdiff_t; + /*! @brief Forward iterator type. */ + using iterator = internal::view_iterator; + + /*! @brief Updates the internal leading view if required. */ + void refresh() noexcept { + size_type pos = static_cast(index != Get) * Get; + for(; pos < Get && pools[pos] != nullptr; ++pos) {} + + if(pos == Get) { + unchecked_refresh(); + } + } + + /** + * @brief Returns the leading storage of a view, if any. + * @return The leading storage of the view. + */ + [[nodiscard]] const common_type *handle() const noexcept { + return (index != Get) ? pools[index] : nullptr; + } + + /** + * @brief Estimates the number of entities iterated by the view. + * @return Estimated number of entities iterated by the view. + */ + [[nodiscard]] size_type size_hint() const noexcept { + return (index != Get) ? offset() : size_type{}; + } + + /** + * @brief Returns an iterator to the first entity of the view. + * + * If the view is empty, the returned iterator will be equal to `end()`. + * + * @return An iterator to the first entity of the view. + */ + [[nodiscard]] iterator begin() const noexcept { + return (index != Get) ? iterator{pools[index]->end() - static_cast(offset()), pools, filter, index} : iterator{}; + } + + /** + * @brief Returns an iterator that is past the last entity of the view. + * @return An iterator to the entity following the last entity of the view. + */ + [[nodiscard]] iterator end() const noexcept { + return (index != Get) ? iterator{pools[index]->end(), pools, filter, index} : iterator{}; + } + + /** + * @brief Returns the first entity of the view, if any. + * @return The first entity of the view if one exists, the null entity + * otherwise. + */ + [[nodiscard]] entity_type front() const noexcept { + const auto it = begin(); + return it != end() ? *it : null; + } + + /** + * @brief Returns the last entity of the view, if any. + * @return The last entity of the view if one exists, the null entity + * otherwise. + */ + [[nodiscard]] entity_type back() const noexcept { + if(index != Get) { + auto it = pools[index]->rbegin(); + const auto last = it + static_cast(offset()); + for(const auto idx = static_cast(index); it != last && !(internal::all_of(pools.begin(), pools.begin() + idx, *it) && internal::all_of(pools.begin() + idx + 1, pools.end(), *it) && internal::none_of(filter.begin(), filter.end(), *it)); ++it) {} + return it == last ? null : *it; + } + + return null; + } + + /** + * @brief Finds an entity. + * @param entt A valid identifier. + * @return An iterator to the given entity if it's found, past the end + * iterator otherwise. + */ + [[nodiscard]] iterator find(const entity_type entt) const noexcept { + return contains(entt) ? iterator{pools[index]->find(entt), pools, filter, index} : end(); + } + + /** + * @brief Checks if a view is fully initialized. + * @return True if the view is fully initialized, false otherwise. + */ + [[nodiscard]] explicit operator bool() const noexcept { + return (index != Get) && internal::fully_initialized(filter.begin(), filter.end(), placeholder); + } + + /** + * @brief Checks if a view contains an entity. + * @param entt A valid identifier. + * @return True if the view contains the given entity, false otherwise. + */ + [[nodiscard]] bool contains(const entity_type entt) const noexcept { + return (index != Get) + && internal::all_of(pools.begin(), pools.end(), entt) + && internal::none_of(filter.begin(), filter.end(), entt) + && pools[index]->index(entt) < offset(); + } + +private: + stl::array pools{}; + stl::array filter{}; + const common_type *placeholder{internal::view_placeholder()}; + size_type index{Get}; +}; + +/** + * @brief General purpose view. + * + * This view visits all entities that are at least in the given storage. During + * initialization, it also looks at the number of elements available for each + * storage and uses the smallest set in order to get a performance boost. + * + * @sa basic_view + * + * @tparam Get Types of storage iterated by the view. + * @tparam Exclude Types of storage used to filter the view. + */ +template +requires (sizeof...(Get) != 0u) +class basic_view, exclude_t> + : public basic_common_view, internal::tombstone_check_v, sizeof...(Get), sizeof...(Exclude)> { + using base_type = basic_common_view, internal::tombstone_check_v, sizeof...(Get), sizeof...(Exclude)>; + + template + using element_at = type_list_element_t>; + + template + static constexpr stl::size_t index_of = type_list_index_v, type_list>; + + template + [[nodiscard]] auto dispatch_get(const stl::tuple &curr) const { + if constexpr(Curr == Other) { + return stl::forward_as_tuple(stl::get(curr)...); + } else { + return storage()->get_as_tuple(stl::get<0>(curr)); + } + } + + template + void each(Func func, stl::index_sequence) const { + for(const auto curr: storage()->each()) { + if(const auto entt = stl::get<0>(curr); (!internal::tombstone_check_v || (entt != tombstone)) && ((Curr == Index || base_type::pool_at(Index)->contains(entt)) && ...) && base_type::none_of(entt)) { + if constexpr(is_applicable_v{}, stl::declval().get({})))>) { + stl::apply(func, stl::tuple_cat(stl::make_tuple(entt), dispatch_get(curr)...)); + } else { + stl::apply(func, stl::tuple_cat(dispatch_get(curr)...)); + } + } + } + } + + template + void storage_if(Type *elem) noexcept { + if(elem != nullptr) { + storage>(*elem); + } + } + +public: + /*! @brief Common type among all storage types. */ + using common_type = base_type::common_type; + /*! @brief Underlying entity identifier. */ + using entity_type = base_type::entity_type; + /*! @brief Unsigned integer type. */ + using size_type = base_type::size_type; + /*! @brief Signed integer type. */ + using difference_type = stl::ptrdiff_t; + /*! @brief Forward iterator type. */ + using iterator = base_type::iterator; + /*! @brief Iterable view type. */ + using iterable = iterable_adaptor>; + + /*! @brief Default constructor to use to create empty, invalid views. */ + basic_view() noexcept + : base_type{} {} + + /** + * @brief Constructs a view from a set of storage classes. + * @param value The storage for the types to iterate. + * @param excl The storage for the types used to filter the view. + */ + basic_view(Get &...value, Exclude &...excl) noexcept + : base_type{{&value...}, {&excl...}} { + } + + /** + * @brief Constructs a view from a set of storage classes. + * @param value The storage for the types to iterate. + * @param excl The storage for the types used to filter the view. + */ + basic_view(stl::tuple value, stl::tuple excl = {}) noexcept + : basic_view{stl::make_from_tuple(stl::tuple_cat(value, excl))} {} + + /** + * @brief Constructs a view from a convertible counterpart. + * @tparam Args Storage types managed by the other view. + * @param other A view to convert from. + */ + template + requires (!stl::same_as>) + basic_view(const basic_view &other) noexcept + : basic_view{} { + (storage_if(other.template storage()), ...); + (storage_if(other.template storage()), ...); + } + + /** + * @brief Forces a view to use a given element to drive iterations + * @tparam Type Type of element to use to drive iterations. + */ + template + void use() noexcept { + use>(); + } + + /** + * @brief Forces a view to use a given element to drive iterations + * @tparam Index Index of the element to use to drive iterations. + */ + template + void use() noexcept { + base_type::use(Index); + } + + /** + * @brief Returns the storage for a given element type, if any. + * @tparam Type Type of element of which to return the storage. + * @return The storage for the given element type. + */ + template + [[nodiscard]] auto *storage() const noexcept { + return storage>(); + } + + /** + * @brief Returns the storage for a given index, if any. + * @tparam Index Index of the storage to return. + * @return The storage for the given index. + */ + template + [[nodiscard]] auto *storage() const noexcept { + if constexpr(Index < sizeof...(Get)) { + return static_cast *>(const_cast> *>(base_type::pool_at(Index))); + } else { + return static_cast *>(const_cast> *>(base_type::filter_at(Index - sizeof...(Get)))); + } + } + + /** + * @brief Assigns a storage to a view. + * @tparam Type Type of storage to assign to the view. + * @param elem A storage to assign to the view. + */ + template + void storage(Type &elem) noexcept { + storage>(elem); + } + + /** + * @brief Assigns a storage to a view. + * @tparam Index Index of the storage to assign to the view. + * @tparam Type Type of storage to assign to the view. + * @param elem A storage to assign to the view. + */ + template + void storage(Type &elem) noexcept { + static_assert(stl::is_convertible_v &>, "Unexpected type"); + + if constexpr(Index < sizeof...(Get)) { + base_type::pool_at(Index, &elem); + } else { + base_type::filter_at(Index - sizeof...(Get), &elem); + } + } + + /** + * @brief Returns the elements assigned to the given entity. + * @param entt A valid identifier. + * @return The elements assigned to the given entity. + */ + [[nodiscard]] decltype(auto) operator[](const entity_type entt) const { + return get(entt); + } + + /** + * @brief Returns the elements assigned to the given entity. + * @tparam Type Type of the element to get. + * @tparam Other Other types of elements to get. + * @param entt A valid identifier. + * @return The elements assigned to the entity. + */ + template + [[nodiscard]] decltype(auto) get(const entity_type entt) const { + return get, index_of...>(entt); + } + + /** + * @brief Returns the elements assigned to the given entity. + * @tparam Index Indexes of the elements to get. + * @param entt A valid identifier. + * @return The elements assigned to the entity. + */ + template + [[nodiscard]] decltype(auto) get(const entity_type entt) const { + if constexpr(sizeof...(Index) == 0) { + return [this, entt](stl::index_sequence) { + return stl::tuple_cat(this->storage()->get_as_tuple(entt)...); + }(stl::index_sequence_for{}); + } else if constexpr(sizeof...(Index) == 1) { + return (storage()->get(entt), ...); + } else { + return stl::tuple_cat(storage()->get_as_tuple(entt)...); + } + } + + /** + * @brief Iterates entities and elements and applies the given function + * object to them. + * + * The signature of the function must be equivalent to one of the following + * (non-empty types only, constness as requested): + * + * @code{.cpp} + * void(const entity_type, Type &...); + * void(Type &...); + * @endcode + * + * @tparam Func Type of the function object to invoke. + * @param func A valid function object. + */ + template + void each(Func func) const { + [this, &func](stl::index_sequence seq) { + if(const auto *view = base_type::handle(); view != nullptr) { + ((view == base_type::pool_at(Index) ? each(stl::move(func), seq) : void()), ...); + } + }(stl::index_sequence_for{}); + } + + /** + * @brief Returns an iterable object to use to _visit_ a view. + * + * The iterable object returns a tuple that contains the current entity and + * a set of references to its non-empty elements. The _constness_ of the + * elements is as requested. + * + * @return An iterable object to use to _visit_ the view. + */ + [[nodiscard]] iterable each() const noexcept { + return iterable{base_type::begin(), base_type::end()}; + } + + /** + * @brief Combines a view and a storage in _more specific_ view. + * @tparam OGet Type of storage to combine the view with. + * @param other The storage for the type to combine the view with. + * @return A more specific view. + */ + template OGet> + [[nodiscard]] basic_view, exclude_t> operator|(OGet &other) const noexcept { + return *this | basic_view, exclude_t<>>{other}; + } + + /** + * @brief Combines two views in a _more specific_ one. + * @tparam OGet Element list of the view to combine with. + * @tparam OExclude Filter list of the view to combine with. + * @param other The view to combine with. + * @return A more specific view. + */ + template... OGet, stl::derived_from... OExclude> + [[nodiscard]] auto operator|(const basic_view, exclude_t> &other) const noexcept { + return internal::view_pack, exclude_t>>( + *this, other, stl::index_sequence_for{}, stl::index_sequence_for{}, stl::index_sequence_for{}, stl::index_sequence_for{}); + } +}; + +/** + * @brief Basic storage view implementation. + * @warning For internal use only, backward compatibility not guaranteed. + * @tparam Type Common type among all storage types. + * @tparam Policy Storage policy. + */ +template +class basic_storage_view { +protected: + /*! @cond ENTT_INTERNAL */ + basic_storage_view() noexcept = default; + + basic_storage_view(const Type *value) noexcept + : leading{value} { + ENTT_ASSERT(leading->policy() == Policy, "Unexpected storage policy"); + } + /*! @endcond */ + +public: + /*! @brief Common type among all storage types. */ + using common_type = Type; + /*! @brief Underlying entity identifier. */ + using entity_type = common_type::entity_type; + /*! @brief Unsigned integer type. */ + using size_type = stl::size_t; + /*! @brief Signed integer type. */ + using difference_type = stl::ptrdiff_t; + /*! @brief Random access iterator type. */ + using iterator = stl::conditional_t, typename common_type::iterator>; + /*! @brief Reverse iterator type. */ + using reverse_iterator = stl::conditional_t; + + /** + * @brief Returns the leading storage of a view, if any. + * @return The leading storage of the view. + */ + [[nodiscard]] const common_type *handle() const noexcept { + return leading; + } + + /** + * @brief Returns the number of entities that have the given element. + * @return Number of entities that have the given element. + */ + [[nodiscard]] size_type size() const noexcept + requires (Policy != deletion_policy::in_place) { + if constexpr(Policy == deletion_policy::swap_and_pop) { + return leading ? leading->size() : size_type{}; + } else { + static_assert(Policy == deletion_policy::swap_only, "Unexpected storage policy"); + return leading ? leading->free_list() : size_type{}; + } + } + + /** + * @brief Estimates the number of entities iterated by the view. + * @return Estimated number of entities iterated by the view. + */ + [[nodiscard]] size_type size_hint() const noexcept + requires (Policy == deletion_policy::in_place) { + return leading ? leading->size() : size_type{}; + } + + /** + * @brief Checks whether a view is empty. + * @return True if the view is empty, false otherwise. + */ + [[nodiscard]] bool empty() const noexcept + requires (Policy != deletion_policy::in_place) { + if constexpr(Policy == deletion_policy::swap_and_pop) { + return !leading || leading->empty(); + } else { + static_assert(Policy == deletion_policy::swap_only, "Unexpected storage policy"); + return !leading || (leading->free_list() == 0u); + } + } + + /** + * @brief Returns an iterator to the first entity of the view. + * + * If the view is empty, the returned iterator will be equal to `end()`. + * + * @return An iterator to the first entity of the view. + */ + [[nodiscard]] iterator begin() const noexcept { + if constexpr(Policy == deletion_policy::swap_and_pop) { + return leading ? leading->begin() : iterator{}; + } else if constexpr(Policy == deletion_policy::swap_only) { + return leading ? (leading->end() - static_cast(leading->free_list())) : iterator{}; + } else { + static_assert(Policy == deletion_policy::in_place, "Unexpected storage policy"); + return leading ? iterator{leading->begin(), {leading}, {}, 0u} : iterator{}; + } + } + + /** + * @brief Returns an iterator that is past the last entity of the view. + * @return An iterator to the entity following the last entity of the view. + */ + [[nodiscard]] iterator end() const noexcept { + if constexpr(Policy == deletion_policy::swap_and_pop || Policy == deletion_policy::swap_only) { + return leading ? leading->end() : iterator{}; + } else { + static_assert(Policy == deletion_policy::in_place, "Unexpected storage policy"); + return leading ? iterator{leading->end(), {leading}, {}, 0u} : iterator{}; + } + } + + /** + * @brief Returns an iterator to the first entity of the reversed view. + * + * If the view is empty, the returned iterator will be equal to `rend()`. + * + * @return An iterator to the first entity of the reversed view. + */ + [[nodiscard]] reverse_iterator rbegin() const noexcept + requires (Policy != deletion_policy::in_place) { + return leading ? leading->rbegin() : reverse_iterator{}; + } + + /** + * @brief Returns an iterator that is past the last entity of the reversed + * view. + * @return An iterator to the entity following the last entity of the + * reversed view. + */ + [[nodiscard]] reverse_iterator rend() const noexcept + requires (Policy != deletion_policy::in_place) { + if constexpr(Policy == deletion_policy::swap_and_pop) { + return leading ? leading->rend() : reverse_iterator{}; + } else { + static_assert(Policy == deletion_policy::swap_only, "Unexpected storage policy"); + return leading ? (leading->rbegin() + static_cast(leading->free_list())) : reverse_iterator{}; + } + } + + /** + * @brief Returns the first entity of the view, if any. + * @return The first entity of the view if one exists, the null entity + * otherwise. + */ + [[nodiscard]] entity_type front() const noexcept { + if constexpr(Policy == deletion_policy::swap_and_pop) { + return empty() ? null : *leading->begin(); + } else if constexpr(Policy == deletion_policy::swap_only) { + return empty() ? null : *(leading->end() - static_cast(leading->free_list())); + } else { + static_assert(Policy == deletion_policy::in_place, "Unexpected storage policy"); + const auto it = begin(); + return (it == end()) ? null : *it; + } + } + + /** + * @brief Returns the last entity of the view, if any. + * @return The last entity of the view if one exists, the null entity + * otherwise. + */ + [[nodiscard]] entity_type back() const noexcept { + if constexpr(Policy == deletion_policy::swap_and_pop || Policy == deletion_policy::swap_only) { + return empty() ? null : *leading->rbegin(); + } else { + static_assert(Policy == deletion_policy::in_place, "Unexpected storage policy"); + + if(leading) { + auto it = leading->rbegin(); + const auto last = leading->rend(); + for(; (it != last) && (*it == tombstone); ++it) {} + return it == last ? null : *it; + } + + return null; + } + } + + /** + * @brief Finds an entity. + * @param entt A valid identifier. + * @return An iterator to the given entity if it's found, past the end + * iterator otherwise. + */ + [[nodiscard]] iterator find(const entity_type entt) const noexcept { + if constexpr(Policy == deletion_policy::swap_and_pop) { + return leading ? leading->find(entt) : iterator{}; + } else if constexpr(Policy == deletion_policy::swap_only) { + const auto it = leading ? leading->find(entt) : iterator{}; + return leading && (static_cast(it.index()) < leading->free_list()) ? it : iterator{}; + } else { + return leading ? iterator{leading->find(entt), {leading}, {}, 0u} : iterator{}; + } + } + + /** + * @brief Checks if a view is fully initialized. + * @return True if the view is fully initialized, false otherwise. + */ + [[nodiscard]] explicit operator bool() const noexcept { + return (leading != nullptr); + } + + /** + * @brief Checks if a view contains an entity. + * @param entt A valid identifier. + * @return True if the view contains the given entity, false otherwise. + */ + [[nodiscard]] bool contains(const entity_type entt) const noexcept { + if constexpr(Policy == deletion_policy::swap_and_pop || Policy == deletion_policy::in_place) { + return leading && leading->contains(entt); + } else { + static_assert(Policy == deletion_policy::swap_only, "Unexpected storage policy"); + return leading && leading->contains(entt) && (leading->index(entt) < leading->free_list()); + } + } + +private: + const common_type *leading{}; +}; + +/** + * @brief Storage view specialization. + * + * This specialization offers a boost in terms of performance. It can access the + * underlying data structure directly and avoid superfluous checks. + * + * @sa basic_view + * + * @tparam Get Type of storage iterated by the view. + */ +template +class basic_view, exclude_t<>> + : public basic_storage_view { + using base_type = basic_storage_view; + + void storage_if(Get *value) noexcept { + if(value != nullptr) { + storage(*value); + } + } + +public: + /*! @brief Common type among all storage types. */ + using common_type = base_type::common_type; + /*! @brief Underlying entity identifier. */ + using entity_type = base_type::entity_type; + /*! @brief Unsigned integer type. */ + using size_type = base_type::size_type; + /*! @brief Signed integer type. */ + using difference_type = stl::ptrdiff_t; + /*! @brief Random access iterator type. */ + using iterator = base_type::iterator; + /*! @brief Reverse iterator type. */ + using reverse_iterator = base_type::reverse_iterator; + /*! @brief Iterable view type. */ + using iterable = stl::conditional_t>, decltype(stl::declval().each())>; + + /*! @brief Default constructor to use to create empty, invalid views. */ + basic_view() noexcept + : base_type{} {} + + /** + * @brief Constructs a view from a storage class. + * @param value The storage for the type to iterate. + */ + basic_view(Get &value) noexcept + : base_type{&value} { + } + + /** + * @brief Constructs a view from a storage class. + * @param value The storage for the type to iterate. + */ + basic_view(stl::tuple value, stl::tuple<> = {}) noexcept + : basic_view{stl::get<0>(value)} {} + + /** + * @brief Constructs a view from a convertible counterpart. + * @tparam Args Storage types managed by the other view. + * @param other A view to convert from. + */ + template + requires (!stl::same_as>) + basic_view(const basic_view &other) noexcept + : base_type{} { + storage_if(other.template storage()); + } + + /** + * @brief Returns the storage for a given element type, if any. + * @tparam Type Type of element of which to return the storage. + * @return The storage for the given element type. + */ + template + [[nodiscard]] auto *storage() const noexcept { + static_assert(stl::is_same_v, typename Get::element_type>, "Invalid element type"); + return storage<0>(); + } + + /** + * @brief Returns the storage for a given index, if any. + * @tparam Index Index of the storage to return. + * @return The storage for the given index. + */ + template + [[nodiscard]] auto *storage() const noexcept { + static_assert(Index == 0u, "Index out of bounds"); + return static_cast(const_cast *>(base_type::handle())); + } + + /** + * @brief Assigns a storage to a view. + * @param elem A storage to assign to the view. + */ + void storage(Get &elem) noexcept { + storage<0>(elem); + } + + /** + * @brief Assigns a storage to a view. + * @tparam Index Index of the storage to assign to the view. + * @param elem A storage to assign to the view. + */ + template + void storage(Get &elem) noexcept { + static_assert(Index == 0u, "Index out of bounds"); + *this = basic_view{elem}; + } + + /** + * @brief Returns a pointer to the underlying storage. + * @return A pointer to the underlying storage. + */ + [[nodiscard]] Get *operator->() const noexcept { + return storage(); + } + + /** + * @brief Returns the element assigned to the given entity. + * @param entt A valid identifier. + * @return The element assigned to the given entity. + */ + [[nodiscard]] decltype(auto) operator[](const entity_type entt) const { + return storage()->get(entt); + } + + /** + * @brief Returns the element assigned to the given entity. + * @tparam Elem Type of the element to get. + * @param entt A valid identifier. + * @return The element assigned to the entity. + */ + template + [[nodiscard]] decltype(auto) get(const entity_type entt) const { + static_assert(stl::is_same_v, typename Get::element_type>, "Invalid element type"); + return get<0>(entt); + } + + /** + * @brief Returns the element assigned to the given entity. + * @tparam Index Index of the element to get. + * @param entt A valid identifier. + * @return The element assigned to the entity. + */ + template + [[nodiscard]] decltype(auto) get(const entity_type entt) const { + if constexpr(sizeof...(Index) == 0) { + return storage()->get_as_tuple(entt); + } else { + return storage()->get(entt); + } + } + + /** + * @brief Iterates entities and elements and applies the given function + * object to them. + * + * The signature of the function must be equivalent to one of the following + * (non-empty types only, constness as requested): + * + * @code{.cpp} + * void(const entity_type, Type &); + * void(Type &); + * @endcode + * + * @tparam Func Type of the function object to invoke. + * @param func A valid function object. + */ + template + void each(Func func) const { + if constexpr(is_applicable_v{}, stl::declval().get({})))>) { + for(const auto pack: each()) { + stl::apply(func, pack); + } + } else if constexpr(Get::storage_policy == deletion_policy::swap_and_pop || Get::storage_policy == deletion_policy::swap_only) { + if constexpr(stl::is_void_v) { + for(size_type pos = base_type::size(); pos; --pos) { + func(); + } + } else { + if(const auto len = static_cast(base_type::size()); len != 0) { + for(auto last = storage()->end(), first = last - len; first != last; ++first) { + func(*first); + } + } + } + } else { + static_assert(Get::storage_policy == deletion_policy::in_place, "Unexpected storage policy"); + + for(const auto pack: each()) { + stl::apply([&func](const auto, auto &&...elem) { func(stl::forward(elem)...); }, pack); + } + } + } + + /** + * @brief Returns an iterable object to use to _visit_ a view. + * + * The iterable object returns a tuple that contains the current entity and + * a reference to its element if it's a non-empty one. The _constness_ of + * the element is as requested. + * + * @return An iterable object to use to _visit_ the view. + */ + [[nodiscard]] iterable each() const noexcept { + if constexpr(Get::storage_policy == deletion_policy::swap_and_pop || Get::storage_policy == deletion_policy::swap_only) { + return base_type::handle() ? storage()->each() : iterable{}; + } else { + static_assert(Get::storage_policy == deletion_policy::in_place, "Unexpected storage policy"); + return iterable{base_type::begin(), base_type::end()}; + } + } + + /** + * @brief Combines a view and a storage in _more specific_ view. + * @tparam OGet Type of storage to combine the view with. + * @param other The storage for the type to combine the view with. + * @return A more specific view. + */ + template OGet> + [[nodiscard]] basic_view, exclude_t<>> operator|(OGet &other) const noexcept { + return *this | basic_view, exclude_t<>>{other}; + } + + /** + * @brief Combines two views in a _more specific_ one. + * @tparam OGet Element list of the view to combine with. + * @tparam OExclude Filter list of the view to combine with. + * @param other The view to combine with. + * @return A more specific view. + */ + template... OGet, stl::derived_from... OExclude> + [[nodiscard]] auto operator|(const basic_view, exclude_t> &other) const noexcept { + return internal::view_pack, exclude_t>>( + *this, other, stl::index_sequence_for{}, stl::index_sequence_for<>{}, stl::index_sequence_for{}, stl::index_sequence_for{}); + } +}; + +/** + * @brief Deduction guide. + * @tparam Type Type of storage classes used to create the view. + * @param storage The storage for the types to iterate. + */ +template +basic_view(Type &...storage) -> basic_view, exclude_t<>>; + +/** + * @brief Deduction guide. + * @tparam Get Types of elements iterated by the view. + * @tparam Exclude Types of elements used to filter the view. + */ +template +basic_view(stl::tuple, stl::tuple = {}) -> basic_view, exclude_t>; + +} // namespace entt + +#endif diff --git a/include/entt/entt.hpp b/include/entt/entt.hpp new file mode 100644 index 0000000..09bd14f --- /dev/null +++ b/include/entt/entt.hpp @@ -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 diff --git a/include/entt/fwd.hpp b/include/entt/fwd.hpp new file mode 100644 index 0000000..4b6e60c --- /dev/null +++ b/include/entt/fwd.hpp @@ -0,0 +1,11 @@ +// IWYU pragma: begin_exports +#include "container/fwd.hpp" +#include "core/fwd.hpp" +#include "entity/fwd.hpp" +#include "graph/fwd.hpp" +#include "meta/fwd.hpp" +#include "poly/fwd.hpp" +#include "process/fwd.hpp" +#include "resource/fwd.hpp" +#include "signal/fwd.hpp" +// IWYU pragma: end_exports diff --git a/include/entt/graph/adjacency_matrix.hpp b/include/entt/graph/adjacency_matrix.hpp new file mode 100644 index 0000000..ba83561 --- /dev/null +++ b/include/entt/graph/adjacency_matrix.hpp @@ -0,0 +1,332 @@ +#ifndef ENTT_GRAPH_ADJACENCY_MATRIX_HPP +#define ENTT_GRAPH_ADJACENCY_MATRIX_HPP + +#include "../config/config.h" +#include "../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" +#include "../stl/vector.hpp" +#include "fwd.hpp" + +namespace entt { + +/*! @cond ENTT_INTERNAL */ +namespace internal { + +template +class edge_iterator { + using size_type = stl::size_t; + + void find_next() noexcept { + for(; pos != last && !it[static_cast(pos)]; pos += offset) {} + } + +public: + using value_type = stl::pair; + using pointer = input_iterator_pointer; + 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 edge_iterator() noexcept = default; + + // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) + constexpr edge_iterator(It base, const size_type vertices, const size_type from, const size_type to, const size_type step) noexcept + : it{stl::move(base)}, + vert{vertices}, + pos{from}, + last{to}, + offset{step} { + find_next(); + } + + constexpr edge_iterator &operator++() noexcept { + pos += offset; + find_next(); + return *this; + } + + constexpr edge_iterator operator++(int) noexcept { + const edge_iterator orig = *this; + return ++(*this), orig; + } + + [[nodiscard]] constexpr reference operator*() const noexcept { + return *operator->(); + } + + [[nodiscard]] constexpr pointer operator->() const noexcept { + return stl::make_pair(pos / vert, pos % vert); + } + + [[nodiscard]] constexpr bool operator==(const edge_iterator &other) const noexcept { + return pos == other.pos; + } + +private: + It it{}; + size_type vert{}; + size_type pos{}; + size_type last{}; + size_type offset{}; +}; + +} // namespace internal +/*! @endcond */ + +/** + * @brief Basic implementation of a directed adjacency matrix. + * @tparam Category Either a directed or undirected category tag. + * @tparam Allocator Type of allocator used to manage memory and elements. + */ +template Category, typename Allocator> +class adjacency_matrix { + using alloc_traits = stl::allocator_traits; + static_assert(stl::is_same_v, "Invalid value type"); + using container_type = stl::vector>; + +public: + /*! @brief Allocator type. */ + using allocator_type = Allocator; + /*! @brief Unsigned integer type. */ + using size_type = stl::size_t; + /*! @brief Vertex type. */ + using vertex_type = size_type; + /*! @brief Edge type. */ + using edge_type = stl::pair; + /*! @brief Vertex iterator type. */ + using vertex_iterator = iota_iterator; + /*! @brief Edge iterator type. */ + using edge_iterator = internal::edge_iterator; + /*! @brief Out-edge iterator type. */ + using out_edge_iterator = edge_iterator; + /*! @brief In-edge iterator type. */ + using in_edge_iterator = edge_iterator; + /*! @brief Graph category tag. */ + using graph_category = Category; + + /*! @brief Default constructor. */ + adjacency_matrix() noexcept(noexcept(allocator_type{})) + : adjacency_matrix{0u} { + } + + /** + * @brief Constructs an empty container with a given allocator. + * @param allocator The allocator to use. + */ + explicit adjacency_matrix(const allocator_type &allocator) noexcept + : adjacency_matrix{0u, allocator} {} + + /** + * @brief Constructs an empty container with a given allocator and user + * supplied number of vertices. + * @param vertices Number of vertices. + * @param allocator The allocator to use. + */ + adjacency_matrix(const size_type vertices, const allocator_type &allocator = allocator_type{}) + : matrix{vertices * vertices, allocator}, + vert{vertices} {} + + /*! @brief Default copy constructor. */ + adjacency_matrix(const adjacency_matrix &) = default; + + /** + * @brief Allocator-extended copy constructor. + * @param other The instance to copy from. + * @param allocator The allocator to use. + */ + adjacency_matrix(const adjacency_matrix &other, const allocator_type &allocator) + : matrix{other.matrix, allocator}, + vert{other.vert} {} + + /*! @brief Default move constructor. */ + adjacency_matrix(adjacency_matrix &&) noexcept = default; + + /** + * @brief Allocator-extended move constructor. + * @param other The instance to move from. + * @param allocator The allocator to use. + */ + adjacency_matrix(adjacency_matrix &&other, const allocator_type &allocator) + : matrix{stl::move(other.matrix), allocator}, + vert{other.vert} {} + + /*! @brief Default destructor. */ + ~adjacency_matrix() = default; + + /** + * @brief Default copy assignment operator. + * @return This container. + */ + adjacency_matrix &operator=(const adjacency_matrix &) = default; + + /** + * @brief Default move assignment operator. + * @return This container. + */ + adjacency_matrix &operator=(adjacency_matrix &&) noexcept = default; + + /** + * @brief Exchanges the contents with those of a given adjacency matrix. + * @param other Adjacency matrix to exchange the content with. + */ + void swap(adjacency_matrix &other) noexcept { + using stl::swap; + swap(matrix, other.matrix); + swap(vert, other.vert); + } + + /** + * @brief Returns the associated allocator. + * @return The associated allocator. + */ + [[nodiscard]] constexpr allocator_type get_allocator() const noexcept { + return matrix.get_allocator(); + } + + /*! @brief Clears the adjacency matrix. */ + void clear() noexcept { + matrix.clear(); + vert = {}; + } + + /** + * @brief Returns true if an adjacency matrix is empty, false otherwise. + * + * @warning + * Potentially expensive, try to avoid it on hot paths. + * + * @return True if the adjacency matrix is empty, false otherwise. + */ + [[nodiscard]] bool empty() const noexcept { + const auto iterable = edges(); + return (iterable.begin() == iterable.end()); + } + + /** + * @brief Returns the number of vertices. + * @return The number of vertices. + */ + [[nodiscard]] size_type size() const noexcept { + return vert; + } + + /** + * @brief Returns an iterable object to visit all vertices of a matrix. + * @return An iterable object to visit all vertices of a matrix. + */ + [[nodiscard]] iterable_adaptor vertices() const noexcept { + return {0u, vert}; + } + + /** + * @brief Returns an iterable object to visit all edges of a matrix. + * @return An iterable object to visit all edges of a matrix. + */ + [[nodiscard]] iterable_adaptor edges() const noexcept { + const auto it = matrix.cbegin(); + const auto sz = matrix.size(); + return {{it, vert, 0u, sz, 1u}, {it, vert, sz, sz, 1u}}; + } + + /** + * @brief Returns an iterable object to visit all out-edges of a vertex. + * @param vertex The vertex of which to return all out-edges. + * @return An iterable object to visit all out-edges of a vertex. + */ + [[nodiscard]] iterable_adaptor out_edges(const vertex_type vertex) const noexcept { + const auto it = matrix.cbegin(); + const auto from = vertex * vert; + const auto to = from + vert; + return {{it, vert, from, to, 1u}, {it, vert, to, to, 1u}}; + } + + /** + * @brief Returns an iterable object to visit all in-edges of a vertex. + * @param vertex The vertex of which to return all in-edges. + * @return An iterable object to visit all in-edges of a vertex. + */ + [[nodiscard]] iterable_adaptor in_edges(const vertex_type vertex) const noexcept { + const auto it = matrix.cbegin(); + const auto from = vertex; + const auto to = vert * vert + from; + return {{it, vert, from, to, vert}, {it, vert, to, to, vert}}; + } + + /** + * @brief Resizes an adjacency matrix. + * @param vertices The new number of vertices. + */ + void resize(const size_type vertices) { + adjacency_matrix other{vertices, get_allocator()}; + + for(auto [lhs, rhs]: edges()) { + other.insert(lhs, rhs); + } + + other.swap(*this); + } + + /** + * @brief Inserts an edge into the adjacency matrix, if it does not exist. + * @param lhs The left hand vertex of the edge. + * @param rhs The right hand vertex of the edge. + * @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 insert(const vertex_type lhs, const vertex_type rhs) { + const auto pos = lhs * vert + rhs; + + if constexpr(stl::is_same_v) { + const auto rev = rhs * vert + lhs; + ENTT_ASSERT(matrix[pos] == matrix[rev], "Something went really wrong"); + matrix[rev] = 1u; + } + + const auto inserted = !stl::exchange(matrix[pos], 1u); + return {edge_iterator{matrix.cbegin(), vert, pos, matrix.size(), 1u}, inserted}; + } + + /** + * @brief Removes the edge associated with a pair of given vertices. + * @param lhs The left hand vertex of the edge. + * @param rhs The right hand vertex of the edge. + * @return Number of elements removed (either 0 or 1). + */ + size_type erase(const vertex_type lhs, const vertex_type rhs) { + const auto pos = lhs * vert + rhs; + + if constexpr(stl::is_same_v) { + const auto rev = rhs * vert + lhs; + ENTT_ASSERT(matrix[pos] == matrix[rev], "Something went really wrong"); + matrix[rev] = 0u; + } + + return stl::exchange(matrix[pos], 0u); + } + + /** + * @brief Checks if an adjacency matrix contains a given edge. + * @param lhs The left hand vertex of the edge. + * @param rhs The right hand vertex of the edge. + * @return True if there is such an edge, false otherwise. + */ + [[nodiscard]] bool contains(const vertex_type lhs, const vertex_type rhs) const { + const auto pos = lhs * vert + rhs; + return pos < matrix.size() && matrix[pos]; + } + +private: + container_type matrix; + size_type vert; +}; + +} // namespace entt + +#endif diff --git a/include/entt/graph/dot.hpp b/include/entt/graph/dot.hpp new file mode 100644 index 0000000..e8d237a --- /dev/null +++ b/include/entt/graph/dot.hpp @@ -0,0 +1,56 @@ +#ifndef ENTT_GRAPH_DOT_HPP +#define ENTT_GRAPH_DOT_HPP + +#include "../stl/concepts.hpp" +#include "../stl/ostream.hpp" +#include "fwd.hpp" + +namespace entt { + +/** + * @brief Outputs a graph in dot format. + * @tparam Graph Graph type, valid as long as it exposes edges and vertices. + * @param out A standard output stream. + * @param graph The graph to output. + * @param writer Vertex decorator object. + */ +template +requires stl::derived_from +void dot(stl::ostream &out, const Graph &graph, stl::invocable auto writer) { + if constexpr(stl::same_as) { + out << "graph{"; + } else { + out << "digraph{"; + } + + for(auto &&vertex: graph.vertices()) { + out << vertex << "["; + writer(out, vertex); + out << "];"; + } + + for(auto [lhs, rhs]: graph.edges()) { + if constexpr(stl::same_as) { + out << lhs << "--" << rhs << ";"; + } else { + out << lhs << "->" << rhs << ";"; + } + } + + out << "}"; +} + +/** + * @brief Outputs a graph in dot format. + * @tparam Graph Graph type, valid as long as it exposes edges and vertices. + * @param out A standard output stream. + * @param graph The graph to output. + */ +template +void dot(stl::ostream &out, const Graph &graph) { + return dot(out, graph, [](auto &&...) {}); +} + +} // namespace entt + +#endif diff --git a/include/entt/graph/flow.hpp b/include/entt/graph/flow.hpp new file mode 100644 index 0000000..df4372f --- /dev/null +++ b/include/entt/graph/flow.hpp @@ -0,0 +1,345 @@ +#ifndef ENTT_GRAPH_FLOW_HPP +#define ENTT_GRAPH_FLOW_HPP + +#include "../config/config.h" +#include "../container/dense_map.hpp" +#include "../container/dense_set.hpp" +#include "../core/compressed_pair.hpp" +#include "../core/fwd.hpp" +#include "../core/iterator.hpp" +#include "../stl/algorithm.hpp" +#include "../stl/concepts.hpp" +#include "../stl/cstddef.hpp" +#include "../stl/functional.hpp" +#include "../stl/iterator.hpp" +#include "../stl/memory.hpp" +#include "../stl/type_traits.hpp" +#include "../stl/utility.hpp" +#include "../stl/vector.hpp" +#include "adjacency_matrix.hpp" +#include "fwd.hpp" + +namespace entt { + +/** + * @brief Utility class for creating task graphs. + * @tparam Allocator Type of allocator used to manage memory and elements. + */ +template +class basic_flow { + using alloc_traits = stl::allocator_traits; + static_assert(stl::is_same_v, "Invalid value type"); + using task_container_type = dense_set, typename alloc_traits::template rebind_alloc>; + using ro_rw_container_type = stl::vector, typename alloc_traits::template rebind_alloc>>; + using deps_container_type = dense_map, typename alloc_traits::template rebind_alloc>>; + using adjacency_matrix_type = adjacency_matrix>; + + void emplace(const id_type res, const bool is_rw) { + ENTT_ASSERT(index.first() < vertices.size(), "Invalid node"); + + if(!deps.contains(res) && sync_on != vertices.size()) { + deps[res].emplace_back(sync_on, true); + } + + deps[res].emplace_back(index.first(), is_rw); + } + + void setup_graph(adjacency_matrix_type &matrix) const { + for(const auto &elem: deps) { + const auto last = elem.second.cend(); + auto it = elem.second.cbegin(); + + while(it != last) { + if(it->second) { + // rw item + if(auto curr = it++; it != last) { + if(it->second) { + matrix.insert(curr->first, it->first); + } else if(const auto next = stl::find_if(it, last, [](const auto &value) { return value.second; }); next != last) { + for(; it != next; ++it) { + matrix.insert(curr->first, it->first); + matrix.insert(it->first, next->first); + } + } else { + for(; it != next; ++it) { + matrix.insert(curr->first, it->first); + } + } + } + } else { + // ro item (first iteration only) + if(const auto next = stl::find_if(it, last, [](const auto &value) { return value.second; }); next != last) { + for(; it != next; ++it) { + matrix.insert(it->first, next->first); + } + } else { + it = last; + } + } + } + } + } + + void transitive_closure(adjacency_matrix_type &matrix) const { + const auto length = matrix.size(); + + for(stl::size_t vk{}; vk < length; ++vk) { + for(stl::size_t vi{}; vi < length; ++vi) { + for(stl::size_t vj{}; vj < length; ++vj) { + if(matrix.contains(vi, vk) && matrix.contains(vk, vj)) { + matrix.insert(vi, vj); + } + } + } + } + } + + void transitive_reduction(adjacency_matrix_type &matrix) const { + const auto length = matrix.size(); + + for(stl::size_t vert{}; vert < length; ++vert) { + matrix.erase(vert, vert); + } + + for(stl::size_t vj{}; vj < length; ++vj) { + for(stl::size_t vi{}; vi < length; ++vi) { + if(matrix.contains(vi, vj)) { + for(stl::size_t vk{}; vk < length; ++vk) { + if(matrix.contains(vj, vk)) { + matrix.erase(vi, vk); + } + } + } + } + } + } + +public: + /*! @brief Allocator type. */ + using allocator_type = Allocator; + /*! @brief Unsigned integer type. */ + using size_type = stl::size_t; + /*! @brief Iterable task list. */ + using iterable = iterable_adaptor; + /*! @brief Adjacency matrix type. */ + using graph_type = adjacency_matrix_type; + + /*! @brief Default constructor. */ + basic_flow() + : basic_flow{allocator_type{}} {} + + /** + * @brief Constructs a flow builder with a given allocator. + * @param allocator The allocator to use. + */ + explicit basic_flow(const allocator_type &allocator) + : index{0u, allocator}, + vertices{allocator}, + deps{allocator} {} + + /*! @brief Default copy constructor. */ + basic_flow(const basic_flow &) = default; + + /** + * @brief Allocator-extended copy constructor. + * @param other The instance to copy from. + * @param allocator The allocator to use. + */ + basic_flow(const basic_flow &other, const allocator_type &allocator) + : index{other.index.first(), allocator}, + vertices{other.vertices, allocator}, + deps{other.deps, allocator}, + sync_on{other.sync_on} {} + + /*! @brief Default move constructor. */ + basic_flow(basic_flow &&) noexcept = default; + + /** + * @brief Allocator-extended move constructor. + * @param other The instance to move from. + * @param allocator The allocator to use. + */ + basic_flow(basic_flow &&other, const allocator_type &allocator) + : index{other.index.first(), allocator}, + vertices{stl::move(other.vertices), allocator}, + deps{stl::move(other.deps), allocator}, + sync_on{other.sync_on} {} + + /*! @brief Default destructor. */ + ~basic_flow() = default; + + /** + * @brief Default copy assignment operator. + * @return This flow builder. + */ + basic_flow &operator=(const basic_flow &) = default; + + /** + * @brief Default move assignment operator. + * @return This flow builder. + */ + basic_flow &operator=(basic_flow &&) noexcept = default; + + /** + * @brief Exchanges the contents with those of a given flow builder. + * @param other Flow builder to exchange the content with. + */ + void swap(basic_flow &other) noexcept { + using stl::swap; + swap(index, other.index); + swap(vertices, other.vertices); + swap(deps, other.deps); + swap(sync_on, other.sync_on); + } + + /** + * @brief Returns the associated allocator. + * @return The associated allocator. + */ + [[nodiscard]] constexpr allocator_type get_allocator() const noexcept { + return allocator_type{index.second()}; + } + + /** + * @brief Returns the identifier at specified location. + * @param pos Position of the identifier to return. + * @return The requested identifier. + */ + [[nodiscard]] id_type operator[](const size_type pos) const { + return vertices.cbegin()[static_cast(pos)]; + } + + /*! @brief Clears the flow builder. */ + void clear() noexcept { + index.first() = {}; + vertices.clear(); + deps.clear(); + sync_on = {}; + } + + /** + * @brief Returns true if a flow builder contains no tasks, false otherwise. + * @return True if the flow builder contains no tasks, false otherwise. + */ + [[nodiscard]] bool empty() const noexcept { + return vertices.empty(); + } + + /** + * @brief Returns the number of tasks. + * @return The number of tasks. + */ + [[nodiscard]] size_type size() const noexcept { + return vertices.size(); + } + + /** + * @brief Binds a task to a flow builder. + * @param value Task identifier. + * @return This flow builder. + */ + basic_flow &bind(const id_type value) { + sync_on += (sync_on == vertices.size()); + const auto it = vertices.emplace(value).first; + index.first() = size_type(it - vertices.begin()); + return *this; + } + + /** + * @brief Turns the current task into a sync point. + * @return This flow builder. + */ + basic_flow &sync() { + ENTT_ASSERT(index.first() < vertices.size(), "Invalid node"); + sync_on = index.first(); + + for(const auto &elem: deps) { + elem.second.emplace_back(sync_on, true); + } + + return *this; + } + + /** + * @brief Assigns a resource to the current task with a given access mode. + * @param res Resource identifier. + * @param is_rw Access mode. + * @return This flow builder. + */ + basic_flow &set(const id_type res, bool is_rw = false) { + emplace(res, is_rw); + return *this; + } + + /** + * @brief Assigns a read-only resource to the current task. + * @param res Resource identifier. + * @return This flow builder. + */ + basic_flow &ro(const id_type res) { + emplace(res, false); + return *this; + } + + /** + * @brief Assigns a range of read-only resources to the current task. + * @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 This flow builder. + */ + basic_flow &ro(stl::input_iterator auto first, stl::input_iterator auto last) { + for(; first != last; ++first) { + emplace(*first, false); + } + + return *this; + } + + /** + * @brief Assigns a writable resource to the current task. + * @param res Resource identifier. + * @return This flow builder. + */ + basic_flow &rw(const id_type res) { + emplace(res, true); + return *this; + } + + /** + * @brief Assigns a range of writable resources to the current task. + * @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 This flow builder. + */ + basic_flow &rw(stl::input_iterator auto first, stl::input_iterator auto last) { + for(; first != last; ++first) { + emplace(*first, true); + } + + return *this; + } + + /** + * @brief Generates a task graph for the current content. + * @return The adjacency matrix of the task graph. + */ + [[nodiscard]] graph_type graph() const { + graph_type matrix{vertices.size(), get_allocator()}; + + setup_graph(matrix); + transitive_closure(matrix); + transitive_reduction(matrix); + + return matrix; + } + +private: + compressed_pair index; + task_container_type vertices; + deps_container_type deps; + size_type sync_on{}; +}; + +} // namespace entt + +#endif diff --git a/include/entt/graph/fwd.hpp b/include/entt/graph/fwd.hpp new file mode 100644 index 0000000..c1fd58e --- /dev/null +++ b/include/entt/graph/fwd.hpp @@ -0,0 +1,28 @@ +#ifndef ENTT_GRAPH_FWD_HPP +#define ENTT_GRAPH_FWD_HPP + +#include "../core/fwd.hpp" +#include "../stl/concepts.hpp" +#include "../stl/cstddef.hpp" +#include "../stl/memory.hpp" + +namespace entt { + +/*! @brief Undirected graph category tag. */ +struct directed_tag {}; + +/*! @brief Directed graph category tag. */ +struct undirected_tag: directed_tag {}; + +template, typename = stl::allocator> +class adjacency_matrix; + +template> +class basic_flow; + +/*! @brief Alias declaration for the most common use case. */ +using flow = basic_flow<>; + +} // namespace entt + +#endif diff --git a/include/entt/locator/locator.hpp b/include/entt/locator/locator.hpp new file mode 100644 index 0000000..0d6fecf --- /dev/null +++ b/include/entt/locator/locator.hpp @@ -0,0 +1,161 @@ +#ifndef ENTT_LOCATOR_LOCATOR_HPP +#define ENTT_LOCATOR_LOCATOR_HPP + +#include "../config/config.h" +#include "../stl/concepts.hpp" +#include "../stl/memory.hpp" +#include "../stl/utility.hpp" + +namespace entt { + +/** + * @brief Service locator, nothing more. + * + * A service locator is used to do what it promises: locate services.
+ * Usually service locators are tightly bound to the services they expose and + * thus it's hard to define a general purpose class to do that. This tiny class + * tries to fill the gap and to get rid of the burden of defining a different + * specific locator for each application. + * + * @note + * Users shouldn't retain references to a service. The recommended way is to + * retrieve the service implementation currently set each and every time the + * need for it arises. The risk is to incur in unexpected behaviors otherwise. + * + * @tparam Service Service type. + */ +template +class locator final { + class service_handle { + friend class locator; + stl::shared_ptr value{}; + }; + +public: + /*! @brief Service type. */ + using type = Service; + /*! @brief Service node type. */ + using node_type = service_handle; + + /*! @brief Default constructor, deleted on purpose. */ + locator() = delete; + + /*! @brief Default copy constructor, deleted on purpose. */ + locator(const locator &) = delete; + + /*! @brief Default destructor, deleted on purpose. */ + ~locator() = delete; + + /** + * @brief Default copy assignment operator, deleted on purpose. + * @return This locator. + */ + locator &operator=(const locator &) = delete; + + /** + * @brief Checks whether a service locator contains a value. + * @return True if the service locator contains a value, false otherwise. + */ + [[nodiscard]] static bool has_value() noexcept { + return (service != nullptr); + } + + /** + * @brief Returns a reference to a valid service, if any. + * + * @warning + * Invoking this function can result in undefined behavior if the service + * hasn't been set yet. + * + * @return A reference to the service currently set, if any. + */ + [[nodiscard]] static Service &value() noexcept { + ENTT_ASSERT(has_value(), "Service not available"); + return *service; + } + + /** + * @brief Returns a service if available or sets it from a fallback type. + * + * Arguments are used only if a service doesn't already exist. In all other + * cases, they are discarded. + * + * @tparam Args Types of arguments to use to construct the fallback service. + * @tparam Type Fallback service type. + * @param args Parameters to use to construct the fallback service. + * @return A reference to a valid service. + */ + template Type = Service, typename... Args> + requires stl::constructible_from + [[nodiscard]] static Service &value_or(Args &&...args) { + return service ? *service : emplace(stl::forward(args)...); + } + + /** + * @brief Sets or replaces a service. + * @tparam Type Service type. + * @tparam Args Types of arguments to use to construct the service. + * @param args Parameters to use to construct the service. + * @return A reference to a valid service. + */ + template Type = Service, typename... Args> + requires stl::constructible_from + static Service &emplace(Args &&...args) { + service = stl::make_shared(stl::forward(args)...); + return *service; + } + + /** + * @brief Sets or replaces a service using a given allocator. + * @tparam Type Service type. + * @tparam Args Types of arguments to use to construct the service. + * @param alloc The allocator to use. + * @param args Parameters to use to construct the service. + * @return A reference to a valid service. + */ + template Type = Service, typename... Args> + requires stl::constructible_from + static Service &emplace(stl::allocator_arg_t, auto alloc, Args &&...args) { + service = stl::allocate_shared(alloc, stl::forward(args)...); + return *service; + } + + /** + * @brief Returns a handle to the underlying service. + * @return A handle to the underlying service. + */ + static node_type handle() noexcept { + node_type node{}; + node.value = service; + return node; + } + + /** + * @brief Resets or replaces a service. + * @param other Optional handle with which to replace the service. + */ + static void reset(const node_type &other = {}) noexcept { + service = other.value; + } + + /** + * @brief Resets or replaces a service. + * @tparam Type Service type. + * @tparam Deleter Deleter type. + * @param elem A pointer to a service to manage. + * @param deleter A deleter to use to destroy the service. + */ + template Type, typename Deleter = stl::default_delete> + static void reset(Type *elem, Deleter deleter = {}) { + service = stl::shared_ptr{elem, stl::move(deleter)}; + } + +private: + // stl::shared_ptr because of its type erased allocator which is useful here + // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) + inline static stl::shared_ptr service{}; +}; + +} // namespace entt + +#endif diff --git a/include/entt/meta/adl_pointer.hpp b/include/entt/meta/adl_pointer.hpp new file mode 100644 index 0000000..5bb768a --- /dev/null +++ b/include/entt/meta/adl_pointer.hpp @@ -0,0 +1,35 @@ +#ifndef ENTT_META_ADL_POINTER_HPP +#define ENTT_META_ADL_POINTER_HPP + +namespace entt { + +/** + * @brief ADL based lookup function for dereferencing meta pointer-like types. + * @tparam Type Element type. + * @param value A pointer-like object. + * @return The value returned from the dereferenced pointer. + */ +template +decltype(auto) dereference_meta_pointer_like(const Type &value) { + return *value; +} + +/** + * @brief Fake ADL based lookup function for meta pointer-like types. + * @tparam Type Element type. + */ +template +struct adl_meta_pointer_like { + /** + * @brief Uses the default ADL based lookup method to resolve the call. + * @param value A pointer-like object. + * @return The value returned from the dereferenced pointer. + */ + static decltype(auto) dereference(const Type &value) { + return dereference_meta_pointer_like(value); + } +}; + +} // namespace entt + +#endif diff --git a/include/entt/meta/container.hpp b/include/entt/meta/container.hpp new file mode 100644 index 0000000..9c131be --- /dev/null +++ b/include/entt/meta/container.hpp @@ -0,0 +1,298 @@ +// IWYU pragma: always_keep + +#ifndef ENTT_META_CONTAINER_HPP +#define ENTT_META_CONTAINER_HPP + +#include "../core/concepts.hpp" +#include "../core/type_traits.hpp" +#include "../stl/concepts.hpp" +#include "../stl/cstddef.hpp" +#include "../stl/iterator.hpp" +#include "../stl/type_traits.hpp" +#include "../stl/utility.hpp" +#include "context.hpp" +#include "fwd.hpp" +#include "meta.hpp" +#include "type_traits.hpp" + +namespace entt { + +/*! @cond ENTT_INTERNAL */ +namespace internal { + +template +struct sequence_container_extent: integral_constant {}; + +template +requires is_complete_v> +struct sequence_container_extent: integral_constant> {}; + +template +inline constexpr stl::size_t sequence_container_extent_v = sequence_container_extent::value; + +template +concept meta_sequence_container_like = requires(Type elem) { + typename Type::value_type; + typename Type::iterator; + requires entt::stl::forward_iterator; + { elem.begin() } -> stl::same_as; + { elem.end() } -> stl::same_as; + requires !requires { typename Type::key_type; }; + requires !requires { elem.substr(); }; +}; + +template +concept meta_associative_container_like = requires(Type value) { + typename Type::key_type; + typename Type::value_type; + typename Type::iterator; + requires entt::stl::forward_iterator; + { value.begin() } -> stl::same_as; + { value.end() } -> stl::same_as; + value.find(stl::declval()); +}; + +} // namespace internal +/*! @endcond */ + +/** + * @brief General purpose implementation of meta sequence container traits. + * @tparam Type Type of underlying sequence container. + */ +template +struct basic_meta_sequence_container_traits { + /*! @brief Unsigned integer type. */ + using size_type = meta_sequence_container::size_type; + /*! @brief Meta iterator type. */ + using iterator = meta_sequence_container::iterator; + + /*! @brief Number of elements, or `meta_dynamic_extent` if dynamic. */ + static constexpr stl::size_t extent = internal::sequence_container_extent_v; + + /** + * @brief Returns the number of elements in a container. + * @param container Opaque pointer to a container of the given type. + * @return Number of elements. + */ + [[nodiscard]] static size_type size(const void *container) { + return static_cast(container)->size(); + } + + /** + * @brief Clears a container. + * @param container Opaque pointer to a container of the given type. + * @return True in case of success, false otherwise. + */ + [[nodiscard]] static bool clear([[maybe_unused]] void *container) { + if constexpr(requires(Type elem) { elem.clear(); }) { + static_cast(container)->clear(); + return true; + } else { + return false; + } + } + + /** + * @brief Increases the capacity of a container. + * @param container Opaque pointer to a container of the given type. + * @param sz Desired capacity. + * @return True in case of success, false otherwise. + */ + [[nodiscard]] static bool reserve([[maybe_unused]] void *container, [[maybe_unused]] const size_type sz) { + if constexpr(requires(Type elem) { elem.reserve(sz); }) { + static_cast(container)->reserve(sz); + return true; + } else { + return false; + } + } + + /** + * @brief Resizes a container. + * @param container Opaque pointer to a container of the given type. + * @param sz The new number of elements. + * @return True in case of success, false otherwise. + */ + [[nodiscard]] static bool resize([[maybe_unused]] void *container, [[maybe_unused]] const size_type sz) { + if constexpr(stl::is_default_constructible_v && requires(Type elem) { elem.resize(sz); }) { + static_cast(container)->resize(sz); + return true; + } else { + return false; + } + } + + /** + * @brief Returns a possibly const iterator to the beginning or the end. + * @param area The context to pass to the newly created iterator. + * @param container Opaque pointer to a container of the given type. + * @param as_const Const opaque pointer fallback. + * @param end False to get a pointer that is past the last element. + * @return An iterator to the first or past the last element of the + * container. + */ + static iterator iter(const meta_ctx &area, void *container, const void *as_const, const bool end) { + return (container == nullptr) + ? iterator{area, end ? static_cast(as_const)->cend() : static_cast(as_const)->cbegin()} + : iterator{area, end ? static_cast(container)->end() : static_cast(container)->begin()}; + } + + /** + * @brief Assigns one element to a container and constructs its object from + * a given opaque instance. + * @param area The context to pass to the newly created iterator. + * @param container Opaque pointer to a container of the given type. + * @param value Optional opaque instance of the object to construct (as + * value type). + * @param cref Optional opaque instance of the object to construct (as + * decayed const reference type). + * @param it Iterator before which the element will be inserted. + * @return A possibly invalid iterator to the inserted element. + */ + [[nodiscard]] static iterator insert([[maybe_unused]] const meta_ctx &area, [[maybe_unused]] void *container, [[maybe_unused]] const void *value, [[maybe_unused]] const void *cref, [[maybe_unused]] const iterator &it) { + if constexpr(requires(Type elem, typename Type::const_iterator iter, Type::value_type instance) { elem.insert(iter, instance); }) { + auto *const non_const = any_cast(&it.base()); + return {area, static_cast(container)->insert( + non_const ? *non_const : any_cast(it.base()), + (value != nullptr) ? *static_cast(value) : *static_cast *>(cref))}; + } else { + return iterator{}; + } + } + + /** + * @brief Erases an element from a container. + * @param area The context to pass to the newly created iterator. + * @param container Opaque pointer to a container of the given type. + * @param it An opaque iterator to the element to erase. + * @return A possibly invalid iterator following the last removed element. + */ + [[nodiscard]] static iterator erase([[maybe_unused]] const meta_ctx &area, [[maybe_unused]] void *container, [[maybe_unused]] const iterator &it) { + if constexpr(requires(Type elem, typename Type::const_iterator iter) { elem.erase(iter); }) { + auto *const non_const = any_cast(&it.base()); + return {area, static_cast(container)->erase(non_const ? *non_const : any_cast(it.base()))}; + } else { + return iterator{}; + } + } +}; + +/** + * @brief General purpose implementation of meta associative container traits. + * @tparam Type Type of underlying associative container. + */ +template +struct basic_meta_associative_container_traits { + /*! @brief Unsigned integer type. */ + using size_type = meta_associative_container::size_type; + /*! @brief Meta iterator type. */ + using iterator = meta_associative_container::iterator; + + /*! @brief True in case of key-only containers, false otherwise. */ + static constexpr bool key_only = !requires { typename Type::mapped_type; }; + + /** + * @brief Returns the number of elements in a container. + * @param container Opaque pointer to a container of the given type. + * @return Number of elements. + */ + [[nodiscard]] static size_type size(const void *container) { + return static_cast(container)->size(); + } + + /** + * @brief Clears a container. + * @param container Opaque pointer to a container of the given type. + * @return True in case of success, false otherwise. + */ + [[nodiscard]] static bool clear(void *container) { + static_cast(container)->clear(); + return true; + } + + /** + * @brief Increases the capacity of a container. + * @param container Opaque pointer to a container of the given type. + * @param sz Desired capacity. + * @return True in case of success, false otherwise. + */ + [[nodiscard]] static bool reserve([[maybe_unused]] void *container, [[maybe_unused]] const size_type sz) { + if constexpr(requires(Type elem) { elem.reserve(sz); }) { + static_cast(container)->reserve(sz); + return true; + } else { + return false; + } + } + + /** + * @brief Returns a possibly const iterator to the beginning or the end. + * @param area The context to pass to the newly created iterator. + * @param container Opaque pointer to a container of the given type. + * @param as_const Const opaque pointer fallback. + * @param end False to get a pointer that is past the last element. + * @return An iterator to the first or past the last element of the + * container. + */ + static iterator iter(const meta_ctx &area, void *container, const void *as_const, const bool end) { + return (container == nullptr) + ? iterator{area, stl::bool_constant{}, end ? static_cast(as_const)->cend() : static_cast(as_const)->cbegin()} + : iterator{area, stl::bool_constant{}, end ? static_cast(container)->end() : static_cast(container)->begin()}; + } + + /** + * @brief Inserts an element into a container, if the key does not exist. + * @param container Opaque pointer to a container of the given type. + * @param key An opaque key value of an element to insert. + * @param value Optional opaque value to insert (key-value containers). + * @return True if the insertion took place, false otherwise. + */ + [[nodiscard]] static bool insert(void *container, const void *key, [[maybe_unused]] const void *value) { + if constexpr(key_only) { + return static_cast(container)->insert(*static_cast(key)).second; + } else { + return static_cast(container)->emplace(*static_cast(key), *static_cast(value)).second; + } + } + + /** + * @brief Removes an element from a container. + * @param container Opaque pointer to a container of the given type. + * @param key An opaque key value of an element to remove. + * @return Number of elements removed (either 0 or 1). + */ + [[nodiscard]] static size_type erase(void *container, const void *key) { + return static_cast(container)->erase(*static_cast(key)); + } + + /** + * @brief Finds an element with a given key. + * @param area The context to pass to the newly created iterator. + * @param container Opaque pointer to a container of the given type. + * @param as_const Const opaque pointer fallback. + * @param key Opaque key value of an element to search for. + * @return An iterator to the element with the given key, if any. + */ + static iterator find(const meta_ctx &area, void *container, const void *as_const, const void *key) { + return (container != nullptr) ? iterator{area, stl::bool_constant{}, static_cast(container)->find(*static_cast(key))} + : iterator{area, stl::bool_constant{}, static_cast(as_const)->find(*static_cast(key))}; + } +}; + +/** + * @brief Traits meta sequence container like types. + * @tparam Type Container type to inspect. + */ +template +struct meta_sequence_container_traits: basic_meta_sequence_container_traits {}; + +/** + * @brief Traits for meta associative container like types. + * @tparam Type Container type to inspect. + */ +template +struct meta_associative_container_traits: basic_meta_associative_container_traits {}; + +} // namespace entt + +#endif diff --git a/include/entt/meta/context.hpp b/include/entt/meta/context.hpp new file mode 100644 index 0000000..4d3e34e --- /dev/null +++ b/include/entt/meta/context.hpp @@ -0,0 +1,47 @@ +#ifndef ENTT_META_CTX_HPP +#define ENTT_META_CTX_HPP + +#include "../container/dense_map.hpp" +#include "../core/fwd.hpp" +#include "../stl/functional.hpp" +#include "../stl/memory.hpp" +#include "fwd.hpp" + +namespace entt { + +/*! @cond ENTT_INTERNAL */ +namespace internal { + +struct meta_type_node; + +struct meta_context { + using bucket_type = dense_map, stl::identity>; + + bucket_type bucket; + + [[nodiscard]] inline static meta_context &from(meta_ctx &); + [[nodiscard]] inline static const meta_context &from(const meta_ctx &); +}; + +} // namespace internal +/*! @endcond */ + +/*! @brief Opaque meta context type. */ +struct meta_ctx: private internal::meta_context { + // attorney idiom like model to access the base class + friend struct internal::meta_context; +}; + +/*! @cond ENTT_INTERNAL */ +[[nodiscard]] inline internal::meta_context &internal::meta_context::from(meta_ctx &ctx) { + return ctx; +} + +[[nodiscard]] inline const internal::meta_context &internal::meta_context::from(const meta_ctx &ctx) { + return ctx; +} +/*! @endcond */ + +} // namespace entt + +#endif diff --git a/include/entt/meta/factory.hpp b/include/entt/meta/factory.hpp new file mode 100644 index 0000000..e91888a --- /dev/null +++ b/include/entt/meta/factory.hpp @@ -0,0 +1,656 @@ +#ifndef ENTT_META_FACTORY_HPP +#define ENTT_META_FACTORY_HPP + +#include "../config/config.h" +#include "../core/bit.hpp" +#include "../core/fwd.hpp" +#include "../core/hashed_string.hpp" +#include "../core/type_info.hpp" +#include "../core/type_traits.hpp" +#include "../locator/locator.hpp" +#include "../stl/algorithm.hpp" +#include "../stl/concepts.hpp" +#include "../stl/cstddef.hpp" +#include "../stl/cstdint.hpp" +#include "../stl/functional.hpp" +#include "../stl/memory.hpp" +#include "../stl/type_traits.hpp" +#include "../stl/utility.hpp" +#include "context.hpp" +#include "fwd.hpp" +#include "meta.hpp" +#include "node.hpp" +#include "policy.hpp" +#include "range.hpp" +#include "utility.hpp" + +namespace entt { + +/*! @cond ENTT_INTERNAL */ +namespace internal { + +class basic_meta_factory { + using invoke_type = stl::remove_pointer_t; + + enum class mode { + type, + data, + func + }; + + [[nodiscard]] auto *find_member_or_assert() { + auto *member = find_member(parent->details->data, bucket); + ENTT_ASSERT(member != nullptr, "Cannot find member"); + return member; + } + + [[nodiscard]] auto *find_overload_or_assert() { + ENTT_ASSERT(invoke != nullptr, "Invoke function not available"); + auto *overload = find_overload(find_member(parent->details->func, bucket), invoke); + ENTT_ASSERT(overload != nullptr, "Cannot find overload"); + return overload; + } + + bool unique_alias(const id_type alias) const noexcept { + return (ctx->bucket.find(alias) == ctx->bucket.cend()) && (stl::find_if(ctx->bucket.cbegin(), ctx->bucket.cend(), [alias](const auto &value) { return value.second->alias == alias; }) == ctx->bucket.cend()); + } + +protected: + void type(const id_type alias, const char *name) noexcept { + state = mode::type; + ENTT_ASSERT((parent->alias == alias) || unique_alias(alias), "Duplicate identifier"); + parent->alias = alias; + parent->name = name; + } + + template + void insert_or_assign(Type node) { + state = mode::type; + + if constexpr(stl::is_same_v) { + auto *member = find_member(parent->details->base, node.id); + member ? (*member = node) : parent->details->base.emplace_back(node); + } else if constexpr(stl::is_same_v) { + auto *member = find_member(parent->details->conv, node.id); + member ? (*member = node) : parent->details->conv.emplace_back(node); + } else { + static_assert(stl::is_same_v, "Unexpected type"); + auto *member = find_member(parent->details->ctor, node.id); + member ? (*member = node) : parent->details->ctor.emplace_back(node); + } + } + + void data(meta_data_node node) { + state = mode::data; + bucket = node.id; + + if(auto *member = find_member(parent->details->data, node.id); member == nullptr) { + parent->details->data.emplace_back(stl::move(node)); + } else if(member->set != node.set || member->get != node.get) { + *member = stl::move(node); + } + } + + void func(meta_func_node node) { + state = mode::func; + bucket = node.id; + invoke = node.invoke; + + if(auto *member = find_member(parent->details->func, node.id); member == nullptr) { + parent->details->func.emplace_back(stl::move(node)); + } else if(auto *overload = find_overload(member, node.invoke); overload == nullptr) { + while(member->next != nullptr) { member = member->next.get(); } + member->next = stl::make_unique(stl::move(node)); + } + } + + void traits(const meta_traits value, const bool unset) { + const auto set_or_unset_on = [=](auto &node) { + node.traits = (unset ? (node.traits & ~value) : (node.traits | value)); + }; + + switch(state) { + case mode::type: + set_or_unset_on(*parent); + break; + case mode::data: + set_or_unset_on(*find_member_or_assert()); + break; + case mode::func: + set_or_unset_on(*find_overload_or_assert()); + break; + } + } + + void custom(meta_custom_node node) { + switch(state) { + case mode::type: + parent->custom = stl::move(node); + break; + case mode::data: + find_member_or_assert()->custom = stl::move(node); + break; + case mode::func: + find_overload_or_assert()->custom = stl::move(node); + break; + } + } + +public: + basic_meta_factory(meta_ctx &area, meta_type_node node, const id_type id) + : ctx{&meta_context::from(area)}, + bucket{}, + state{mode::type} { + if(const auto it = ctx->bucket.find(id); it == ctx->bucket.cend()) { + ENTT_ASSERT(unique_alias(id), "Duplicate identifier"); + parent = ctx->bucket.emplace(id, stl::make_unique(stl::move(node))).first->second.get(); + parent->details = stl::make_unique(); + parent->alias = id; + } else { + parent = it->second.get(); + } + } + +private: + meta_context *ctx{}; + invoke_type *invoke{}; + meta_type_node *parent{}; + id_type bucket{}; + mode state{}; +}; + +} // namespace internal +/*! @endcond */ + +/** + * @brief Meta factory to be used for reflection purposes. + * @tparam Type Type for which the factory was created. + */ +template +class meta_factory: private internal::basic_meta_factory { + using base_type = internal::basic_meta_factory; + +public: + /*! @brief Type of object for which this factory builds a meta type. */ + using element_type = Type; + + /*! @brief Default constructor. */ + meta_factory() noexcept + : meta_factory{locator::value_or()} {} + + /** + * @brief Context aware constructor. + * @param area The context into which to construct meta types. + */ + meta_factory(meta_ctx &area) noexcept + : base_type{area, internal::setup_node_for(), type_hash::value()} {} + + /** + * @brief Constructs an unconstrained type assigned to a given identifier. + * @param id A custom unique identifier. + */ + meta_factory(const id_type id) noexcept + : meta_factory{locator::value_or(), id} {} + + /** + * @brief Context aware constructor. + * @param id A custom unique identifier. + * @param area The context into which to construct meta types. + */ + meta_factory(meta_ctx &area, const id_type id) noexcept + : base_type{area, internal::setup_node_for(), id} {} + + /** + * @brief Assigns a custom unique identifier to a meta type. + * @param name A custom unique identifier as a **string literal**. + * @return A meta factory for the given type. + */ + meta_factory type(const char *name) noexcept { + return type(hashed_string::value(name), name); + } + + /** + * @brief Assigns a custom unique identifier to a meta type. + * @param alias A custom unique identifier. + * @param name An optional name for the type as a **string literal**. + * @return A meta factory for the given type. + */ + meta_factory type(const id_type alias, const char *name = nullptr) noexcept { + base_type::type(alias, name); + return *this; + } + + /** + * @brief Assigns a meta base to a meta type. + * + * A reflected base class must be a real base class of the reflected type. + * + * @tparam Base Type of the base class to assign to the meta type. + * @return A meta factory for the parent type. + */ + template + requires stl::derived_from + meta_factory base() noexcept { + if constexpr(!stl::same_as) { + auto *const op = +[](const void *instance) noexcept { return static_cast(static_cast(static_cast(instance))); }; + + base_type::insert_or_assign( + internal::meta_base_node{ + type_id().hash(), + &internal::resolve, + op}); + } + + return *this; + } + + /** + * @brief Assigns a meta conversion function to a meta type. + * + * Conversion functions can be either free functions or member + * functions.
+ * In case of free functions, they must accept a const reference to an + * instance of the parent type as an argument. In case of member functions, + * they should have no arguments at all. + * + * @tparam Candidate The actual function to use for the conversion. + * @return A meta factory for the parent type. + */ + template + auto conv() noexcept { + using conv_type = stl::remove_cvref_t>; + auto *const op = +[](const meta_ctx &area, const void *instance) { return forward_as_meta(area, stl::invoke(Candidate, *static_cast(instance))); }; + + base_type::insert_or_assign( + internal::meta_conv_node{ + type_id().hash(), + op}); + + return *this; + } + + /** + * @brief Assigns a meta conversion function to a meta type. + * + * The given type must be such that an instance of the reflected type can be + * converted to it. + * + * @tparam To Type of the conversion function to assign to the meta type. + * @return A meta factory for the parent type. + */ + template + meta_factory conv() noexcept { + using conv_type = stl::remove_cvref_t; + auto *const op = +[](const meta_ctx &area, const void *instance) { return forward_as_meta(area, static_cast(*static_cast(instance))); }; + + base_type::insert_or_assign( + internal::meta_conv_node{ + type_id().hash(), + op}); + + return *this; + } + + /** + * @brief Assigns a meta constructor to a meta type. + * + * Both member functions and free function can be assigned to meta types in + * the role of constructors. All that is required is that they return an + * instance of the underlying type.
+ * From a client's point of view, nothing changes if a constructor of a meta + * type is a built-in one or not. + * + * @tparam Candidate The actual function to use as a constructor. + * @tparam Policy Optional policy (no policy set by default). + * @return A meta factory for the parent type. + */ + template + meta_factory ctor() noexcept { + using descriptor = meta_function_helper_t; + static_assert(Policy::template value, "Invalid return type for the given policy"); + static_assert(stl::is_same_v, element_type>, "The function doesn't return an object of the required type"); + + base_type::insert_or_assign( + internal::meta_ctor_node{ + type_id().hash(), + descriptor::args_type::size, + &meta_arg, + &meta_construct}); + + return *this; + } + + /** + * @brief Assigns a meta constructor to a meta type. + * + * A meta constructor is uniquely identified by the types of its arguments + * and is such that there exists an actual constructor of the underlying + * type that can be invoked with parameters whose types are those given. + * + * @tparam Args Types of arguments to use to construct an instance. + * @return A meta factory for the parent type. + */ + template + meta_factory ctor() noexcept { + // default constructor is already implicitly generated, no need for redundancy + if constexpr(sizeof...(Args) != 0u) { + using descriptor = meta_function_helper_t; + + base_type::insert_or_assign( + internal::meta_ctor_node{ + type_id().hash(), + descriptor::args_type::size, + &meta_arg, + &meta_construct}); + } + + return *this; + } + + /** + * @brief Assigns a meta data to a meta type. + * @tparam Data The actual variable to attach to the meta type. + * @tparam Policy Optional policy (no policy set by default). + * @param name A custom unique identifier as a **string literal**. + * @return A meta factory for the given type. + */ + template + meta_factory data(const char *name) noexcept { + return data(hashed_string::value(name), name); + } + + /** + * @brief Assigns a meta data to a meta type. + * + * Both data members and static and global variables, as well as constants + * of any kind, can be assigned to a meta type.
+ * From a client's point of view, all the variables associated with the + * reflected object will appear as if they were part of the type itself. + * + * @tparam Data The actual variable to attach to the meta type. + * @tparam Policy Optional policy (no policy set by default). + * @param id Unique identifier. + * @param name An optional name for the meta data as a **string literal**. + * @return A meta factory for the parent type. + */ + template + meta_factory data(const id_type id, const char *name = nullptr) noexcept { + if constexpr(stl::is_member_object_pointer_v) { + using data_type = stl::invoke_result_t; + static_assert(Policy::template value, "Invalid return type for the given policy"); + + base_type::data( + internal::meta_data_node{ + id, + name, + /* this is never static */ + stl::is_const_v> ? internal::meta_traits::is_const : internal::meta_traits::is_none, + 1u, + 0u, + &meta_arg>>, + &meta_arg>, + &internal::resolve>, + &meta_setter, + &meta_getter}); + } else { + using data_type = stl::remove_pointer_t; + + if constexpr(stl::is_pointer_v) { + static_assert(Policy::template value, "Invalid return type for the given policy"); + } else { + static_assert(Policy::template value, "Invalid return type for the given policy"); + } + + base_type::data( + internal::meta_data_node{ + id, + name, + ((!stl::is_pointer_v || stl::is_const_v) ? internal::meta_traits::is_const : internal::meta_traits::is_none) | internal::meta_traits::is_static, + 1u, + 0u, + &meta_arg>>, + &meta_arg>, + &internal::resolve>, + &meta_setter, + &meta_getter}); + } + + return *this; + } + + /** + * @brief Assigns a meta data to a meta type by means of its setter and + * getter. + * @tparam Setter The actual function to use as a setter. + * @tparam Getter The actual function to use as a getter. + * @tparam Policy Optional policy (no policy set by default). + * @param name A custom unique identifier as a **string literal**. + * @return A meta factory for the given type. + */ + template + meta_factory data(const char *name) noexcept { + return data(hashed_string::value(name), name); + } + + /** + * @brief Assigns a meta data to a meta type by means of its setter and + * getter. + * + * Setters and getters can be either free functions, member functions or a + * mix of them.
+ * In case of free functions, setters and getters must accept a reference to + * an instance of the parent type as their first argument. A setter has then + * an extra argument of a type convertible to that of the parameter to + * set.
+ * In case of member functions, getters have no arguments at all, while + * setters has an argument of a type convertible to that of the parameter to + * set. + * + * @tparam Setter The actual function to use as a setter. + * @tparam Getter The actual function to use as a getter. + * @tparam Policy Optional policy (no policy set by default). + * @param id Unique identifier. + * @param name An optional name for the meta data as a **string literal**. + * @return A meta factory for the parent type. + */ + template + meta_factory data(const id_type id, const char *name = nullptr) noexcept { + using getter = meta_function_helper_t; + static_assert(Policy::template value, "Invalid return type for the given policy"); + + if constexpr(stl::is_same_v) { + base_type::data( + internal::meta_data_node{ + id, + name, + /* this is never static */ + internal::meta_traits::is_const, + 0u, + getter::args_type::size, + &meta_arg>, + &meta_arg, + &internal::resolve>, + &meta_setter, + &meta_getter}); + } else { + using setter = meta_function_helper_t; + + base_type::data( + internal::meta_data_node{ + id, + name, + /* this is never static nor const */ + internal::meta_traits::is_none, + setter::args_type::size, + getter::args_type::size, + &meta_arg, + &meta_arg, + &internal::resolve>, + &meta_setter, + &meta_getter}); + } + + return *this; + } + + /** + * @brief Assigns a meta function to a meta type. + * @tparam Candidate The actual function to attach to the meta function. + * @tparam Policy Optional policy (no policy set by default). + * @param name A custom unique identifier as a **string literal**. + * @return A meta factory for the given type. + */ + template + meta_factory func(const char *name) noexcept { + return func(hashed_string::value(name), name); + } + + /** + * @brief Assigns a meta function to a meta type. + * + * Both member functions and free functions can be assigned to a meta + * type.
+ * From a client's point of view, all the functions associated with the + * reflected object will appear as if they were part of the type itself. + * + * @tparam Candidate The actual function to attach to the meta type. + * @tparam Policy Optional policy (no policy set by default). + * @param id Unique identifier. + * @param name An optional name for the function as a **string literal**. + * @return A meta factory for the parent type. + */ + template + meta_factory func(const id_type id, const char *name = nullptr) noexcept { + using descriptor = meta_function_helper_t; + static_assert(Policy::template value, "Invalid return type for the given policy"); + + base_type::func( + internal::meta_func_node{ + id, + name, + (descriptor::is_const ? internal::meta_traits::is_const : internal::meta_traits::is_none) | (descriptor::is_static ? internal::meta_traits::is_static : internal::meta_traits::is_none), + descriptor::args_type::size, + &internal::resolve, void, stl::remove_cvref_t>>, + &meta_arg, + &meta_invoke}); + + return *this; + } + + /** + * @brief Sets traits on the last created meta object. + * + * The assigned value must be an enum and intended as a bitmask. + * + * @tparam Value Type of the traits value. + * @param value Traits value. + * @param unset True to unset the given traits, false otherwise. + * @return A meta factory for the parent type. + */ + template + meta_factory traits(const Value value, const bool unset = false) { + static_assert(stl::is_enum_v, "Invalid enum type"); + base_type::traits(internal::user_to_meta_traits(value), unset); + return *this; + } + + /** + * @brief Sets user defined data that will never be used by the library. + * @tparam Value Type of user defined data to store. + * @tparam Args Types of arguments to use to construct the user data. + * @param args Parameters to use to initialize the user data. + * @return A meta factory for the parent type. + */ + template + meta_factory custom(Args &&...args) { + base_type::custom(internal::meta_custom_node{type_id().hash(), stl::make_shared(stl::forward(args)...)}); + return *this; + } +}; + +/** + * @brief Resets a type and all its parts. + * + * Resets a type and all its data members, member functions and properties, as + * well as its constructors, destructors and conversion functions if any.
+ * Base classes aren't reset but the link between the two types is removed. + * + * The type is also removed from the set of searchable types. + * + * @param alias Unique identifier. + * @param ctx The context from which to reset meta types. + */ +inline void meta_reset(meta_ctx &ctx, const id_type alias) noexcept { + auto &bucket = internal::meta_context::from(ctx).bucket; + + // fast path for unsearchable and overloaded types + if(bucket.erase(alias) == 0u) { + if(const auto it = stl::find_if(bucket.cbegin(), bucket.cend(), [alias](const auto &value) { return value.second->alias == alias; }); it != bucket.cend()) { + bucket.erase(it); + } + } +} + +/** + * @brief Resets a type and all its parts. + * + * Resets a type and all its data members, member functions and properties, as + * well as its constructors, destructors and conversion functions if any.
+ * Base classes aren't reset but the link between the two types is removed. + * + * The type is also removed from the set of searchable types. + * + * @param alias Unique identifier. + */ +inline void meta_reset(const id_type alias) noexcept { + meta_reset(locator::value_or(), alias); +} + +/** + * @brief Resets a type and all its parts. + * + * @sa meta_reset + * + * @tparam Type Type to reset. + * @param ctx The context from which to reset meta types. + */ +template +void meta_reset(meta_ctx &ctx) noexcept { + internal::meta_context::from(ctx).bucket.erase(type_id().hash()); +} + +/** + * @brief Resets a type and all its parts. + * + * @sa meta_reset + * + * @tparam Type Type to reset. + */ +template +void meta_reset() noexcept { + meta_reset(locator::value_or()); +} + +/** + * @brief Resets all meta types. + * + * @sa meta_reset + * + * @param ctx The context from which to reset meta types. + */ +inline void meta_reset(meta_ctx &ctx) noexcept { + internal::meta_context::from(ctx).bucket.clear(); +} + +/** + * @brief Resets all meta types. + * + * @sa meta_reset + */ +inline void meta_reset() noexcept { + meta_reset(locator::value_or()); +} + +} // namespace entt + +#endif diff --git a/include/entt/meta/fwd.hpp b/include/entt/meta/fwd.hpp new file mode 100644 index 0000000..ac949c7 --- /dev/null +++ b/include/entt/meta/fwd.hpp @@ -0,0 +1,43 @@ +#ifndef ENTT_META_FWD_HPP +#define ENTT_META_FWD_HPP + +#include "../stl/cstddef.hpp" +#include "../stl/limits.hpp" + +namespace entt { + +struct meta_ctx; + +class meta_sequence_container; + +class meta_associative_container; + +class meta_any; + +class meta_handle; + +struct meta_custom; + +struct meta_data; + +struct meta_func; + +struct meta_base; + +class meta_type; + +template +class meta_factory; + +/*! @brief Used to identicate that a sequence container has not a fixed size. */ +inline constexpr stl::size_t meta_dynamic_extent = (stl::numeric_limits::max)(); + +/*! @brief Disambiguation tag for constructors and the like. */ +struct meta_ctx_arg_t final {}; + +/*! @brief Constant of type meta_context_arg_t used to disambiguate calls. */ +inline constexpr meta_ctx_arg_t meta_ctx_arg{}; + +} // namespace entt + +#endif diff --git a/include/entt/meta/meta.hpp b/include/entt/meta/meta.hpp new file mode 100644 index 0000000..f94026d --- /dev/null +++ b/include/entt/meta/meta.hpp @@ -0,0 +1,1902 @@ +#ifndef ENTT_META_META_HPP +#define ENTT_META_META_HPP + +#include "../config/config.h" +#include "../core/any.hpp" +#include "../core/concepts.hpp" +#include "../core/fwd.hpp" +#include "../core/iterator.hpp" +#include "../core/type_info.hpp" +#include "../core/type_traits.hpp" +#include "../core/utility.hpp" +#include "../locator/locator.hpp" +#include "../stl/array.hpp" +#include "../stl/concepts.hpp" +#include "../stl/cstddef.hpp" +#include "../stl/iterator.hpp" +#include "../stl/memory.hpp" +#include "../stl/string_view.hpp" +#include "../stl/type_traits.hpp" +#include "../stl/utility.hpp" +#include "adl_pointer.hpp" +#include "context.hpp" +#include "fwd.hpp" +#include "node.hpp" +#include "range.hpp" +#include "type_traits.hpp" + +namespace entt { + +/*! @cond ENTT_INTERNAL */ +namespace internal { + +template +struct basic_meta_object { + [[nodiscard]] auto &node_or_assert() const noexcept { + ENTT_ASSERT(node != nullptr, "Invalid pointer to node"); + return *node; + } + + const Type *node{}; + const meta_ctx *ctx{&locator::value_or()}; +}; + +} // namespace internal +/*! @endcond */ + +/*! @brief Proxy object for sequence containers. */ +class meta_sequence_container { + class meta_iterator; + +public: + /*! @brief Unsigned integer type. */ + using size_type = stl::size_t; + /*! @brief Meta iterator type. */ + using iterator = meta_iterator; + + /*! @brief Default constructor. */ + meta_sequence_container() = default; + + /** + * @brief Context aware constructor. + * @tparam Type Type of container to wrap. + * @param area The context from which to search for meta types. + * @param instance The container to wrap. + */ + template + meta_sequence_container(const meta_ctx &area, Type &instance) noexcept + : ctx{&area}, + data{&instance}, + value_type_node{&internal::resolve}, + const_reference_node{&internal::resolve>}, + size_fn{meta_sequence_container_traits>::size}, + clear_fn{meta_sequence_container_traits>::clear}, + reserve_fn{meta_sequence_container_traits>::reserve}, + resize_fn{meta_sequence_container_traits>::resize}, + begin_end_fn{meta_sequence_container_traits>::iter}, + insert_fn{meta_sequence_container_traits>::insert}, + erase_fn{meta_sequence_container_traits>::erase}, + const_only{stl::is_const_v} {} + + [[nodiscard]] inline meta_type value_type() const noexcept; + [[nodiscard]] inline size_type size() const noexcept; + inline bool resize(size_type); + inline bool clear(); + inline bool reserve(size_type); + [[nodiscard]] inline iterator begin(); + [[nodiscard]] inline iterator end(); + inline iterator insert(const iterator &, meta_any); + inline iterator erase(const iterator &); + [[nodiscard]] inline meta_any operator[](size_type); + [[nodiscard]] inline explicit operator bool() const noexcept; + +private: + const meta_ctx *ctx{}; + const void *data{}; + const internal::meta_type_node &(*value_type_node)(const internal::meta_context &){}; + const internal::meta_type_node &(*const_reference_node)(const internal::meta_context &){}; + size_type (*size_fn)(const void *){}; + bool (*clear_fn)(void *){}; + bool (*reserve_fn)(void *, const size_type){}; + bool (*resize_fn)(void *, const size_type){}; + iterator (*begin_end_fn)(const meta_ctx &, void *, const void *, const bool){}; + iterator (*insert_fn)(const meta_ctx &, void *, const void *, const void *, const iterator &){}; + iterator (*erase_fn)(const meta_ctx &, void *, const iterator &){}; + bool const_only{}; +}; + +/*! @brief Proxy object for associative containers. */ +class meta_associative_container { + class meta_iterator; + +public: + /*! @brief Unsigned integer type. */ + using size_type = stl::size_t; + /*! @brief Meta iterator type. */ + using iterator = meta_iterator; + + /*! @brief Default constructor. */ + meta_associative_container() = default; + + /** + * @brief Context aware constructor. + * @tparam Type Type of container to wrap. + * @param area The context from which to search for meta types. + * @param instance The container to wrap. + */ + template + meta_associative_container(const meta_ctx &area, Type &instance) noexcept + : ctx{&area}, + data{&instance}, + key_type_node{&internal::resolve}, + value_type_node{&internal::resolve}, + size_fn{&meta_associative_container_traits>::size}, + clear_fn{&meta_associative_container_traits>::clear}, + reserve_fn{&meta_associative_container_traits>::reserve}, + begin_end_fn{&meta_associative_container_traits>::iter}, + insert_fn{&meta_associative_container_traits>::insert}, + erase_fn{&meta_associative_container_traits>::erase}, + find_fn{&meta_associative_container_traits>::find}, + const_only{stl::is_const_v} { + if constexpr(!meta_associative_container_traits>::key_only) { + mapped_type_node = &internal::resolve; + } + } + + [[nodiscard]] inline meta_type key_type() const noexcept; + [[nodiscard]] inline meta_type mapped_type() const noexcept; + [[nodiscard]] inline meta_type value_type() const noexcept; + [[nodiscard]] inline size_type size() const noexcept; + inline bool clear(); + inline bool reserve(size_type); + [[nodiscard]] inline iterator begin(); + [[nodiscard]] inline iterator end(); + inline bool insert(meta_any, meta_any); + inline size_type erase(meta_any); + [[nodiscard]] inline iterator find(meta_any); + [[nodiscard]] inline explicit operator bool() const noexcept; + +private: + const meta_ctx *ctx{}; + const void *data{}; + const internal::meta_type_node &(*key_type_node)(const internal::meta_context &){}; + const internal::meta_type_node &(*mapped_type_node)(const internal::meta_context &){}; + const internal::meta_type_node &(*value_type_node)(const internal::meta_context &){}; + size_type (*size_fn)(const void *){}; + bool (*clear_fn)(void *){}; + bool (*reserve_fn)(void *, const size_type){}; + iterator (*begin_end_fn)(const meta_ctx &, void *, const void *, const bool){}; + bool (*insert_fn)(void *, const void *, const void *){}; + size_type (*erase_fn)(void *, const void *){}; + iterator (*find_fn)(const meta_ctx &, void *, const void *, const void *){}; + bool const_only{}; +}; + +/*! @brief Opaque wrapper for values of any type. */ +class meta_any { + using vtable_type = void(const internal::meta_traits, const meta_any &, void *); + + template + static void basic_vtable(const internal::meta_traits req, const meta_any &value, [[maybe_unused]] void *other) { + if(req == internal::meta_traits::is_none) { + value.node = &internal::resolve(internal::meta_context::from(*value.ctx)); + } + + if constexpr(is_meta_pointer_like_v) { + if(req == internal::meta_traits::is_pointer) { + if constexpr(!stl::is_void_v::element_type>>) { + if constexpr(stl::is_constructible_v) { + if(const auto &pointer_like = any_cast(value.storage); pointer_like) { + static_cast(other)->emplace::dereference(stl::declval()))>(adl_meta_pointer_like::dereference(pointer_like)); + } + } else { + static_cast(other)->emplace::dereference(stl::declval()))>(adl_meta_pointer_like::dereference(any_cast(value.storage))); + } + } + } + } else if constexpr(requires(Type elem) { *elem; }) { + if(req == internal::meta_traits::is_pointer) { + if constexpr(stl::is_class_v) { + if(const auto &elem = any_cast(value.storage); elem) { + return (value.storage.policy() == any_policy::cref) ? static_cast(other)->emplace(*elem) : static_cast(other)->emplace(elem))>(*const_cast(elem)); + } + } else if constexpr(!stl::is_array_v && !stl::is_void_v>>) { + if(auto *pointer = any_cast(value.storage); pointer) { + static_cast(other)->emplace>>, Type, stl::remove_pointer_t &>>(*pointer); + } + } + } + } else if constexpr(is_complete_v> || is_complete_v>) { + if(constexpr auto flag = (is_complete_v> ? internal::meta_traits::is_sequence_container : internal::meta_traits::is_associative_container); req == flag) { + using container_type = stl::conditional_t>, meta_sequence_container, meta_associative_container>; + *static_cast(other) = (value.storage.policy() == any_policy::cref) ? container_type{*value.ctx, any_cast(value.storage)} : container_type{*value.ctx, any_cast(const_cast(value).storage)}; + } + } + } + + [[nodiscard]] const auto &fetch_node() const { + if(node == nullptr) { + ENTT_ASSERT(*this, "Invalid vtable function"); + vtable(internal::meta_traits::is_none, *this, nullptr); + } + + ENTT_ASSERT(node != nullptr, "Invalid pointer to node"); + return *node; + } + + meta_any(const meta_any &other, any elem) + : storage{stl::move(elem)}, + ctx{other.ctx}, + node{other.node}, + vtable{other.vtable} {} + +public: + /*! Default constructor. */ + meta_any() = default; + + /** + * @brief Context aware constructor. + * @param area The context from which to search for meta types. + */ + meta_any(meta_ctx_arg_t, const meta_ctx &area) + : ctx{&area} {} + + /** + * @brief Constructs a wrapper by directly initializing the new object. + * @tparam Type Type of object to use to initialize the wrapper. + * @param args Parameters to use to construct the instance. + */ + template + explicit meta_any(stl::in_place_type_t, auto &&...args) + : meta_any{locator::value_or(), stl::in_place_type, stl::forward(args)...} {} + + /** + * @brief Constructs a wrapper by directly initializing the new object. + * @tparam Type Type of object to use to initialize the wrapper. + * @param area The context from which to search for meta types. + * @param args Parameters to use to construct the instance. + */ + template + explicit meta_any(const meta_ctx &area, stl::in_place_type_t, auto &&...args) + : storage{stl::in_place_type, stl::forward(args)...}, + ctx{&area}, + vtable{&basic_vtable>} {} + + /** + * @brief Constructs a wrapper taking ownership of the passed object. + * @param value A pointer to an object to take ownership of. + */ + explicit meta_any(stl::in_place_t, auto *value) + : meta_any{locator::value_or(), stl::in_place, value} {} + + /** + * @brief Constructs a wrapper taking ownership of the passed object. + * @param area The context from which to search for meta types. + * @param value A pointer to an object to take ownership of. + */ + explicit meta_any(const meta_ctx &area, stl::in_place_t, auto *value) + : storage{stl::in_place, value}, + ctx{&area}, + vtable{storage ? &basic_vtable>> : nullptr} { + } + + /** + * @brief Constructs a wrapper from a given value. + * @param value An instance of an object to use to initialize the wrapper. + */ + meta_any(auto &&value) + requires (!stl::same_as, meta_any>) + : meta_any{locator::value_or(), stl::forward(value)} {} + + /** + * @brief Constructs a wrapper from a given value. + * @param area The context from which to search for meta types. + * @param value An instance of an object to use to initialize the wrapper. + */ + meta_any(const meta_ctx &area, auto &&value) + requires (!stl::same_as, meta_any>) + : meta_any{area, stl::in_place_type>, stl::forward(value)} {} + + /** + * @brief Context aware copy constructor. + * @param area The context from which to search for meta types. + * @param other The instance to copy from. + */ + meta_any(const meta_ctx &area, const meta_any &other) + : storage{other.storage}, + ctx{&area}, + node{(ctx == other.ctx) ? other.node : nullptr}, + vtable{other.vtable} {} + + /** + * @brief Context aware move constructor. + * @param area The context from which to search for meta types. + * @param other The instance to move from. + */ + meta_any(const meta_ctx &area, meta_any &&other) + : storage{stl::move(other.storage)}, + ctx{&area}, + node{(ctx == other.ctx) ? stl::exchange(other.node, nullptr) : nullptr}, + vtable{stl::exchange(other.vtable, nullptr)} {} + + /** + * @brief Copy constructor. + * @param other The instance to copy from. + */ + meta_any(const meta_any &other) + : storage{other.storage}, + ctx{other.ctx}, + node{(other.storage && !storage) ? nullptr : other.node}, + vtable{(other.storage && !storage) ? nullptr : other.vtable} { + } + + /** + * @brief Move constructor. + * @param other The instance to move from. + */ + meta_any(meta_any &&other) noexcept + : storage{stl::move(other.storage)}, + ctx{other.ctx}, + node{stl::exchange(other.node, nullptr)}, + vtable{stl::exchange(other.vtable, nullptr)} {} + + /*! @brief Default destructor. */ + ~meta_any() = default; + + /** + * @brief Copy assignment operator. + * @param other The instance to copy from. + * @return This meta any object. + */ + meta_any &operator=(const meta_any &other) { + if(this != &other) { + ctx = other.ctx; + storage = other.storage; + node = (other.storage && !storage) ? nullptr : other.node; + vtable = (other.storage && !storage) ? nullptr : other.vtable; + } + + return *this; + } + + /** + * @brief Move assignment operator. + * @param other The instance to move from. + * @return This meta any object. + */ + meta_any &operator=(meta_any &&other) noexcept { + storage = stl::move(other.storage); + ctx = other.ctx; + node = stl::exchange(other.node, nullptr); + vtable = stl::exchange(other.vtable, nullptr); + return *this; + } + + /** + * @brief Value assignment operator. + * @param value An instance of an object to use to initialize the wrapper. + * @return This meta any object. + */ + meta_any &operator=(auto &&value) + requires (!stl::same_as, meta_any>) { + emplace>(stl::forward(value)); + return *this; + } + + /** + * @brief Returns the meta type associated with the contained instance. + * @return The meta type associated with the contained instance. + */ + [[nodiscard]] inline meta_type type() const noexcept; + + /** + * @brief Sets a meta type for the contained instance. + * @param alias The meta to use with the contained instance. + */ + inline void type(const meta_type &alias) noexcept; + + /** + * @brief Invokes the underlying function, if possible. + * @param id Unique identifier. + * @param args Parameters to use to invoke the function. + * @return A wrapper containing the returned value, if any. + */ + meta_any invoke(id_type id, auto &&...args) const; + + /*! @copydoc invoke */ + meta_any invoke(id_type id, auto &&...args); + + /** + * @brief Sets the value of a given variable. + * @param id Unique identifier. + * @param args Parameters to use to set the underlying variable. + * @return True in case of success, false otherwise. + */ + bool set(id_type id, auto &&...args); + + /** + * @brief Gets the value of a given variable. + * @param id Unique identifier. + * @param args Parameters to use to set the underlying variable, if any. + * @return A wrapper containing the value of the underlying variable. + */ + [[nodiscard]] meta_any get(id_type id, auto &&...args) const; + + /*! @copydoc get */ + [[nodiscard]] meta_any get(id_type id, auto &&...args); + + /** + * @brief Tries to cast an instance to a given type. + * @tparam Type Type to which to cast the instance. + * @return A (possibly null) pointer to the contained instance. + */ + template + [[nodiscard]] const Type *try_cast() const { + const auto *elem = any_cast(&storage); + return ((elem != nullptr) || !*this) ? elem : static_cast(internal::try_cast(internal::meta_context::from(*ctx), fetch_node(), type_hash>::value(), storage.data())); + } + + /*! @copydoc try_cast */ + template + [[nodiscard]] Type *try_cast() { + return ((storage.policy() == any_policy::cref) && !stl::is_const_v) ? nullptr : const_cast(stl::as_const(*this).try_cast>()); + } + + /** + * @brief Tries to cast an instance to a given type. + * @tparam Type Type to which to cast the instance. + * @return A reference to the contained instance. + */ + template + [[nodiscard]] stl::remove_const_t cast() const { + auto *const instance = try_cast>(); + ENTT_ASSERT(instance, "Invalid instance"); + return static_cast(*instance); + } + + /*! @copydoc cast */ + template + [[nodiscard]] stl::remove_const_t cast() { + // forces const on non-reference types to make them work also with wrappers for const references + auto *const instance = try_cast>(); + ENTT_ASSERT(instance, "Invalid instance"); + return static_cast(*instance); + } + + /** + * @brief Converts an object in such a way that a given cast becomes viable. + * @param type Meta type to which the cast is requested. + * @return A valid meta object if convertible, an invalid one otherwise. + */ + [[nodiscard]] meta_any allow_cast(const meta_type &type) const; + + /** + * @brief Converts an object in such a way that a given cast becomes viable. + * @param type Meta type to which the cast is requested. + * @return True if convertible, false otherwise. + */ + [[nodiscard]] bool allow_cast(const meta_type &type); + + /** + * @brief Converts an object in such a way that a given cast becomes viable. + * @tparam Type Type to which the cast is requested. + * @return A valid meta object if convertible, an invalid one otherwise. + */ + template + [[nodiscard]] meta_any allow_cast() const { + if constexpr(!stl::is_reference_v || stl::is_const_v>) { + if(storage.has_value>()) { + return as_ref(); + } else if(*this) { + if constexpr(stl::is_arithmetic_v> || stl::is_enum_v>) { + if(const auto &from = fetch_node(); from.conversion_helper) { + return meta_any{*ctx, static_cast(from.conversion_helper(nullptr, storage.data()))}; + } + } + + if(const auto &from = fetch_node(); from.details != nullptr) { + if(const auto *elem = internal::find_member(from.details->conv, entt::type_hash>::value()); elem != nullptr) { + return elem->conv(*ctx, storage.data()); + } + + for(auto &&curr: from.details->base) { + if(auto other = curr.type(internal::meta_context::from(*ctx)).from_void(*ctx, nullptr, curr.cast(storage.data())); curr.id == entt::type_hash>::value()) { + return other; + } else if(auto from_base = stl::as_const(other).template allow_cast(); from_base) { + return from_base; + } + } + } + } + } + + return meta_any{meta_ctx_arg, *ctx}; + } + + /** + * @brief Converts an object in such a way that a given cast becomes viable. + * @tparam Type Type to which the cast is requested. + * @return True if convertible, false otherwise. + */ + template + [[nodiscard]] bool allow_cast() { + if constexpr(stl::is_reference_v && !stl::is_const_v>) { + return allow_cast &>() && (storage.policy() != any_policy::cref); + } else { + if(storage.has_value>()) { + return true; + } else if(auto other = stl::as_const(*this).allow_cast>(); other) { + if(other.storage.owner()) { + stl::swap(*this, other); + } + + return true; + } + + return false; + } + } + + /*! @copydoc any::emplace */ + template + void emplace(auto &&...args) { + storage.emplace(stl::forward(args)...); + auto *prev = stl::exchange(vtable, &basic_vtable>); + node = (prev == vtable) ? node : nullptr; + } + + /*! @copydoc any::assign */ + bool assign(const meta_any &other); + + /*! @copydoc any::assign */ + bool assign(meta_any &&other); + + /*! @copydoc any::reset */ + void reset() { + storage.reset(); + node = nullptr; + vtable = nullptr; + } + + /** + * @brief Returns a sequence container proxy. + * @return A sequence container proxy for the underlying object. + */ + [[nodiscard]] meta_sequence_container as_sequence_container() noexcept { + meta_sequence_container proxy{}; + if(*this) { vtable(internal::meta_traits::is_sequence_container, *this, &proxy); } + return proxy; + } + + /*! @copydoc as_sequence_container */ + [[nodiscard]] meta_sequence_container as_sequence_container() const noexcept { + meta_sequence_container proxy{}; + if(*this) { vtable(internal::meta_traits::is_sequence_container, as_ref(), &proxy); } + return proxy; + } + + /** + * @brief Returns an associative container proxy. + * @return An associative container proxy for the underlying object. + */ + [[nodiscard]] meta_associative_container as_associative_container() noexcept { + meta_associative_container proxy{}; + if(*this) { vtable(internal::meta_traits::is_associative_container, *this, &proxy); } + return proxy; + } + + /*! @copydoc as_associative_container */ + [[nodiscard]] meta_associative_container as_associative_container() const noexcept { + meta_associative_container proxy{}; + if(*this) { vtable(internal::meta_traits::is_associative_container, as_ref(), &proxy); } + return proxy; + } + + /** + * @brief Indirection operator for dereferencing opaque objects. + * @return A wrapper that shares a reference to an unmanaged object if the + * wrapped element is dereferenceable, an invalid meta any otherwise. + */ + [[nodiscard]] meta_any operator*() noexcept { + meta_any ret{meta_ctx_arg, *ctx}; + if(*this) { vtable(internal::meta_traits::is_pointer, *this, &ret); } + return ret; + } + + /*! @copydoc operator* */ + [[nodiscard]] meta_any operator*() const noexcept { + meta_any ret{meta_ctx_arg, *ctx}; + if(*this) { vtable(internal::meta_traits::is_pointer, as_ref(), &ret); } + return ret; + } + + /*! @copydoc any::operator bool */ + [[nodiscard]] explicit operator bool() const noexcept { + return !(vtable == nullptr); + } + + /*! @copydoc any::operator== */ + [[nodiscard]] bool operator==(const meta_any &other) const noexcept { + return (ctx == other.ctx) && (!*this == !other) && (storage == other.storage); + } + + /*! @copydoc any::as_ref */ + [[nodiscard]] meta_any as_ref() noexcept { + return meta_any{*this, storage.as_ref()}; + } + + /*! @copydoc any::as_ref */ + [[nodiscard]] meta_any as_ref() const noexcept { + return meta_any{*this, storage.as_ref()}; + } + + /** + * @brief Returns the underlying storage. + * @return The underlyig storage. + */ + [[nodiscard]] const any &base() const noexcept { + return storage; + } + + /** + * @brief Returns the underlying meta context. + * @return The underlying meta context. + */ + [[nodiscard]] const meta_ctx &context() const noexcept { + return *ctx; + } + +private: + any storage{}; + const meta_ctx *ctx{&locator::value_or()}; + mutable const internal::meta_type_node *node{}; + vtable_type *vtable{}; +}; + +/** + * @brief Forwards its argument and avoids copies for lvalue references. + * @param value Parameter to use to construct the instance. + * @param ctx The context from which to search for meta types. + * @return A properly initialized and not necessarily owning wrapper. + */ +[[nodiscard]] meta_any forward_as_meta(const meta_ctx &ctx, auto &&value) { + return meta_any{ctx, stl::in_place_type, stl::forward(value)}; +} + +/** + * @brief Forwards its argument and avoids copies for lvalue references. + * @param value Parameter to use to construct the instance. + * @return A properly initialized and not necessarily owning wrapper. + */ +[[nodiscard]] meta_any forward_as_meta(auto &&value) { + return forward_as_meta(locator::value_or(), stl::forward(value)); +} + +/*! @brief Opaque pointers to instances of any type. */ +class meta_handle { + meta_handle(int, auto &value, auto &&...args) + requires stl::same_as, meta_any> + : any{stl::forward(args)..., value.as_ref()} {} + + meta_handle(char, auto &value, auto &&...args) + : any{stl::forward(args)..., stl::in_place_type, value} {} + +public: + /*! Default constructor. */ + meta_handle() = default; + + /** + * @brief Creates a handle that points to an unmanaged object. + * @param ctx The context from which to search for meta types. + * @param value An instance of an object to use to initialize the handle. + */ + meta_handle(const meta_ctx &ctx, auto &value) + requires (!stl::same_as, meta_handle>) + : meta_handle{0, value, ctx} {} + + /** + * @brief Creates a handle that points to an unmanaged object. + * @param value An instance of an object to use to initialize the handle. + */ + meta_handle(auto &value) + requires (!stl::same_as, meta_handle>) + : meta_handle{0, value} {} + + /** + * @brief Context aware move constructor. + * @param area The context from which to search for meta types. + * @param other The instance to move from. + */ + meta_handle(const meta_ctx &area, meta_handle &&other) + : any{area, stl::move(other.any)} {} + + /*! @brief Default copy constructor, deleted on purpose. */ + meta_handle(const meta_handle &) = delete; + + /*! @brief Default move constructor. */ + meta_handle(meta_handle &&) = default; + + /*! @brief Default destructor. */ + ~meta_handle() = default; + + /** + * @brief Default copy assignment operator, deleted on purpose. + * @return This meta handle. + */ + meta_handle &operator=(const meta_handle &) = delete; + + /** + * @brief Default move assignment operator. + * @return This meta handle. + */ + meta_handle &operator=(meta_handle &&) = default; + + /** + * @brief Returns false if a handle is invalid, true otherwise. + * @return False if the handle is invalid, true otherwise. + */ + [[nodiscard]] explicit operator bool() const noexcept { + return static_cast(any); + } + + /** + * @brief Access operator for accessing the contained opaque object. + * @return A wrapper that shares a reference to an unmanaged object. + */ + [[nodiscard]] meta_any *operator->() { + return &any; + } + +private: + meta_any any{}; +}; + +/*! @brief Opaque wrapper for user defined data of any type. */ +struct meta_custom { + /*! @brief Default constructor. */ + meta_custom() noexcept = default; + + /** + * @brief Basic constructor for meta objects. + * @param curr The underlying node with which to construct the instance. + */ + meta_custom(const internal::meta_custom_node &curr) noexcept + : node{&curr} {} + + /** + * @brief Generic conversion operator. + * @tparam Type Type to which conversion is requested. + */ + template + [[nodiscard]] operator Type *() const noexcept { + return ((node != nullptr) && (type_hash>::value() == node->id)) ? static_cast(node->value.get()) : nullptr; + } + + /** + * @brief Generic conversion operator. + * @tparam Type Type to which conversion is requested. + */ + template + [[nodiscard]] operator Type &() const noexcept { + ENTT_ASSERT(static_cast(*this) != nullptr, "Invalid type"); + return *static_cast(node->value.get()); + } + +private: + const internal::meta_custom_node *node{}; +}; + +/** + * @brief Common opaque wrapper for meta objects. + * @tparam Type Underlying meta node type. + */ +template +struct meta_object: protected internal::basic_meta_object { + /*! @brief Underlying meta node type. */ + using node_type = Type; + /*! @brief Unsigned integer type. */ + using size_type = stl::size_t; + + /*! @brief Default constructor. */ + meta_object() noexcept = default; + + /** + * @brief Context aware constructor for meta objects. + * @param area The context from which to search for meta types. + * @param curr The underlying node with which to construct the instance. + */ + meta_object(const meta_ctx &area, const node_type &curr) noexcept + : internal::basic_meta_object{&curr, &area} { + } + + /** + * @brief Returns true if an object is valid, false otherwise. + * @return True if the object is valid, false otherwise. + */ + [[nodiscard]] explicit operator bool() const noexcept { + return (this->node != nullptr); + } + + /** + * @brief Checks if two objects refer to the same type. + * @param other The object with which to compare. + * @return True if the objects refer to the same type, false otherwise. + */ + [[nodiscard]] bool operator==(const meta_object &other) const noexcept { + return (this->ctx == other.ctx) && (this->node == other.node); + } +}; + +/*! @brief Opaque wrapper for data members. */ +struct meta_data: meta_object { + using meta_object::meta_object; + + /** + * @brief Returns the name assigned to a data member, if any. + * @return The name assigned to the data member, if any. + */ + [[nodiscard]] stl::string_view name() const noexcept { + return (node_or_assert().name == nullptr) ? stl::string_view{} : stl::string_view{node_or_assert().name}; + } + + /** + * @brief Returns the number of arguments of a data member's setter. + * @return The number of arguments accepted by the data member's setter. + */ + [[nodiscard]] size_type set_arity() const noexcept { + return node_or_assert().set_arity; + } + + /** + * @brief Returns the number of arguments of a data member's getter. + * @return The number of arguments accepted by the data member's getter. + */ + [[nodiscard]] size_type get_arity() const noexcept { + return node_or_assert().get_arity; + } + + /** + * @brief Indicates whether a data member is constant or not. + * @return True if the data member is constant, false otherwise. + */ + [[nodiscard]] bool is_const() const noexcept { + return !!(node_or_assert().traits & internal::meta_traits::is_const); + } + + /** + * @brief Indicates whether a data member is static or not. + * @return True if the data member is static, false otherwise. + */ + [[nodiscard]] bool is_static() const noexcept { + return !!(node_or_assert().traits & internal::meta_traits::is_static); + } + + /*! @copydoc meta_any::type */ + [[nodiscard]] inline meta_type type() const noexcept; + + /** + * @brief Sets the value of a given variable. + * @tparam Instance Type of instance to operate on. + * @param instance An instance that fits the underlying type. + * @param args Parameters to use to set the underlying variable. + * @return True in case of success, false otherwise. + */ + template + // NOLINTNEXTLINE(modernize-use-nodiscard) + bool set(Instance &&instance, auto &&...args) const { + return (sizeof...(args) >= set_arity()) && node_or_assert().set(meta_handle{*ctx, stl::forward(instance)}, stl::array{meta_any{*ctx, stl::forward(args)}...}.data()); + } + + /** + * @brief Gets the value of a given variable. + * @tparam Instance Type of instance to operate on. + * @param instance An instance that fits the underlying type. + * @param args Parameters to use to get the underlying variable, if any. + * @return A wrapper containing the value of the underlying variable. + */ + template + [[nodiscard]] meta_any get(Instance &&instance, auto &&...args) const { + return (sizeof...(args) >= get_arity()) ? node_or_assert().get(meta_handle{*ctx, stl::forward(instance)}, stl::array{meta_any{*ctx, stl::forward(args)}...}.data()) : meta_any{meta_ctx_arg, *ctx}; + } + + /** + * @brief Returns the type of the i-th argument of a data member's setter. + * @param index Index of the argument of which to return the type. + * @return The type of the i-th argument of a data member's setter. + */ + [[nodiscard]] inline meta_type set_arg(size_type index) const noexcept; + + /** + * @brief Returns the type of the i-th argument of a data member's getter. + * @param index Index of the argument of which to return the type. + * @return The type of the i-th argument of a data member's getter. + */ + [[nodiscard]] inline meta_type get_arg(size_type index) const noexcept; + + /** + * @brief Returns all meta traits for a given meta object. + * @tparam Type The type to convert the meta traits to. + * @return The registered meta traits, if any. + */ + template + [[nodiscard]] Type traits() const noexcept { + return internal::meta_to_user_traits(node_or_assert().traits); + } + + /** + * @brief Returns user defined data for a given meta object. + * @return User defined arbitrary data. + */ + [[nodiscard]] meta_custom custom() const noexcept { + return {node_or_assert().custom}; + } +}; + +/*! @brief Opaque wrapper for member functions. */ +struct meta_func: meta_object { + using meta_object::meta_object; + + /** + * @brief Returns the name assigned to a member function, if any. + * @return The name assigned to the member function, if any. + */ + [[nodiscard]] stl::string_view name() const noexcept { + return (node_or_assert().name == nullptr) ? stl::string_view{} : stl::string_view{node_or_assert().name}; + } + + /** + * @brief Returns the number of arguments accepted by a member function. + * @return The number of arguments accepted by the member function. + */ + [[nodiscard]] size_type arity() const noexcept { + return node_or_assert().arity; + } + + /** + * @brief Indicates whether a member function is constant or not. + * @return True if the member function is constant, false otherwise. + */ + [[nodiscard]] bool is_const() const noexcept { + return !!(node_or_assert().traits & internal::meta_traits::is_const); + } + + /** + * @brief Indicates whether a member function is static or not. + * @return True if the member function is static, false otherwise. + */ + [[nodiscard]] bool is_static() const noexcept { + return !!(node_or_assert().traits & internal::meta_traits::is_static); + } + + /** + * @brief Returns the return type of a member function. + * @return The return type of the member function. + */ + [[nodiscard]] inline meta_type ret() const noexcept; + + /** + * @brief Returns the type of the i-th argument of a member function. + * @param index Index of the argument of which to return the type. + * @return The type of the i-th argument of a member function. + */ + [[nodiscard]] inline meta_type arg(size_type index) const noexcept; + + /** + * @brief Invokes the underlying function, if possible. + * @tparam Instance Type of instance to operate on. + * @param instance An instance that fits the underlying type. + * @param args Parameters to use to invoke the function. + * @return A wrapper containing the returned value, if any. + */ + template + // NOLINTNEXTLINE(modernize-use-nodiscard) + meta_any invoke(Instance &&instance, auto &&...args) const { + return (sizeof...(args) == arity()) ? node_or_assert().invoke(meta_handle{*ctx, stl::forward(instance)}, stl::array{meta_any{*ctx, stl::forward(args)}...}.data()) : meta_any{meta_ctx_arg, *ctx}; + } + + /*! @copydoc meta_data::traits */ + template + [[nodiscard]] Type traits() const noexcept { + return internal::meta_to_user_traits(node_or_assert().traits); + } + + /*! @copydoc meta_data::custom */ + [[nodiscard]] meta_custom custom() const noexcept { + return {node_or_assert().custom}; + } + + /** + * @brief Returns the next overload of a given function, if any. + * @return The next overload of the given function, if any. + */ + [[nodiscard]] meta_func next() const { + return (node_or_assert().next != nullptr) ? meta_func{*ctx, *node_or_assert().next} : meta_func{}; + } +}; + +/*! @brief Opaque wrapper for base types. */ +struct meta_base: meta_object { + using meta_object::meta_object; + + /*! @copydoc meta_any::type */ + [[nodiscard]] inline meta_type type() const noexcept; +}; + +/*! @brief Opaque wrapper for types. */ +class meta_type { + friend class meta_any; + + [[nodiscard]] const auto &fetch_node() const { + return (node == nullptr) ? internal::resolve(internal::meta_context::from(*ctx)) : *node; + } + + [[nodiscard]] auto lookup(meta_handle *const args, const auto sz, [[maybe_unused]] bool constness, auto next) const { + decltype(next()) candidate = nullptr; + size_type same{}; + bool ambiguous{}; + + for(auto curr = next(); curr; curr = next()) { + if constexpr(stl::is_same_v, internal::meta_func_node>) { + if(constness && !(curr->traits & internal::meta_traits::is_const)) { + continue; + } + } + + if(curr->arity == sz) { + size_type match{}; + size_type pos{}; + + // NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic) - waiting for C++20 (and stl::span) + for(; pos < sz; ++pos) { + const auto other = curr->arg(*ctx, pos); + const auto type = args[pos]->type(); + + if(const auto &info = other.info(); info == type.info()) { + ++match; + } else if(!(type.fetch_node().conversion_helper && other.fetch_node().conversion_helper) && !(type.fetch_node().details && (internal::find_member(type.fetch_node().details->base, info.hash()) || internal::find_member(type.fetch_node().details->conv, info.hash())))) { + break; + } + } + // NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic) + + if(pos == sz) { + if(!candidate || match > same) { + candidate = curr; + same = match; + ambiguous = false; + } else if(match == same) { + if constexpr(stl::is_same_v, internal::meta_func_node>) { + if(!!(curr->traits & internal::meta_traits::is_const) != !!(candidate->traits & internal::meta_traits::is_const)) { + candidate = !!(candidate->traits & internal::meta_traits::is_const) ? curr : candidate; + ambiguous = false; + continue; + } + } + + ambiguous = true; + } + } + } + } + + return ambiguous ? nullptr : candidate; + } + +public: + /*! @brief Unsigned integer type. */ + using size_type = internal::meta_type_node::size_type; + + /*! @brief Default constructor. */ + meta_type() noexcept = default; + + /** + * @brief Context aware constructor for meta objects. + * @param area The context from which to search for meta types. + * @param curr The underlying node with which to construct the instance. + */ + meta_type(const meta_ctx &area, const internal::meta_type_node &curr) noexcept + : node{&curr}, + ctx{&area} {} + + /** + * @brief Returns the type info object of the underlying type. + * @return The type info object of the underlying type. + */ + [[nodiscard]] const type_info &info() const noexcept { + return *fetch_node().info; + } + + /** + * @brief Returns the alias assigned to a type. + * @return The alias assigned to the type. + */ + [[nodiscard]] id_type alias() const noexcept { + return fetch_node().alias; + } + + /** + * @brief Returns the name assigned to a type, if any. + * @return The name assigned to the type, if any. + */ + [[nodiscard]] stl::string_view name() const noexcept { + return (fetch_node().name == nullptr) ? stl::string_view{} : stl::string_view{fetch_node().name}; + } + + /** + * @brief Returns the size of the underlying type if known. + * @return The size of the underlying type if known, 0 otherwise. + */ + [[nodiscard]] size_type size_of() const noexcept { + return fetch_node().size_of; + } + + /** + * @brief Checks whether a type refers to an arithmetic type or not. + * @return True if the underlying type is an arithmetic type, false + * otherwise. + */ + [[nodiscard]] bool is_arithmetic() const noexcept { + return !!(fetch_node().traits & internal::meta_traits::is_arithmetic); + } + + /** + * @brief Checks whether a type refers to an integral type or not. + * @return True if the underlying type is an integral type, false otherwise. + */ + [[nodiscard]] bool is_integral() const noexcept { + return !!(fetch_node().traits & internal::meta_traits::is_integral); + } + + /** + * @brief Checks whether a type refers to a signed type or not. + * @return True if the underlying type is a signed type, false otherwise. + */ + [[nodiscard]] bool is_signed() const noexcept { + return !!(fetch_node().traits & internal::meta_traits::is_signed); + } + + /** + * @brief Checks whether a type refers to an array type or not. + * @return True if the underlying type is an array type, false otherwise. + */ + [[nodiscard]] bool is_array() const noexcept { + return !!(fetch_node().traits & internal::meta_traits::is_array); + } + + /** + * @brief Checks whether a type refers to an enum or not. + * @return True if the underlying type is an enum, false otherwise. + */ + [[nodiscard]] bool is_enum() const noexcept { + return !!(fetch_node().traits & internal::meta_traits::is_enum); + } + + /** + * @brief Checks whether a type refers to a class or not. + * @return True if the underlying type is a class, false otherwise. + */ + [[nodiscard]] bool is_class() const noexcept { + return !!(fetch_node().traits & internal::meta_traits::is_class); + } + + /** + * @brief Checks whether a type refers to a pointer or not. + * @return True if the underlying type is a pointer, false otherwise. + */ + [[nodiscard]] bool is_pointer() const noexcept { + return !!(fetch_node().traits & internal::meta_traits::is_pointer); + } + + /** + * @brief Provides the type for which the pointer is defined. + * @return The type for which the pointer is defined or this type if it + * doesn't refer to a pointer type. + */ + [[nodiscard]] meta_type remove_pointer() const noexcept { + return meta_type{*ctx, fetch_node().remove_pointer(internal::meta_context::from(*ctx))}; + } + + /** + * @brief Checks whether a type is a pointer-like type or not. + * @return True if the underlying type is pointer-like, false otherwise. + */ + [[nodiscard]] bool is_pointer_like() const noexcept { + return !!(fetch_node().traits & internal::meta_traits::is_pointer_like); + } + + /** + * @brief Checks whether a type refers to a sequence container or not. + * @return True if the type is a sequence container, false otherwise. + */ + [[nodiscard]] bool is_sequence_container() const noexcept { + return !!(fetch_node().traits & internal::meta_traits::is_sequence_container); + } + + /** + * @brief Checks whether a type refers to an associative container or not. + * @return True if the type is an associative container, false otherwise. + */ + [[nodiscard]] bool is_associative_container() const noexcept { + return !!(fetch_node().traits & internal::meta_traits::is_associative_container); + } + + /** + * @brief Checks whether a type refers to a template specialization or not. + * @return True if the type is a template specialization, false otherwise. + */ + [[nodiscard]] bool is_template_specialization() const noexcept { + return (fetch_node().templ.arity != 0u); + } + + /** + * @brief Returns the number of template arguments. + * @return The number of template arguments. + */ + [[nodiscard]] size_type template_arity() const noexcept { + return fetch_node().templ.arity; + } + + /** + * @brief Returns a tag for the class template of the underlying type. + * @return The tag for the class template of the underlying type. + */ + [[nodiscard]] meta_type template_type() const noexcept { + return (fetch_node().templ.resolve != nullptr) ? meta_type{*ctx, fetch_node().templ.resolve(internal::meta_context::from(*ctx))} : meta_type{}; + } + + /** + * @brief Returns the type of the i-th template argument of a type. + * @param index Index of the template argument of which to return the type. + * @return The type of the i-th template argument of a type. + */ + [[nodiscard]] meta_type template_arg(const size_type index) const noexcept { + return index < template_arity() ? meta_type{*ctx, fetch_node().templ.arg(internal::meta_context::from(*ctx), index)} : meta_type{}; + } + + /** + * @brief Checks if a type supports direct casting to another type. + * @param other The meta type to test for. + * @return True if direct casting is allowed, false otherwise. + */ + [[nodiscard]] bool can_cast(const meta_type &other) const noexcept { + // casting this is UB in all cases but we aren't going to use the resulting pointer, so... + return other && ((*this == other) || (internal::try_cast(internal::meta_context::from(*ctx), fetch_node(), other.fetch_node().info->hash(), this) != nullptr)); + } + + /** + * @brief Checks whether a type supports conversion to another type. + * @param other The meta type to test for. + * @return True if the conversion is allowed, false otherwise. + */ + [[nodiscard]] bool can_convert(const meta_type &other) const noexcept { + if(const auto &to = other.info().hash(); (info().hash() == to) || ((fetch_node().conversion_helper != nullptr) && (other.is_arithmetic() || other.is_enum()))) { + return true; + } else if(const auto &from = fetch_node(); from.details) { + if(const auto *elem = internal::find_member(from.details->conv, to); elem != nullptr) { + return true; + } + + for(auto &&curr: from.details->base) { + if(curr.id == to || meta_type{*ctx, curr.type(internal::meta_context::from(*ctx))}.can_convert(other)) { + return true; + } + } + } + + return false; + } + + /** + * @brief Returns a range to visit registered top-level base meta types. + * @return An iterable range to visit registered top-level base meta types. + */ + [[nodiscard]] meta_range base() const noexcept { + using range_type = meta_range; + return fetch_node().details ? range_type{{*ctx, fetch_node().details->base.cbegin()}, {*ctx, fetch_node().details->base.cend()}} : range_type{}; + } + + /** + * @brief Returns a range to visit registered top-level meta data. + * @return An iterable range to visit registered top-level meta data. + */ + [[nodiscard]] meta_range data() const noexcept { + using range_type = meta_range; + return fetch_node().details ? range_type{{*ctx, fetch_node().details->data.cbegin()}, {*ctx, fetch_node().details->data.cend()}} : range_type{}; + } + + /** + * @brief Lookup utility for meta data (bases are also visited). + * @param id Unique identifier. + * @param recursive True for a search in the base classes, false otherwise. + * @return The registered meta data for the given identifier, if any. + */ + [[nodiscard]] meta_data data(const id_type id, const bool recursive = true) const { + const auto *elem = internal::look_for<&internal::meta_type_descriptor::data>(internal::meta_context::from(*ctx), fetch_node(), id, recursive); + return (elem != nullptr) ? meta_data{*ctx, *elem} : meta_data{}; + } + + /** + * @brief Returns a range to visit registered top-level functions. + * @return An iterable range to visit registered top-level functions. + */ + [[nodiscard]] meta_range func() const noexcept { + using return_type = meta_range; + return fetch_node().details ? return_type{{*ctx, fetch_node().details->func.cbegin()}, {*ctx, fetch_node().details->func.cend()}} : return_type{}; + } + + /** + * @brief Lookup utility for meta functions (bases are also visited). + * @param id Unique identifier. + * @param recursive True for a search in the base classes, false otherwise. + * @return The registered meta function for the given identifier, if any. + */ + [[nodiscard]] meta_func func(const id_type id, const bool recursive = true) const { + const auto *elem = internal::look_for<&internal::meta_type_descriptor::func>(internal::meta_context::from(*ctx), fetch_node(), id, recursive); + return (elem != nullptr) ? meta_func{*ctx, *elem} : meta_func{}; + } + + /** + * @copybrief construct + * @param args Parameters to use to construct the instance. + * @return A wrapper containing the new instance, if any. + */ + [[nodiscard]] meta_any construct(auto &&...args) const { + if(const auto &ref = fetch_node(); ref.details) { + if(const auto *candidate = lookup(stl::array{meta_handle{*ctx, args}...}.data(), sizeof...(args), false, [first = ref.details->ctor.cbegin(), last = ref.details->ctor.cend()]() mutable { return first == last ? nullptr : &*(first++); }); candidate) { + return candidate->invoke(*ctx, stl::array{meta_any{*ctx, stl::forward(args)}...}.data()); + } + } + + if(const auto &ref = fetch_node(); (sizeof...(args) == 0u) && (ref.default_constructor != nullptr)) { + return ref.default_constructor(*ctx); + } + + return meta_any{meta_ctx_arg, *ctx}; + } + + /** + * @brief Wraps an opaque element of the underlying type. + * @param elem A valid pointer to an element of the underlying type. + * @param transfer_ownership True to transfer ownership, false otherwise. + * @return A wrapper that references the given instance. + */ + [[nodiscard]] meta_any from_void(void *elem, bool transfer_ownership = false) const { + return ((elem != nullptr) && (fetch_node().from_void != nullptr)) ? fetch_node().from_void(*ctx, elem, transfer_ownership ? elem : nullptr) : meta_any{meta_ctx_arg, *ctx}; + } + + /** + * @brief Wraps an opaque element of the underlying type. + * @param elem A valid pointer to an element of the underlying type. + * @return A wrapper that references the given instance. + */ + [[nodiscard]] meta_any from_void(const void *elem) const { + return ((elem != nullptr) && (fetch_node().from_void != nullptr)) ? fetch_node().from_void(*ctx, nullptr, elem) : meta_any{meta_ctx_arg, *ctx}; + } + + /** + * @copybrief invoke + * @param id Unique identifier. + * @tparam Instance Type of instance to operate on. + * @param instance An instance that fits the underlying type. + * @param args Parameters to use to invoke the function. + * @return A wrapper containing the returned value, if any. + */ + template + // NOLINTNEXTLINE(modernize-use-nodiscard) + meta_any invoke(const id_type id, Instance &&instance, auto &&...args) const { + meta_handle wrapped{*ctx, stl::forward(instance)}; + + if(const auto &ref = fetch_node(); ref.details) { + if(auto *elem = internal::find_member(ref.details->func, id); elem != nullptr) { + if(const auto *candidate = lookup(stl::array{meta_handle{*ctx, args}...}.data(), sizeof...(args), (wrapped->base().policy() == any_policy::cref), [curr = elem]() mutable { return (curr != nullptr) ? stl::exchange(curr, curr->next.get()) : nullptr; }); candidate) { + return candidate->invoke(stl::move(wrapped), stl::array{meta_any{*ctx, stl::forward(args)}...}.data()); + } + } + } + + for(auto &&curr: base()) { + if(auto elem = curr.second.type().invoke(id, *wrapped.operator->(), stl::forward(args)...); elem) { + return elem; + } + } + + return meta_any{meta_ctx_arg, *ctx}; + } + + /** + * @brief Sets the value of a given variable. + * @tparam Instance Type of instance to operate on. + * @param id Unique identifier. + * @param instance An instance that fits the underlying type. + * @param args Parameters to use to set the underlying variable. + * @return True in case of success, false otherwise. + */ + template + // NOLINTNEXTLINE(modernize-use-nodiscard) + bool set(const id_type id, Instance &&instance, auto &&...args) const { + const auto candidate = data(id); + return candidate && candidate.set(stl::forward(instance), stl::forward(args)...); + } + + /** + * @brief Gets the value of a given variable. + * @tparam Instance Type of instance to operate on. + * @param id Unique identifier. + * @param instance An instance that fits the underlying type. + * @param args Parameters to use to set the underlying variable, if any. + * @return A wrapper containing the value of the underlying variable. + */ + template + [[nodiscard]] meta_any get(const id_type id, Instance &&instance, auto &&...args) const { + const auto candidate = data(id); + return candidate ? candidate.get(stl::forward(instance), stl::forward(args)...) : meta_any{meta_ctx_arg, *ctx}; + } + + /*! @copydoc meta_data::traits */ + template + [[nodiscard]] Type traits() const noexcept { + return internal::meta_to_user_traits(fetch_node().traits); + } + + /*! @copydoc meta_data::custom */ + [[nodiscard]] meta_custom custom() const noexcept { + return fetch_node().custom; + } + + /*! @copydoc meta_data::operator bool */ + [[nodiscard]] explicit operator bool() const noexcept { + return (node != nullptr); + } + + /*! @copydoc meta_data::operator== */ + [[nodiscard]] bool operator==(const meta_type &other) const noexcept { + return (ctx == other.ctx) && (fetch_node().alias == other.fetch_node().alias); + } + +private: + mutable const internal::meta_type_node *node{}; + const meta_ctx *ctx{&locator::value_or()}; +}; + +[[nodiscard]] inline meta_type meta_any::type() const noexcept { + return *this ? meta_type{*ctx, fetch_node()} : meta_type{}; +} + +inline void meta_any::type(const meta_type &alias) noexcept { + ENTT_ASSERT(storage.info() == alias.info(), "Unexpected type"); + node = alias.node; + ctx = alias.ctx; +} + +// NOLINTNEXTLINE(modernize-use-nodiscard) +meta_any meta_any::invoke(const id_type id, auto &&...args) const { + return type().invoke(id, *this, stl::forward(args)...); +} + +meta_any meta_any::invoke(const id_type id, auto &&...args) { + return type().invoke(id, *this, stl::forward(args)...); +} + +bool meta_any::set(const id_type id, auto &&...args) { + return type().set(id, *this, stl::forward(args)...); +} + +[[nodiscard]] inline meta_any meta_any::get(const id_type id, auto &&...args) const { + return type().get(id, *this, stl::forward(args)...); +} + +[[nodiscard]] inline meta_any meta_any::get(const id_type id, auto &&...args) { + return type().get(id, *this, stl::forward(args)...); +} + +[[nodiscard]] inline meta_any meta_any::allow_cast(const meta_type &type) const { + if(storage.has_value(type.info())) { + return as_ref(); + } else if(*this) { + if(const auto &from = fetch_node(); (from.conversion_helper != nullptr) && (type.is_arithmetic() || type.is_enum())) { + auto other = type.construct(); + const auto value = from.conversion_helper(nullptr, storage.data()); + other.fetch_node().conversion_helper(other.storage.data(), &value); + return other; + } + + if(const auto &from = fetch_node(); from.details) { + if(const auto *elem = internal::find_member(from.details->conv, type.info().hash()); elem != nullptr) { + return elem->conv(*ctx, storage.data()); + } + + for(auto &&curr: from.details->base) { + if(auto other = curr.type(internal::meta_context::from(*ctx)).from_void(*ctx, nullptr, curr.cast(storage.data())); curr.id == type.info().hash()) { + return other; + } else if(auto from_base = stl::as_const(other).allow_cast(type); from_base) { + return from_base; + } + } + } + } + + return meta_any{meta_ctx_arg, *ctx}; +} + +[[nodiscard]] inline bool meta_any::allow_cast(const meta_type &type) { + if(storage.has_value(type.info())) { + return true; + } else if(auto other = stl::as_const(*this).allow_cast(type); other) { + if(other.storage.owner()) { + stl::swap(*this, other); + } + + return true; + } + + return false; +} + +inline bool meta_any::assign(const meta_any &other) { + if(!storage.assign(other.storage)) { + auto value = other.allow_cast(type()); + return storage.assign(value.storage); + } + + return true; +} + +inline bool meta_any::assign(meta_any &&other) { + return storage.assign(stl::move(other.storage)) || storage.assign(stl::as_const(other).allow_cast(type()).storage); +} + +[[nodiscard]] inline meta_type meta_data::type() const noexcept { + return meta_type{*ctx, node_or_assert().type(internal::meta_context::from(*ctx))}; +} + +[[nodiscard]] inline meta_type meta_data::set_arg(const size_type index) const noexcept { + return index < set_arity() ? node_or_assert().set_arg(*ctx, index) : meta_type{}; +} + +[[nodiscard]] inline meta_type meta_data::get_arg(const size_type index) const noexcept { + return index < get_arity() ? node_or_assert().get_arg(*ctx, index) : meta_type{}; +} + +[[nodiscard]] inline meta_type meta_func::ret() const noexcept { + return meta_type{*ctx, node_or_assert().ret(internal::meta_context::from(*ctx))}; +} + +[[nodiscard]] inline meta_type meta_func::arg(const size_type index) const noexcept { + return index < arity() ? node_or_assert().arg(*ctx, index) : meta_type{}; +} + +[[nodiscard]] inline meta_type meta_base::type() const noexcept { + return meta_type{*ctx, node_or_assert().type(internal::meta_context::from(*ctx))}; +} + +/*! @cond ENTT_INTERNAL */ +class meta_sequence_container::meta_iterator final { + using vtable_type = void(const void *, const stl::ptrdiff_t, meta_any *); + + template + static void basic_vtable(const void *value, const stl::ptrdiff_t offset, meta_any *other) { + const auto &it = *static_cast(value); + other ? other->emplace(*it) : stl::advance(const_cast(it), offset); + } + +public: + using value_type = meta_any; + using pointer = input_iterator_pointer; + using reference = value_type; + using difference_type = stl::ptrdiff_t; + using iterator_category = stl::input_iterator_tag; + using iterator_concept = stl::bidirectional_iterator_tag; + + meta_iterator() = default; + + meta_iterator(const meta_ctx &area, stl::bidirectional_iterator auto iter) noexcept + : ctx{&area}, + vtable{&basic_vtable}, + handle{iter} {} + + meta_iterator &operator++() noexcept { + return vtable(handle.data(), 1, nullptr), *this; + } + + meta_iterator operator++(int value) noexcept { + meta_iterator orig = *this; + vtable(handle.data(), ++value, nullptr); + return orig; + } + + meta_iterator &operator--() noexcept { + return vtable(handle.data(), -1, nullptr), *this; + } + + meta_iterator operator--(int value) noexcept { + meta_iterator orig = *this; + vtable(handle.data(), --value, nullptr); + return orig; + } + + [[nodiscard]] reference operator*() const { + reference other{meta_ctx_arg, *ctx}; + vtable(handle.data(), 0, &other); + return other; + } + + [[nodiscard]] pointer operator->() const { + return operator*(); + } + + [[nodiscard]] explicit operator bool() const noexcept { + return (vtable != nullptr); + } + + [[nodiscard]] bool operator==(const meta_iterator &other) const noexcept { + return handle == other.handle; + } + + [[nodiscard]] const any &base() const noexcept { + return handle; + } + +private: + const meta_ctx *ctx{}; + vtable_type *vtable{}; + any handle{}; +}; + +class meta_associative_container::meta_iterator final { + using vtable_type = void(const void *, stl::pair *); + + template + static void basic_vtable(const void *value, stl::pair *other) { + if(const auto &it = *static_cast(value); other) { + if constexpr(KeyOnly) { + other->first.emplace(*it); + } else { + other->first.emplacefirst))>(it->first); + other->second.emplacesecond))>(it->second); + } + } else { + ++const_cast(it); + } + } + +public: + using value_type = stl::pair; + using pointer = input_iterator_pointer; + using reference = value_type; + using difference_type = stl::ptrdiff_t; + using iterator_category = stl::input_iterator_tag; + using iterator_concept = stl::forward_iterator_tag; + + meta_iterator() = default; + + template + meta_iterator(const meta_ctx &area, stl::bool_constant, stl::forward_iterator auto iter) noexcept + : ctx{&area}, + vtable{&basic_vtable}, + handle{iter} {} + + meta_iterator &operator++() noexcept { + return vtable(handle.data(), nullptr), *this; + } + + meta_iterator operator++(int) noexcept { + meta_iterator orig = *this; + vtable(handle.data(), nullptr); + return orig; + } + + [[nodiscard]] reference operator*() const { + reference other{{meta_ctx_arg, *ctx}, {meta_ctx_arg, *ctx}}; + vtable(handle.data(), &other); + return other; + } + + [[nodiscard]] pointer operator->() const { + return operator*(); + } + + [[nodiscard]] explicit operator bool() const noexcept { + return (vtable != nullptr); + } + + [[nodiscard]] bool operator==(const meta_iterator &other) const noexcept { + return handle == other.handle; + } + +private: + const meta_ctx *ctx{}; + vtable_type *vtable{}; + any handle{}; +}; + +/*! @endcond */ + +/** + * @brief Returns the meta value type of a container. + * @return The meta value type of the container. + */ +[[nodiscard]] inline meta_type meta_sequence_container::value_type() const noexcept { + return (value_type_node != nullptr) ? meta_type{*ctx, value_type_node(internal::meta_context::from(*ctx))} : meta_type{}; +} + +/** + * @brief Returns the size of a container. + * @return The size of the container. + */ +[[nodiscard]] inline meta_sequence_container::size_type meta_sequence_container::size() const noexcept { + return size_fn(data); +} + +/** + * @brief Resizes a container to contain a given number of elements. + * @param sz The new size of the container. + * @return True in case of success, false otherwise. + */ +inline bool meta_sequence_container::resize(const size_type sz) { + return !const_only && resize_fn(const_cast(data), sz); +} + +/** + * @brief Clears the content of a container. + * @return True in case of success, false otherwise. + */ +inline bool meta_sequence_container::clear() { + return !const_only && clear_fn(const_cast(data)); +} + +/** + * @brief Reserves storage for at least the given number of elements. + * @param sz The new capacity of the container. + * @return True in case of success, false otherwise. + */ +inline bool meta_sequence_container::reserve(const size_type sz) { + return !const_only && reserve_fn(const_cast(data), sz); +} + +/** + * @brief Returns an iterator to the first element of a container. + * @return An iterator to the first element of the container. + */ +[[nodiscard]] inline meta_sequence_container::iterator meta_sequence_container::begin() { + return begin_end_fn(*ctx, const_only ? nullptr : const_cast(data), data, false); +} + +/** + * @brief Returns an iterator that is past the last element of a container. + * @return An iterator that is past the last element of the container. + */ +[[nodiscard]] inline meta_sequence_container::iterator meta_sequence_container::end() { + return begin_end_fn(*ctx, const_only ? nullptr : const_cast(data), data, true); +} + +/** + * @brief Inserts an element at a specified location of a container. + * @param it Iterator before which the element will be inserted. + * @param value Element value to insert. + * @return A possibly invalid iterator to the inserted element. + */ +inline meta_sequence_container::iterator meta_sequence_container::insert(const iterator &it, meta_any value) { + // this abomination is necessary because only on macos value_type and const_reference are different types for stl::vector + if(const auto &vtype = value_type_node(internal::meta_context::from(*ctx)); !const_only && (value.allow_cast({*ctx, vtype}) || value.allow_cast({*ctx, const_reference_node(internal::meta_context::from(*ctx))}))) { + const bool is_value_type = (value.type().info() == *vtype.info); + return insert_fn(*ctx, const_cast(data), is_value_type ? value.base().data() : nullptr, is_value_type ? nullptr : value.base().data(), it); + } + + return iterator{}; +} + +/** + * @brief Removes a given element from a container. + * @param it Iterator to the element to remove. + * @return A possibly invalid iterator following the last removed element. + */ +inline meta_sequence_container::iterator meta_sequence_container::erase(const iterator &it) { + return const_only ? iterator{} : erase_fn(*ctx, const_cast(data), it); +} + +/** + * @brief Returns a reference to the element at a given location of a container. + * @param pos The position of the element to return. + * @return A reference to the requested element properly wrapped. + */ +[[nodiscard]] inline meta_any meta_sequence_container::operator[](const size_type pos) { + auto it = begin(); + it.operator++(static_cast(pos) - 1); + return *it; +} + +/** + * @brief Returns false if a proxy is invalid, true otherwise. + * @return False if the proxy is invalid, true otherwise. + */ +[[nodiscard]] inline meta_sequence_container::operator bool() const noexcept { + return (data != nullptr); +} + +/** + * @brief Returns the meta key type of a container. + * @return The meta key type of the a container. + */ +[[nodiscard]] inline meta_type meta_associative_container::key_type() const noexcept { + return (key_type_node != nullptr) ? meta_type{*ctx, key_type_node(internal::meta_context::from(*ctx))} : meta_type{}; +} + +/** + * @brief Returns the meta mapped type of a container. + * @return The meta mapped type of the a container. + */ +[[nodiscard]] inline meta_type meta_associative_container::mapped_type() const noexcept { + return (mapped_type_node != nullptr) ? meta_type{*ctx, mapped_type_node(internal::meta_context::from(*ctx))} : meta_type{}; +} + +/*! @copydoc meta_sequence_container::value_type */ +[[nodiscard]] inline meta_type meta_associative_container::value_type() const noexcept { + return (value_type_node != nullptr) ? meta_type{*ctx, value_type_node(internal::meta_context::from(*ctx))} : meta_type{}; +} + +/*! @copydoc meta_sequence_container::size */ +[[nodiscard]] inline meta_associative_container::size_type meta_associative_container::size() const noexcept { + return size_fn(data); +} + +/*! @copydoc meta_sequence_container::clear */ +inline bool meta_associative_container::clear() { + return !const_only && clear_fn(const_cast(data)); +} + +/*! @copydoc meta_sequence_container::reserve */ +inline bool meta_associative_container::reserve(const size_type sz) { + return !const_only && reserve_fn(const_cast(data), sz); +} + +/*! @copydoc meta_sequence_container::begin */ +[[nodiscard]] inline meta_associative_container::iterator meta_associative_container::begin() { + return begin_end_fn(*ctx, const_only ? nullptr : const_cast(data), data, false); +} + +/*! @copydoc meta_sequence_container::end */ +[[nodiscard]] inline meta_associative_container::iterator meta_associative_container::end() { + return begin_end_fn(*ctx, const_only ? nullptr : const_cast(data), data, true); +} + +/** + * @brief Inserts a key-only or key/value element into a container. + * @param key The key of the element to insert. + * @param value The value of the element to insert, if needed. + * @return A bool denoting whether the insertion took place. + */ +inline bool meta_associative_container::insert(meta_any key, meta_any value = {}) { + return !const_only && key.allow_cast(meta_type{*ctx, key_type_node(internal::meta_context::from(*ctx))}) + && ((mapped_type_node == nullptr) || value.allow_cast(meta_type{*ctx, mapped_type_node(internal::meta_context::from(*ctx))})) + && insert_fn(const_cast(data), key.base().data(), value.base().data()); +} + +/** + * @brief Removes the specified element from a container. + * @param key The key of the element to remove. + * @return A bool denoting whether the removal took place. + */ +inline meta_associative_container::size_type meta_associative_container::erase(meta_any key) { + return (!const_only && key.allow_cast(meta_type{*ctx, key_type_node(internal::meta_context::from(*ctx))})) ? erase_fn(const_cast(data), key.base().data()) : 0u; +} + +/** + * @brief Returns an iterator to the element with a given key, if any. + * @param key The key of the element to search. + * @return An iterator to the element with the given key, if any. + */ +[[nodiscard]] inline meta_associative_container::iterator meta_associative_container::find(meta_any key) { + return key.allow_cast(meta_type{*ctx, key_type_node(internal::meta_context::from(*ctx))}) ? find_fn(*ctx, const_only ? nullptr : const_cast(data), data, key.base().data()) : iterator{}; +} + +/** + * @brief Returns false if a proxy is invalid, true otherwise. + * @return False if the proxy is invalid, true otherwise. + */ +[[nodiscard]] inline meta_associative_container::operator bool() const noexcept { + return (data != nullptr); +} + +} // namespace entt + +#endif diff --git a/include/entt/meta/node.hpp b/include/entt/meta/node.hpp new file mode 100644 index 0000000..8b35546 --- /dev/null +++ b/include/entt/meta/node.hpp @@ -0,0 +1,287 @@ +#ifndef ENTT_META_NODE_HPP +#define ENTT_META_NODE_HPP + +#include "../config/config.h" +#include "../core/bit.hpp" +#include "../core/concepts.hpp" +#include "../core/enum.hpp" +#include "../core/fwd.hpp" +#include "../core/type_info.hpp" +#include "../core/type_traits.hpp" +#include "../core/utility.hpp" +#include "../stl/array.hpp" +#include "../stl/bit.hpp" +#include "../stl/cstddef.hpp" +#include "../stl/cstdint.hpp" +#include "../stl/memory.hpp" +#include "../stl/type_traits.hpp" +#include "../stl/utility.hpp" +#include "../stl/vector.hpp" +#include "context.hpp" +#include "fwd.hpp" +#include "type_traits.hpp" + +namespace entt { + +/*! @cond ENTT_INTERNAL */ +namespace internal { + +enum class meta_traits : stl::uint32_t { + is_none = 0x0000, + is_const = 0x0001, + is_static = 0x0002, + is_arithmetic = 0x0004, + is_integral = 0x0008, + is_signed = 0x0010, + is_array = 0x0020, + is_enum = 0x0040, + is_class = 0x0080, + is_pointer = 0x0100, + is_pointer_like = 0x0200, + is_sequence_container = 0x0400, + is_associative_container = 0x0800, + _user_defined_traits = 0xFFFF, + _entt_enum_as_bitmask = 0xFFFF +}; + +template +requires stl::is_enum_v +[[nodiscard]] auto meta_to_user_traits(const meta_traits traits) noexcept { + constexpr auto shift = stl::popcount(static_cast>(meta_traits::_user_defined_traits)); + return Type{static_cast>(static_cast>(traits) >> shift)}; +} + +template +requires stl::is_enum_v +[[nodiscard]] auto user_to_meta_traits(const Type value) noexcept { + constexpr auto shift = stl::popcount(static_cast>(meta_traits::_user_defined_traits)); + const auto traits = static_cast>(static_cast>(value)); + ENTT_ASSERT(traits < ((~static_cast>(meta_traits::_user_defined_traits)) >> shift), "Invalid traits"); + return meta_traits{traits << shift}; +} + +struct meta_type_node; + +struct meta_custom_node { + id_type id{}; + stl::shared_ptr value{}; +}; + +struct meta_base_node { + id_type id{}; + const meta_type_node &(*type)(const meta_context &) noexcept {}; + const void *(*cast)(const void *) noexcept {}; +}; + +struct meta_conv_node { + id_type id{}; + meta_any (*conv)(const meta_ctx &, const void *){}; +}; + +struct meta_ctor_node { + using size_type = stl::size_t; + + id_type id{}; + size_type arity{0u}; + meta_type (*arg)(const meta_ctx &, const size_type) noexcept {}; + meta_any (*invoke)(const meta_ctx &, meta_any *const){}; +}; + +struct meta_data_node { + using size_type = stl::size_t; + + id_type id{}; + const char *name{}; + meta_traits traits{meta_traits::is_none}; + size_type set_arity{0u}; + size_type get_arity{0u}; + meta_type (*set_arg)(const meta_ctx &, const size_type) noexcept {}; + meta_type (*get_arg)(const meta_ctx &, const size_type) noexcept {}; + const meta_type_node &(*type)(const meta_context &) noexcept {}; + bool (*set)(meta_handle, meta_any *const){}; + meta_any (*get)(meta_handle, meta_any *const){}; + meta_custom_node custom{}; +}; + +struct meta_func_node { + using size_type = stl::size_t; + + id_type id{}; + const char *name{}; + meta_traits traits{meta_traits::is_none}; + size_type arity{0u}; + const meta_type_node &(*ret)(const meta_context &) noexcept {}; + meta_type (*arg)(const meta_ctx &, const size_type) noexcept {}; + meta_any (*invoke)(meta_handle, meta_any *const){}; + stl::unique_ptr next; + meta_custom_node custom{}; +}; + +struct meta_template_node { + using size_type = stl::size_t; + + size_type arity{0u}; + const meta_type_node &(*resolve)(const meta_context &) noexcept {}; + const meta_type_node &(*arg)(const meta_context &, const size_type) noexcept {}; +}; + +struct meta_type_descriptor { + stl::vector ctor{}; + stl::vector base{}; + stl::vector conv{}; + stl::vector data{}; + stl::vector func{}; +}; + +struct meta_type_node { + using size_type = stl::size_t; + + const type_info *info{}; + id_type alias{}; + const char *name{}; + meta_traits traits{meta_traits::is_none}; + size_type size_of{0u}; + const meta_type_node &(*remove_pointer)(const meta_context &) noexcept {}; + meta_any (*default_constructor)(const meta_ctx &){}; + double (*conversion_helper)(void *, const void *){}; + meta_any (*from_void)(const meta_ctx &, void *, const void *){}; + meta_template_node templ{}; + meta_custom_node custom{}; + stl::unique_ptr details{}; +}; + +template +[[nodiscard]] auto *find_member(Type &from, const Value value) { + for(auto &&elem: from) { + if(elem.id == value) { + return &elem; + } + } + + return static_cast(nullptr); +} + +[[nodiscard]] inline auto *find_overload(meta_func_node *curr, stl::remove_pointer_t *const ref) { + while((curr != nullptr) && (curr->invoke != ref)) { curr = curr->next.get(); } + return curr; +} + +template +[[nodiscard]] auto *look_for(const meta_context &context, const meta_type_node &node, const id_type id, bool recursive) { + using value_type = stl::remove_reference_t*Member))>::value_type; + + if(node.details) { + if(auto *member = find_member((node.details.get()->*Member), id); member != nullptr) { + return member; + } + + if(recursive) { + for(auto &&curr: node.details->base) { + if(auto *elem = look_for(context, curr.type(context), id, recursive); elem) { + return elem; + } + } + } + } + + return static_cast(nullptr); +} + +template +const meta_type_node &resolve(const meta_context &) noexcept; + +template +[[nodiscard]] const meta_type_node &meta_arg_node(const meta_context &context, type_list, const stl::size_t index) noexcept { + using resolve_type = const meta_type_node &(*)(const meta_context &) noexcept; + constexpr stl::array list{&resolve>...}; + ENTT_ASSERT(index < sizeof...(Args), "Out of bounds"); + return list[index](context); +} + +[[nodiscard]] inline const void *try_cast(const meta_context &context, const meta_type_node &from, const id_type to, const void *instance) noexcept { + if(from.details) { + for(auto &&curr: from.details->base) { + if(const void *other = curr.cast(instance); curr.id == to) { + return other; + } else if(const void *elem = try_cast(context, curr.type(context), to, other); elem) { + return elem; + } + } + } + + return nullptr; +} + +template +auto setup_node_for() noexcept { + meta_type_node node{ + &type_id(), + type_id().hash(), + nullptr, + (stl::is_arithmetic_v ? meta_traits::is_arithmetic : meta_traits::is_none) + | (stl::is_integral_v ? meta_traits::is_integral : meta_traits::is_none) + | (stl::is_signed_v ? meta_traits::is_signed : meta_traits::is_none) + | (stl::is_array_v ? meta_traits::is_array : meta_traits::is_none) + | (stl::is_enum_v ? meta_traits::is_enum : meta_traits::is_none) + | (stl::is_class_v ? meta_traits::is_class : meta_traits::is_none) + | (stl::is_pointer_v ? meta_traits::is_pointer : meta_traits::is_none) + | (is_meta_pointer_like_v ? meta_traits::is_pointer_like : meta_traits::is_none) + | (is_complete_v> ? meta_traits::is_sequence_container : meta_traits::is_none) + | (is_complete_v> ? meta_traits::is_associative_container : meta_traits::is_none), + size_of_v, + &resolve>>}; + + if constexpr(stl::is_default_constructible_v) { + node.default_constructor = +[](const meta_ctx &ctx) { + return meta_any{ctx, stl::in_place_type}; + }; + } + + if constexpr(stl::is_arithmetic_v) { + node.conversion_helper = +[](void *lhs, const void *rhs) { + return lhs ? static_cast(*static_cast(lhs) = static_cast(*static_cast(rhs))) : static_cast(*static_cast(rhs)); + }; + } else if constexpr(stl::is_enum_v) { + node.conversion_helper = +[](void *lhs, const void *rhs) { + return lhs ? static_cast(*static_cast(lhs) = static_cast(static_cast>(*static_cast(rhs)))) : static_cast(*static_cast(rhs)); + }; + } + + if constexpr(!stl::is_void_v && !stl::is_function_v) { + node.from_void = +[](const meta_ctx &ctx, void *elem, const void *celem) { + if(elem && celem) { // ownership construction request + return meta_any{ctx, stl::in_place, static_cast *>(elem)}; + } + + if(elem) { // non-const reference construction request + return meta_any{ctx, stl::in_place_type &>, *static_cast *>(elem)}; + } + + // const reference construction request + return meta_any{ctx, stl::in_place_type &>, *static_cast *>(celem)}; + }; + } + + if constexpr(is_complete_v>) { + node.templ = meta_template_node{ + meta_template_traits::args_type::size, + &resolve::class_type>, + +[](const meta_context &area, const stl::size_t index) noexcept -> decltype(auto) { return meta_arg_node(area, typename meta_template_traits::args_type{}, index); }}; + } + + return node; +} + +template +[[nodiscard]] const meta_type_node &resolve(const meta_context &context) noexcept { + static const meta_type_node node = setup_node_for(); + const auto it = context.bucket.find(node.info->hash()); + return (it == context.bucket.cend()) ? node : *it->second; +} + +} // namespace internal +/*! @endcond */ + +} // namespace entt + +#endif diff --git a/include/entt/meta/pointer.hpp b/include/entt/meta/pointer.hpp new file mode 100644 index 0000000..e347756 --- /dev/null +++ b/include/entt/meta/pointer.hpp @@ -0,0 +1,42 @@ +// IWYU pragma: always_keep + +#ifndef ENTT_META_POINTER_HPP +#define ENTT_META_POINTER_HPP + +#include "../stl/memory.hpp" +#include "../stl/type_traits.hpp" +#include "type_traits.hpp" + +namespace entt { + +/** + * @brief Makes `stl::shared_ptr`s of any type pointer-like types for the meta + * system. + * @tparam Type Element type. + */ +template +struct is_meta_pointer_like> + : stl::true_type {}; + +/** + * @brief Makes `stl::unique_ptr`s of any type pointer-like types for the meta + * system. + * @tparam Type Element type. + * @tparam Args Other arguments. + */ +template +struct is_meta_pointer_like> + : stl::true_type {}; + +/** + * @brief Specialization for self-proclaimed meta pointer like types. + * @tparam Type Element type. + */ +template +requires requires { typename Type::is_meta_pointer_like; } +struct is_meta_pointer_like + : stl::true_type {}; + +} // namespace entt + +#endif diff --git a/include/entt/meta/policy.hpp b/include/entt/meta/policy.hpp new file mode 100644 index 0000000..be0e3d9 --- /dev/null +++ b/include/entt/meta/policy.hpp @@ -0,0 +1,81 @@ +#ifndef ENTT_META_POLICY_HPP +#define ENTT_META_POLICY_HPP + +#include "../stl/type_traits.hpp" + +namespace entt { + +/*! @cond ENTT_INTERNAL */ +namespace internal { + +struct meta_policy {}; + +} // namespace internal +/*! @endcond */ + +/*! @brief Empty class type used to request the _as-is_ policy. */ +struct as_value_t final: private internal::meta_policy { + /*! @cond ENTT_INTERNAL */ + template + static constexpr bool value = true; + /*! @endcond */ +}; + +/*! @brief Empty class type used to request the _as void_ policy. */ +struct as_void_t final: private internal::meta_policy { + /*! @cond ENTT_INTERNAL */ + template + static constexpr bool value = true; + /*! @endcond */ +}; + +/*! @brief Empty class type used to request the _as ref_ policy. */ +struct as_ref_t final: private internal::meta_policy { + /*! @cond ENTT_INTERNAL */ + template + static constexpr bool value = stl::is_reference_v && !stl::is_const_v>; + /*! @endcond */ +}; + +/*! @brief Empty class type used to request the _as cref_ policy. */ +struct as_cref_t final: private internal::meta_policy { + /*! @cond ENTT_INTERNAL */ + template + static constexpr bool value = stl::is_reference_v; + /*! @endcond */ +}; + +/*! @brief Empty class type used to request the _as auto_ policy. */ +struct as_is_t final: private internal::meta_policy { + /*! @cond ENTT_INTERNAL */ + template + static constexpr bool value = true; + /*! @endcond */ +}; + +/** + * @brief Provides the member constant `value` equal to true if a type also is a + * meta policy, false otherwise. + * @tparam Type Type to check. + */ +template +struct is_meta_policy + : stl::bool_constant> {}; + +/** + * @brief Helper variable template. + * @tparam Type Type to check. + */ +template +inline constexpr bool is_meta_policy_v = is_meta_policy::value; + +/** + * @brief Specifies whether a type is a meta policy. + * @tparam Type Type to check. + */ +template +concept meta_policy = is_meta_policy_v; + +} // namespace entt + +#endif diff --git a/include/entt/meta/range.hpp b/include/entt/meta/range.hpp new file mode 100644 index 0000000..2e47d79 --- /dev/null +++ b/include/entt/meta/range.hpp @@ -0,0 +1,119 @@ +#ifndef ENTT_META_RANGE_HPP +#define ENTT_META_RANGE_HPP + +#include +#include "../core/fwd.hpp" +#include "../core/iterator.hpp" +#include "../stl/concepts.hpp" +#include "../stl/cstddef.hpp" +#include "../stl/iterator.hpp" +#include "../stl/utility.hpp" +#include "context.hpp" + +namespace entt { + +/*! @cond ENTT_INTERNAL */ +namespace internal { + +struct meta_base_node; + +template +struct meta_range_iterator final { + using value_type = stl::pair; + using pointer = input_iterator_pointer; + 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 meta_range_iterator() noexcept + : it{}, + ctx{} {} + + constexpr meta_range_iterator(const meta_ctx &area, const It iter) noexcept + : it{iter}, + ctx{&area} {} + + constexpr meta_range_iterator &operator++() noexcept { + return ++it, *this; + } + + constexpr meta_range_iterator operator++(int) noexcept { + const meta_range_iterator orig = *this; + return ++(*this), orig; + } + + constexpr meta_range_iterator &operator--() noexcept { + return --it, *this; + } + + constexpr meta_range_iterator operator--(int) noexcept { + const meta_range_iterator orig = *this; + return operator--(), orig; + } + + constexpr meta_range_iterator &operator+=(const difference_type value) noexcept { + it += value; + return *this; + } + + constexpr meta_range_iterator operator+(const difference_type value) const noexcept { + meta_range_iterator copy = *this; + return (copy += value); + } + + constexpr meta_range_iterator &operator-=(const difference_type value) noexcept { + return (*this += -value); + } + + constexpr meta_range_iterator operator-(const difference_type value) const noexcept { + return (*this + -value); + } + + [[nodiscard]] constexpr reference operator[](const difference_type value) const noexcept { + if constexpr(stl::is_same_v) { + return {it[value].first, Type{*ctx, *it[value].second}}; + } else { + return {it[value].id, Type{*ctx, it[value]}}; + } + } + + [[nodiscard]] constexpr pointer operator->() const noexcept { + return operator*(); + } + + [[nodiscard]] constexpr reference operator*() const noexcept { + return operator[](0); + } + + [[nodiscard]] constexpr stl::ptrdiff_t operator-(const meta_range_iterator &other) const noexcept { + return it - other.it; + } + + [[nodiscard]] constexpr bool operator==(const meta_range_iterator &other) const noexcept { + return it == other.it; + } + + [[nodiscard]] constexpr auto operator<=>(const meta_range_iterator &other) const noexcept { + return it <=> other.it; + } + +private: + It it; + const meta_ctx *ctx; +}; + +} // namespace internal +/*! @endcond */ + +/** + * @brief Iterable range to use to iterate all types of meta objects. + * @tparam Type Type of meta objects returned. + * @tparam It Type of forward iterator. + */ +template +using meta_range = iterable_adaptor>; + +} // namespace entt + +#endif diff --git a/include/entt/meta/resolve.hpp b/include/entt/meta/resolve.hpp new file mode 100644 index 0000000..7f3eb97 --- /dev/null +++ b/include/entt/meta/resolve.hpp @@ -0,0 +1,109 @@ +#ifndef ENTT_META_RESOLVE_HPP +#define ENTT_META_RESOLVE_HPP + +#include "../core/type_info.hpp" +#include "../locator/locator.hpp" +#include "../stl/type_traits.hpp" +#include "context.hpp" +#include "meta.hpp" +#include "node.hpp" +#include "range.hpp" + +namespace entt { + +/** + * @brief Returns the meta type associated with a given type. + * @tparam Type Type to use to search for a meta type. + * @param ctx The context from which to search for meta types. + * @return The meta type associated with the given type, if any. + */ +template +[[nodiscard]] meta_type resolve(const meta_ctx &ctx) noexcept { + const auto &context = internal::meta_context::from(ctx); + return {ctx, internal::resolve>(context)}; +} + +/** + * @brief Returns the meta type associated with a given type. + * @tparam Type Type to use to search for a meta type. + * @return The meta type associated with the given type, if any. + */ +template +[[nodiscard]] meta_type resolve() noexcept { + return resolve(locator::value_or()); +} + +/** + * @brief Returns a range to use to visit all meta types. + * @param ctx The context from which to search for meta types. + * @return An iterable range to use to visit all meta types. + */ +[[nodiscard]] inline meta_range resolve(const meta_ctx &ctx) noexcept { + const auto &context = internal::meta_context::from(ctx); + return {{ctx, context.bucket.cbegin()}, {ctx, context.bucket.cend()}}; +} + +/** + * @brief Returns a range to use to visit all meta types. + * @return An iterable range to use to visit all meta types. + */ +[[nodiscard]] inline meta_range resolve() noexcept { + return resolve(locator::value_or()); +} + +/** + * @brief Returns the meta type associated with a given identifier, if any. + * @param ctx The context from which to search for meta types. + * @param alias Unique identifier. + * @return The meta type associated with the given identifier, if any. + */ +[[nodiscard]] inline meta_type resolve(const meta_ctx &ctx, const id_type alias) noexcept { + const auto &context = internal::meta_context::from(ctx); + + // fast lookup for unsearchable and overloaded types + if(const auto it = context.bucket.find(alias); it != context.bucket.end()) { + return meta_type{ctx, *it->second}; + } + + for(auto &&curr: context.bucket) { + if(curr.second->alias == alias) { + return meta_type{ctx, *curr.second}; + } + } + + return meta_type{}; +} + +/** + * @brief Returns the meta type associated with a given identifier, if any. + * @param alias Unique identifier. + * @return The meta type associated with the given identifier, if any. + */ +[[nodiscard]] inline meta_type resolve(const id_type alias) noexcept { + return resolve(locator::value_or(), alias); +} + +/** + * @brief Returns the meta type associated with a given type info object. + * @param ctx The context from which to search for meta types. + * @param info The type info object of the requested type. + * @return The meta type associated with the given type info object, if any. + */ +[[nodiscard]] inline meta_type resolve(const meta_ctx &ctx, const type_info &info) noexcept { + const auto &context = internal::meta_context::from(ctx); + const auto it = context.bucket.find(info.hash()); + return (it == context.bucket.cend()) ? meta_type{} : meta_type{ctx, *it->second}; +} + +/** + * @brief Returns the meta type associated with a given type info object. + * @param info The type info object of the requested type. + * @return The meta type associated with the given type info object, if any. + */ +[[nodiscard]] inline meta_type resolve(const type_info &info) noexcept { + return resolve(locator::value_or(), info); +} + +} // namespace entt + +#endif diff --git a/include/entt/meta/template.hpp b/include/entt/meta/template.hpp new file mode 100644 index 0000000..5eab2af --- /dev/null +++ b/include/entt/meta/template.hpp @@ -0,0 +1,29 @@ +// IWYU pragma: always_keep + +#ifndef ENTT_META_TEMPLATE_HPP +#define ENTT_META_TEMPLATE_HPP + +#include "../core/type_traits.hpp" + +namespace entt { + +/*! @brief Utility class to disambiguate class templates. */ +template class> +struct meta_class_template_tag {}; + +/** + * @brief General purpose traits class for generating meta template information. + * @tparam Clazz Type of class template. + * @tparam Args Types of template arguments. + */ +template class Clazz, typename... Args> +struct meta_template_traits> { + /*! @brief Wrapped class template. */ + using class_type = meta_class_template_tag; + /*! @brief List of template arguments. */ + using args_type = type_list; +}; + +} // namespace entt + +#endif diff --git a/include/entt/meta/type_traits.hpp b/include/entt/meta/type_traits.hpp new file mode 100644 index 0000000..cc271fc --- /dev/null +++ b/include/entt/meta/type_traits.hpp @@ -0,0 +1,54 @@ +#ifndef ENTT_META_TYPE_TRAITS_HPP +#define ENTT_META_TYPE_TRAITS_HPP + +#include "../stl/type_traits.hpp" +#include "../stl/utility.hpp" + +namespace entt { + +/** + * @brief Traits class template to be specialized to enable support for meta + * template information. + */ +template +struct meta_template_traits; + +/** + * @brief Traits class template to be specialized to enable support for meta + * sequence containers. + */ +template +struct meta_sequence_container_traits; + +/** + * @brief Traits class template to be specialized to enable support for meta + * associative containers. + */ +template +struct meta_associative_container_traits; + +/** + * @brief Provides the member constant `value` equal to true if a given type is + * a pointer-like type, false otherwise. + */ +template +struct is_meta_pointer_like: stl::false_type {}; + +/** + * @brief Partial specialization to ensure that const pointer-like types are + * also accepted. + * @tparam Type Potentially pointer-like type. + */ +template +struct is_meta_pointer_like: is_meta_pointer_like {}; + +/** + * @brief Helper variable template. + * @tparam Type Potentially pointer-like type. + */ +template +inline constexpr auto is_meta_pointer_like_v = is_meta_pointer_like::value; + +} // namespace entt + +#endif diff --git a/include/entt/meta/utility.hpp b/include/entt/meta/utility.hpp new file mode 100644 index 0000000..ea5b01e --- /dev/null +++ b/include/entt/meta/utility.hpp @@ -0,0 +1,500 @@ +#ifndef ENTT_META_UTILITY_HPP +#define ENTT_META_UTILITY_HPP + +#include "../core/type_traits.hpp" +#include "../locator/locator.hpp" +#include "../stl/cstddef.hpp" +#include "../stl/functional.hpp" +#include "../stl/type_traits.hpp" +#include "../stl/utility.hpp" +#include "meta.hpp" +#include "node.hpp" +#include "policy.hpp" + +namespace entt { + +/** + * @brief Meta function descriptor traits. + * @tparam Ret Function return type. + * @tparam Args Function arguments. + * @tparam Static Function staticness. + * @tparam Const Function constness. + */ +template +struct meta_function_descriptor_traits { + /*! @brief Meta function return type. */ + using return_type = Ret; + /*! @brief Meta function arguments. */ + using args_type = Args; + + /*! @brief True if the meta function is static, false otherwise. */ + static constexpr bool is_static = Static; + /*! @brief True if the meta function is const, false otherwise. */ + static constexpr bool is_const = Const; +}; + +/*! @brief Primary template isn't defined on purpose. */ +template +struct meta_function_descriptor; + +/** + * @brief Meta function descriptor. + * @tparam Type Reflected type to which the meta function is associated. + * @tparam Ret Function return type. + * @tparam Class Actual owner of the member function. + * @tparam Args Function arguments. + */ +template +struct meta_function_descriptor + : meta_function_descriptor_traits< + Ret, + stl::conditional_t, type_list, type_list>, + !stl::is_base_of_v, + true> {}; + +/** + * @brief Meta function descriptor. + * @tparam Type Reflected type to which the meta function is associated. + * @tparam Ret Function return type. + * @tparam Class Actual owner of the member function. + * @tparam Args Function arguments. + */ +template +struct meta_function_descriptor + : meta_function_descriptor_traits< + Ret, + stl::conditional_t, type_list, type_list>, + !stl::is_base_of_v, + false> {}; + +/** + * @brief Meta function descriptor. + * @tparam Type Reflected type to which the meta data is associated. + * @tparam Class Actual owner of the data member. + * @tparam Ret Data member type. + */ +template +struct meta_function_descriptor + : meta_function_descriptor_traits< + Ret &, + stl::conditional_t, type_list<>, type_list>, + !stl::is_base_of_v, + false> {}; + +/** + * @brief Meta function descriptor. + * @tparam Type Reflected type to which the meta function is associated. + * @tparam Ret Function return type. + * @tparam MaybeType First function argument. + * @tparam Args Other function arguments. + */ +template +struct meta_function_descriptor + : meta_function_descriptor_traits< + Ret, + stl::conditional_t< + stl::is_same_v, Type> || stl::is_base_of_v, Type>, + type_list, + type_list>, + !(stl::is_same_v, Type> || stl::is_base_of_v, Type>), + stl::is_const_v> && (stl::is_same_v, Type> || stl::is_base_of_v, Type>)> {}; + +/** + * @brief Meta function descriptor. + * @tparam Type Reflected type to which the meta function is associated. + * @tparam Ret Function return type. + */ +template +struct meta_function_descriptor + : meta_function_descriptor_traits< + Ret, + type_list<>, + true, + false> {}; + +/** + * @brief Meta function helper. + * + * Converts a function type to be associated with a reflected type into its meta + * function descriptor. + * + * @tparam Type Reflected type to which the meta function is associated. + * @tparam Candidate The actual function to associate with the reflected type. + */ +template +class meta_function_helper { + template + static meta_function_descriptor get_rid_of_noexcept(Ret (Class::*)(Args...) const); + + template + static meta_function_descriptor get_rid_of_noexcept(Ret (Class::*)(Args...)); + + template + requires stl::is_member_object_pointer_v + static meta_function_descriptor get_rid_of_noexcept(Ret Class::*); + + template + static meta_function_descriptor get_rid_of_noexcept(Ret (*)(Args...)); + + template + static meta_function_descriptor get_rid_of_noexcept(Class); + +public: + /*! @brief The meta function descriptor of the given function. */ + using type = decltype(get_rid_of_noexcept(stl::declval())); +}; + +/** + * @brief Helper type. + * @tparam Type Reflected type to which the meta function is associated. + * @tparam Candidate The actual function to associate with the reflected type. + */ +template +using meta_function_helper_t = meta_function_helper::type; + +/** + * @brief Wraps a value depending on the given policy. + * + * This function always returns a wrapped value in the requested context.
+ * Therefore, if the passed value is itself a wrapped object with a different + * context, it undergoes a rebinding to the requested context. + * + * @tparam Policy Optional policy (no policy set by default). + * @tparam Type Type of value to wrap. + * @param ctx The context from which to search for meta types. + * @param value Value to wrap. + * @return A meta any containing the returned value, if any. + */ +template +[[nodiscard]] meta_any meta_dispatch(const meta_ctx &ctx, [[maybe_unused]] Type &&value) { + if constexpr(stl::is_same_v) { + static_assert(stl::is_lvalue_reference_v, "Invalid type"); + return meta_any{ctx, stl::in_place_type &>, stl::as_const(value)}; + } else if constexpr(stl::is_same_v || (stl::is_same_v && stl::is_lvalue_reference_v)) { + return meta_any{ctx, stl::in_place_type, value}; + } else if constexpr(stl::is_same_v) { + return meta_any{ctx, stl::in_place_type}; + } else { + return meta_any{ctx, stl::forward(value)}; + } +} + +/** + * @brief Wraps a value depending on the given policy. + * @tparam Policy Optional policy (no policy set by default). + * @tparam Type Type of value to wrap. + * @param value Value to wrap. + * @return A meta any containing the returned value, if any. + */ +template +[[nodiscard]] meta_any meta_dispatch(Type &&value) { + return meta_dispatch(locator::value_or(), stl::forward(value)); +} + +/*! @cond ENTT_INTERNAL */ +namespace internal { + +template +[[nodiscard]] meta_any meta_invoke_with_args(const meta_ctx &ctx, Candidate &&candidate, Args &&...args) { + if constexpr(stl::is_void_v(candidate), args...))>) { + stl::invoke(stl::forward(candidate), args...); + return meta_any{ctx, stl::in_place_type}; + } else { + return meta_dispatch(ctx, stl::invoke(stl::forward(candidate), args...)); + } +} + +template +[[nodiscard]] meta_any meta_invoke(meta_any &instance, Candidate &&candidate, [[maybe_unused]] meta_any *const args, stl::index_sequence) { + using descriptor = meta_function_helper_t>; + + // NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic) - waiting for C++20 (and stl::span) + if constexpr(stl::is_invocable_v, const Type &, type_list_element_t...>) { + if(const auto *const clazz = instance.try_cast(); clazz && ((args + Index)->allow_cast>() && ...)) { + return meta_invoke_with_args(instance.context(), stl::forward(candidate), *clazz, (args + Index)->cast>()...); + } + } else if constexpr(stl::is_invocable_v, Type &, type_list_element_t...>) { + if(auto *const clazz = instance.try_cast(); clazz && ((args + Index)->allow_cast>() && ...)) { + return meta_invoke_with_args(instance.context(), stl::forward(candidate), *clazz, (args + Index)->cast>()...); + } + } else { + if(((args + Index)->allow_cast>() && ...)) { + return meta_invoke_with_args(instance.context(), stl::forward(candidate), (args + Index)->cast>()...); + } + } + // NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic) + + return meta_any{meta_ctx_arg, instance.context()}; +} + +template +[[nodiscard]] meta_any meta_construct(const meta_ctx &ctx, meta_any *const args, stl::index_sequence) { + // NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic) - waiting for C++20 (and stl::span) + if(((args + Index)->allow_cast() && ...)) { + return meta_any{ctx, stl::in_place_type, (args + Index)->cast()...}; + } + // NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic) + + return meta_any{meta_ctx_arg, ctx}; +} + +} // namespace internal +/*! @endcond */ + +/** + * @brief Returns the meta type of the i-th element of a list of arguments. + * @tparam Type Type list of the actual types of arguments. + * @param ctx The context from which to search for meta types. + * @param index The index of the element for which to return the meta type. + * @return The meta type of the i-th element of the list of arguments. + */ +template +[[nodiscard]] meta_type meta_arg(const meta_ctx &ctx, const stl::size_t index) noexcept { + const auto &context = internal::meta_context::from(ctx); + return {ctx, internal::meta_arg_node(context, Type{}, index)}; +} + +/** + * @brief Returns the meta type of the i-th element of a list of arguments. + * @tparam Type Type list of the actual types of arguments. + * @param index The index of the element for which to return the meta type. + * @return The meta type of the i-th element of the list of arguments. + */ +template +[[nodiscard]] meta_type meta_arg(const stl::size_t index) noexcept { + return meta_arg(locator::value_or(), index); +} + +/** + * @brief Sets the value of a given variable. + * @tparam Type Reflected type to which the variable is associated. + * @tparam Data The actual variable to set. + * @param instance An opaque instance of the underlying type, if required. + * @param args Parameters to use to set the variable. + * @return True in case of success, false otherwise. + */ +template +[[nodiscard]] bool meta_setter([[maybe_unused]] meta_handle instance, [[maybe_unused]] meta_any *const args) { + if constexpr(stl::is_member_function_pointer_v || stl::is_function_v>>) { + return static_cast(internal::meta_invoke(*instance.operator->(), Data, args, stl::make_index_sequence::args_type::size>{})); + } else if constexpr(stl::is_member_object_pointer_v) { + using data_type = stl::remove_reference_t::return_type>; + + if constexpr(!stl::is_array_v && !stl::is_const_v) { + if(auto *const clazz = instance->try_cast(); clazz && args->allow_cast()) { + stl::invoke(Data, *clazz) = args->cast(); + return true; + } + } + + return false; + } else if constexpr(stl::is_pointer_v) { + using data_type = stl::remove_reference_t; + + if constexpr(!stl::is_array_v && !stl::is_const_v) { + if(args->allow_cast()) { + *Data = args->cast(); + return true; + } + } + + return false; + } else { + return false; + } +} + +/** + * @brief Sets the value of a given variable. + * @tparam Type Reflected type to which the variable is associated. + * @tparam Data The actual variable to set. + * @param instance An opaque instance of the underlying type, if required. + * @param value Parameter to use to set the variable. + * @return True in case of success, false otherwise. + */ +template +[[nodiscard]] bool meta_setter(meta_handle instance, meta_any value) { + return meta_setter(*instance.operator->(), &value); +} + +/** + * @brief Gets the value of a given variable. + * @tparam Type Reflected type to which the variable is associated. + * @tparam Data The actual variable to get. + * @tparam Policy Optional policy (no policy set by default). + * @param instance An opaque instance of the underlying type, if required. + * @param args Parameters to use to set the variable. + * @return A meta any containing the value of the underlying variable. + */ +template +[[nodiscard]] meta_any meta_getter(meta_handle instance, [[maybe_unused]] meta_any *const args) { + if constexpr(stl::is_member_function_pointer_v || stl::is_function_v>>) { + return internal::meta_invoke(*instance.operator->(), Data, args, stl::make_index_sequence::args_type::size>{}); + } else if constexpr(stl::is_member_object_pointer_v) { + if constexpr(!stl::is_array_v>>) { + if(auto *clazz = instance->try_cast(); clazz) { + return meta_dispatch(instance->context(), stl::invoke(Data, *clazz)); + } else if(auto *fallback = instance->try_cast(); fallback) { + return meta_dispatch(instance->context(), stl::invoke(Data, *fallback)); + } + } + + return meta_any{meta_ctx_arg, instance->context()}; + } else if constexpr(stl::is_pointer_v) { + if constexpr(stl::is_array_v>) { + return meta_any{meta_ctx_arg, instance->context()}; + } else { + return meta_dispatch(instance->context(), *Data); + } + } else { + return meta_dispatch(instance->context(), Data); + } +} + +/** + * @brief Gets the value of a given variable. + * @tparam Type Reflected type to which the variable is associated. + * @tparam Data The actual variable to get. + * @tparam Policy Optional policy (no policy set by default). + * @param instance An opaque instance of the underlying type, if required. + * @return A meta any containing the value of the underlying variable. + */ +template +[[nodiscard]] meta_any meta_getter(meta_handle instance) { + return meta_getter(*instance.operator->(), nullptr); +} + +/** + * @brief Tries to _invoke_ an object given a list of erased parameters. + * @tparam Type Reflected type to which the object to _invoke_ is associated. + * @tparam Policy Optional policy (no policy set by default). + * @tparam Candidate The type of the actual object to _invoke_. + * @param instance An opaque instance of the underlying type, if required. + * @param candidate The actual object to _invoke_. + * @param args Parameters to use to _invoke_ the object. + * @return A meta any containing the returned value, if any. + */ +template +[[nodiscard]] meta_any meta_invoke(meta_handle instance, Candidate &&candidate, meta_any *const args) { + return internal::meta_invoke(*instance.operator->(), stl::forward(candidate), args, stl::make_index_sequence>::args_type::size>{}); +} + +/** + * @brief Tries to invoke a function given a list of erased parameters. + * @tparam Type Reflected type to which the function is associated. + * @tparam Candidate The actual function to invoke. + * @tparam Policy Optional policy (no policy set by default). + * @param instance An opaque instance of the underlying type, if required. + * @param args Parameters to use to invoke the function. + * @return A meta any containing the returned value, if any. + */ +template +[[nodiscard]] meta_any meta_invoke(meta_handle instance, meta_any *const args) { + return internal::meta_invoke(*instance.operator->(), Candidate, args, stl::make_index_sequence>::args_type::size>{}); +} + +/** + * @brief Tries to construct an instance given a list of erased parameters. + * + * @warning + * The context provided is used only for the return type.
+ * It's up to the caller to bind the arguments to the right context(s). + * + * @tparam Type Actual type of the instance to construct. + * @tparam Args Types of arguments expected. + * @param ctx The context from which to search for meta types. + * @param args Parameters to use to construct the instance. + * @return A meta any containing the new instance, if any. + */ +template +[[nodiscard]] meta_any meta_construct(const meta_ctx &ctx, meta_any *const args) { + return internal::meta_construct(ctx, args, stl::index_sequence_for{}); +} + +/** + * @brief Tries to construct an instance given a list of erased parameters. + * @tparam Type Actual type of the instance to construct. + * @tparam Args Types of arguments expected. + * @param args Parameters to use to construct the instance. + * @return A meta any containing the new instance, if any. + */ +template +[[nodiscard]] meta_any meta_construct(meta_any *const args) { + return meta_construct(locator::value_or(), args); +} + +/** + * @brief Tries to construct an instance given a list of erased parameters. + * + * @warning + * The context provided is used only for the return type.
+ * It's up to the caller to bind the arguments to the right context(s). + * + * @tparam Type Reflected type to which the object to _invoke_ is associated. + * @tparam Policy Optional policy (no policy set by default). + * @tparam Candidate The type of the actual object to _invoke_. + * @param ctx The context from which to search for meta types. + * @param candidate The actual object to _invoke_. + * @param args Parameters to use to _invoke_ the object. + * @return A meta any containing the returned value, if any. + */ +template +[[nodiscard]] meta_any meta_construct(const meta_ctx &ctx, Candidate &&candidate, meta_any *const args) { + if constexpr(meta_function_helper_t::is_static || stl::is_class_v>) { + meta_any placeholder{meta_ctx_arg, ctx}; + return internal::meta_invoke(placeholder, stl::forward(candidate), args, stl::make_index_sequence>::args_type::size>{}); + } else { + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) - waiting for C++20 (and stl::span) + return internal::meta_invoke(*args, stl::forward(candidate), args + 1u, stl::make_index_sequence>::args_type::size>{}); + } +} + +/** + * @brief Tries to construct an instance given a list of erased parameters. + * @tparam Type Reflected type to which the object to _invoke_ is associated. + * @tparam Policy Optional policy (no policy set by default). + * @tparam Candidate The type of the actual object to _invoke_. + * @param candidate The actual object to _invoke_. + * @param args Parameters to use to _invoke_ the object. + * @return A meta any containing the returned value, if any. + */ +template +[[nodiscard]] meta_any meta_construct(Candidate &&candidate, meta_any *const args) { + return meta_construct(locator::value_or(), stl::forward(candidate), args); +} + +/** + * @brief Tries to construct an instance given a list of erased parameters. + * + * @warning + * The context provided is used only for the return type.
+ * It's up to the caller to bind the arguments to the right context(s). + * + * @tparam Type Reflected type to which the function is associated. + * @tparam Candidate The actual function to invoke. + * @tparam Policy Optional policy (no policy set by default). + * @param ctx The context from which to search for meta types. + * @param args Parameters to use to invoke the function. + * @return A meta any containing the returned value, if any. + */ +template +[[nodiscard]] meta_any meta_construct(const meta_ctx &ctx, meta_any *const args) { + return meta_construct(ctx, Candidate, args); +} + +/** + * @brief Tries to construct an instance given a list of erased parameters. + * @tparam Type Reflected type to which the function is associated. + * @tparam Candidate The actual function to invoke. + * @tparam Policy Optional policy (no policy set by default). + * @param args Parameters to use to invoke the function. + * @return A meta any containing the returned value, if any. + */ +template +[[nodiscard]] meta_any meta_construct(meta_any *const args) { + return meta_construct(locator::value_or(), args); +} + +} // namespace entt + +#endif diff --git a/include/entt/natvis/config.natvis b/include/entt/natvis/config.natvis new file mode 100644 index 0000000..6eb47e3 --- /dev/null +++ b/include/entt/natvis/config.natvis @@ -0,0 +1,3 @@ + + + diff --git a/include/entt/natvis/container.natvis b/include/entt/natvis/container.natvis new file mode 100644 index 0000000..5bd4ac6 --- /dev/null +++ b/include/entt/natvis/container.natvis @@ -0,0 +1,39 @@ + + + + + + {{ size={ size() } }} + + packed.first_base::value.capacity() + bucket_count() + (float)size() / (float)bucket_count() + threshold + + size() + packed.first_base::value[$i].element + + + + + + + {{ size={ size() } }} + + packed.first_base::value.capacity() + bucket_count() + (float)size() / (float)bucket_count() + threshold + + size() + packed.first_base::value[$i].second + + + + + { payload } + + payload + + + diff --git a/include/entt/natvis/core.natvis b/include/entt/natvis/core.natvis new file mode 100644 index 0000000..1e9dd19 --- /dev/null +++ b/include/entt/natvis/core.natvis @@ -0,0 +1,32 @@ + + + + {{ policy={ mode,en } }} + + + + + + + ({ first() }, { second() }) + + first() + second() + + + + {{ hash={ base_type::hash } }} + {{}} + + base_type::repr,na + base_type::length + + + + {{ name={ alias,na } }} + + identifier + seq + + + diff --git a/include/entt/natvis/entity.natvis b/include/entt/natvis/entity.natvis new file mode 100644 index 0000000..f8fe42c --- /dev/null +++ b/include/entt/natvis/entity.natvis @@ -0,0 +1,181 @@ + + + + {{ pools={ pools.size() } }} + + entities + + { pools.size() } + + + + + + + + *pools.packed.first_base::value[pos].element.second,view(simple) + + ++pos + + + + + groups.size() + + { vars.ctx.size() } + + + + + + + + vars.ctx.packed.first_base::value[pos].element.second + + ++pos + + + + + + + + + + + + + + + {{ size={ packed.size() }, type={ descriptor->alias,na } }} + + packed.capacity() + mode,en + head + + { sparse.size() * traits_type::page_size } + + + + + + + + + page = pos / traits_type::page_size + offset = pos & (traits_type::page_size - 1) + + *((traits_type::entity_type *)&sparse[page][offset]) & traits_type::entity_mask + + ++pos + + + + + + { packed.size() } + + + + + + + + packed[pos] + + ++pos + + + + + + + + + + + {{ size={ base_type::packed.size() }, type={ base_type::descriptor->alias,na } }} + + payload.capacity() * traits_type::page_size + traits_type::page_size + placeholder + (base_type*)this,nand + (base_type*)this,view(simple)nand + + + + + + + payload[pos / traits_type::page_size][pos & (traits_type::page_size - 1)] + + ++pos + + + + + + {{ size_hint={ pools[index]->packed.size() } }} + {{ size_hint=0 }} + + pools,na + filter,na + pools[index],na + + + + {{ size={ leading->packed.size() } }} + {{ size=0 }} + + leading,na + + + + { *(base_type*)this } + + *(base_type*)this + + + + {{ size_hint={ pools[0]->packed.size() } }} + {{ size_hint=0 }} + + pools,na + filter,na + + + + + + + {{ entity={ entt } }} + + entt + owner,na + + + + + + + + + + + + pool_at(pos),view(simple)nanr + + ++pos + + + + + + + + <null> + + + <tombstone> + + diff --git a/include/entt/natvis/graph.natvis b/include/entt/natvis/graph.natvis new file mode 100644 index 0000000..d585ae2 --- /dev/null +++ b/include/entt/natvis/graph.natvis @@ -0,0 +1,19 @@ + + + + {{ size={ vert } }} + + + + + + + + pos % vert + + ++pos + + + + + diff --git a/include/entt/natvis/locator.natvis b/include/entt/natvis/locator.natvis new file mode 100644 index 0000000..6eb47e3 --- /dev/null +++ b/include/entt/natvis/locator.natvis @@ -0,0 +1,3 @@ + + + diff --git a/include/entt/natvis/meta.natvis b/include/entt/natvis/meta.natvis new file mode 100644 index 0000000..739897e --- /dev/null +++ b/include/entt/natvis/meta.natvis @@ -0,0 +1,200 @@ + + + + {{ id={ id } }} + {{}} + + id + + + + {{ id={ id } }} + {{}} + + id + + + + {{ id={ id } }} + {{}} + + id + arity + + + + {{ id={ id } }} + {{}} + + id + value + + + + + + + {{ id={ name,na } }} + {{ id={ id } }} + {{}} + + id + name,na + arity + has_trait(entt::internal::meta_traits::is_const) + has_trait(entt::internal::meta_traits::is_static) + custom + + + + + + + {{ id={ name,na } }} + {{ id={ id } }} + {{}} + + id + name,na + arity + has_trait(entt::internal::meta_traits::is_const) + has_trait(entt::internal::meta_traits::is_static) + *next + custom + + + + {{ arity={ arity } }} + {{}} + + arity + + + + + + ctor,view(simple) + base,view(simple) + conv,view(simple) + data,view(simple) + func,view(simple) + + + + + + + {{ type={ name,na } }} + {{ type={ info->alias,na } }} + {{}} + + alias + name,na + size_of + has_trait(entt::internal::meta_traits::is_arithmetic) + has_trait(entt::internal::meta_traits::is_integral) + has_trait(entt::internal::meta_traits::is_signed) + has_trait(entt::internal::meta_traits::is_array) + has_trait(entt::internal::meta_traits::is_enum) + has_trait(entt::internal::meta_traits::is_class) + has_trait(entt::internal::meta_traits::is_pointer) + has_trait(entt::internal::meta_traits::is_pointer_like) + has_trait(entt::internal::meta_traits::is_sequence_container) + has_trait(entt::internal::meta_traits::is_associative_container) + default_constructor != nullptr + conversion_helper != nullptr + from_void != nullptr + templ + custom + *details + + + + { storage } + {{}} + + node,na + ctx,na + + + + { any } + + any + + + + {{ const={ const_only } }} + {{}} + + ctx,na + const_only + data + + + + {{ const={ const_only } }} + {{}} + + ctx,na + const_only + data + + + + { node,na } + {{}} + + node + + + + { node,na } + {{}} + + node + ctx,na + + + + { node,na } + {{}} + + node + ctx,na + + + + { node,na } + {{}} + + node + ctx,na + + + + { node,na } + {{}} + + node + ctx,na + + + + + + + { bucket } + + + + + + + element_at(pos).second + ++pos + + + + + diff --git a/include/entt/natvis/poly.natvis b/include/entt/natvis/poly.natvis new file mode 100644 index 0000000..8dfd72a --- /dev/null +++ b/include/entt/natvis/poly.natvis @@ -0,0 +1,6 @@ + + + + { storage } + + diff --git a/include/entt/natvis/process.natvis b/include/entt/natvis/process.natvis new file mode 100644 index 0000000..1f959ff --- /dev/null +++ b/include/entt/natvis/process.natvis @@ -0,0 +1,21 @@ + + + + {{ state={ current,en } }} + + current,en + *next.first_base::value + + + + + {{ size={ size() } }} + + handlers.first_base::value.capacity() + + size() + *handlers.first_base::value[$i] + + + + diff --git a/include/entt/natvis/resource.natvis b/include/entt/natvis/resource.natvis new file mode 100644 index 0000000..cecd3b9 --- /dev/null +++ b/include/entt/natvis/resource.natvis @@ -0,0 +1,26 @@ + + + + { value } + + value + + + + + {{ size={ size() } }} + + + + + + + + *pool.first_base::value.packed.first_base::value[pos].element.second + + ++pos + + + + + diff --git a/include/entt/natvis/signal.natvis b/include/entt/natvis/signal.natvis new file mode 100644 index 0000000..b486810 --- /dev/null +++ b/include/entt/natvis/signal.natvis @@ -0,0 +1,51 @@ + + + + {{ type={ "$T1" } }} + + fn == nullptr + instance + + + + + {{ size={ size() } }} + + + size() + *pools.first_base::value.packed.first_base::value[$i].element.second + + + + + {{ size={ events.size() }, event={ "$T1" } }} + + signal + events,view(simple) + + + + {{ size={ handlers.first_base::value.size() } }} + + + {{ bound={ signal != nullptr } }} + + + { conn } + + + {{ size={ calls.size() }, type={ "$T1" } }} + + + calls.size() + calls[$i] + + + + + {{ type={ "$T1" } }} + + signal,na + + + diff --git a/include/entt/poly/fwd.hpp b/include/entt/poly/fwd.hpp new file mode 100644 index 0000000..4d513e3 --- /dev/null +++ b/include/entt/poly/fwd.hpp @@ -0,0 +1,21 @@ +#ifndef ENTT_POLY_FWD_HPP +#define ENTT_POLY_FWD_HPP + +#include "../stl/cstddef.hpp" + +namespace entt { + +// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays, modernize-avoid-c-arrays) +template +class basic_poly; + +/** + * @brief Alias declaration for the most common use case. + * @tparam Concept Concept descriptor. + */ +template +using poly = basic_poly; + +} // namespace entt + +#endif diff --git a/include/entt/poly/poly.hpp b/include/entt/poly/poly.hpp new file mode 100644 index 0000000..a409c9a --- /dev/null +++ b/include/entt/poly/poly.hpp @@ -0,0 +1,316 @@ +#ifndef ENTT_POLY_POLY_HPP +#define ENTT_POLY_POLY_HPP + +#include "../core/any.hpp" +#include "../core/concepts.hpp" +#include "../core/type_info.hpp" +#include "../core/type_traits.hpp" +#include "../stl/concepts.hpp" +#include "../stl/cstddef.hpp" +#include "../stl/functional.hpp" +#include "../stl/tuple.hpp" +#include "../stl/type_traits.hpp" +#include "../stl/utility.hpp" +#include "fwd.hpp" + +namespace entt { + +/*! @brief Inspector class used to infer the type of the virtual table. */ +struct poly_inspector { + /** + * @brief Generic conversion operator (definition only). + * @tparam Type Type to which conversion is requested. + */ + template + operator Type &&() const; + + /** + * @brief Dummy invocation function (definition only). + * @tparam Member Index of the function to invoke. + * @tparam Args Types of arguments to pass to the function. + * @param args The arguments to pass to the function. + * @return A poly inspector convertible to any type. + */ + template + [[nodiscard]] poly_inspector invoke(Args &&...args) const; + + /*! @copydoc invoke */ + template + [[nodiscard]] poly_inspector invoke(Args &&...args); +}; + +/** + * @brief Static virtual table factory. + * @tparam Concept Concept descriptor. + * @tparam Len Size of the storage reserved for the small buffer optimization. + * @tparam Align Alignment requirement. + */ +template +class poly_vtable { + using inspector = Concept::template type; + + template + requires stl::derived_from> + static auto vtable_entry(Ret (*)(Clazz &, Args...)) + -> Ret (*)(constness_as_t, Clazz> &, Args...); + + template + static auto vtable_entry(Ret (*)(Args...)) + -> Ret (*)(const basic_any &, Args...); + + template + requires stl::derived_from + static auto vtable_entry(Ret (Clazz::*)(Args...)) + -> Ret (*)(basic_any &, Args...); + + template + requires stl::derived_from + static auto vtable_entry(Ret (Clazz::*)(Args...) const) + -> Ret (*)(const basic_any &, Args...); + + template + static auto make_vtable(value_list) noexcept + -> decltype(stl::make_tuple(vtable_entry(Candidate)...)); + + template + [[nodiscard]] static ENTT_CONSTEVAL auto make_vtable(type_list) noexcept { + if constexpr(sizeof...(Func) == 0u) { + return decltype(make_vtable(typename Concept::template impl{})){}; + } else if constexpr((stl::is_function_v && ...)) { + return decltype(stl::make_tuple(vtable_entry(stl::declval())...)){}; + } + } + + template + static void fill_vtable_entry(Ret (*&entry)(Any &, Args...)) noexcept { + if constexpr(stl::is_invocable_r_v) { + entry = +[](Any &, Args... args) -> Ret { + return stl::invoke(Candidate, stl::forward(args)...); + }; + } else { + entry = +[](Any &instance, Args... args) -> Ret { + return static_cast(stl::invoke(Candidate, any_cast &>(instance), stl::forward(args)...)); + }; + } + } + + template + [[nodiscard]] static auto fill_vtable(stl::index_sequence) noexcept { + vtable_type impl{}; + (fill_vtable_entry>>(stl::get(impl)), ...); + return impl; + } + + using vtable_type = decltype(make_vtable(Concept{})); + static constexpr bool is_mono = stl::tuple_size_v == 1u; + +public: + /*! @brief Virtual table type. */ + using type = stl::conditional_t, const vtable_type *>; + + /** + * @brief Returns a static virtual table for a specific concept and type. + * @tparam Type The type for which to generate the virtual table. + * @return A static virtual table for the given concept and type. + */ + template + [[nodiscard]] static type instance() noexcept { + static const vtable_type vtable = fill_vtable(stl::make_index_sequence::size>{}); + + if constexpr(is_mono) { + return stl::get<0>(vtable); + } else { + return &vtable; + } + } +}; + +/** + * @brief Poly base class used to inject functionalities into concepts. + * @tparam Poly The outermost poly class. + */ +template +struct poly_base { + /** + * @brief Invokes a function from the static virtual table. + * @tparam Member Index of the function to invoke. + * @tparam Args Types of arguments to pass to the function. + * @param self A reference to the poly object that made the call. + * @param args The arguments to pass to the function. + * @return The return value of the invoked function, if any. + */ + template + [[nodiscard]] decltype(auto) invoke(const poly_base &self, Args &&...args) const { + const auto &poly = static_cast(self); + + if constexpr(stl::is_function_v>) { + return poly.vtable(poly.storage, stl::forward(args)...); + } else { + return stl::get(*poly.vtable)(poly.storage, stl::forward(args)...); + } + } + + /*! @copydoc invoke */ + template + [[nodiscard]] decltype(auto) invoke(poly_base &self, Args &&...args) { + auto &poly = static_cast(self); + + if constexpr(stl::is_function_v>) { + static_assert(Member == 0u, "Unknown member"); + return poly.vtable(poly.storage, stl::forward(args)...); + } else { + return stl::get(*poly.vtable)(poly.storage, stl::forward(args)...); + } + } +}; + +/** + * @brief Shortcut for calling `poly_base::invoke`. + * @tparam Member Index of the function to invoke. + * @tparam Poly A fully defined poly object. + * @tparam Args Types of arguments to pass to the function. + * @param self A reference to the poly object that made the call. + * @param args The arguments to pass to the function. + * @return The return value of the invoked function, if any. + */ +template +decltype(auto) poly_call(Poly &&self, Args &&...args) { + return stl::forward(self).template invoke(self, stl::forward(args)...); +} + +/** + * @brief Static polymorphism made simple and within everyone's reach. + * + * Static polymorphism is a very powerful tool in C++, albeit sometimes + * cumbersome to obtain.
+ * This class aims to make it simple and easy to use. + * + * @note + * Both deduced and defined static virtual tables are supported.
+ * Moreover, the `poly` class template also works with unmanaged objects. + * + * @tparam Concept Concept descriptor. + * @tparam Len Size of the storage reserved for the small buffer optimization. + * @tparam Align Optional alignment requirement. + */ +template +class basic_poly: private Concept::template type>> { + friend struct poly_base; + +public: + /*! @brief Concept type. */ + using concept_type = Concept::template type>; + /*! @brief Virtual table type. */ + using vtable_type = poly_vtable::type; + + /*! @brief Default constructor. */ + basic_poly() noexcept = default; + + /** + * @brief Constructs a poly by directly initializing the new object. + * @tparam Type Type of object to use to initialize the poly. + * @tparam Args Types of arguments to use to construct the new instance. + * @param args Parameters to use to construct the instance. + */ + template + explicit basic_poly(stl::in_place_type_t, Args &&...args) + : storage{stl::in_place_type, stl::forward(args)...}, + vtable{poly_vtable::template instance>()} {} + + /** + * @brief Constructs a poly from a given value. + * @tparam Type Type of object to use to initialize the poly. + * @param value An instance of an object to use to initialize the poly. + */ + template + requires (!stl::same_as, basic_poly>) + basic_poly(Type &&value) noexcept + : basic_poly{stl::in_place_type>, stl::forward(value)} {} + + /** + * @brief Returns the object type info if any, `type_id()` otherwise. + * @return The object type info if any, `type_id()` otherwise. + */ + [[nodiscard]] const type_info &info() const noexcept { + return storage.info(); + } + + /** + * @brief Returns an opaque pointer to the contained instance. + * @return An opaque pointer the contained instance, if any. + */ + [[nodiscard]] const void *data() const noexcept { + return storage.data(); + } + + /*! @copydoc data */ + [[nodiscard]] void *data() noexcept { + return storage.data(); + } + + /** + * @brief Replaces the contained object by creating a new instance directly. + * @tparam Type Type of object to use to initialize the poly. + * @tparam Args Types of arguments to use to construct the new instance. + * @param args Parameters to use to construct the instance. + */ + template + void emplace(Args &&...args) { + storage.template emplace(stl::forward(args)...); + vtable = poly_vtable::template instance>(); + } + + /*! @brief Destroys contained object */ + void reset() { + storage.reset(); + vtable = {}; + } + + /** + * @brief Returns false if a poly is empty, true otherwise. + * @return False if the poly is empty, true otherwise. + */ + [[nodiscard]] explicit operator bool() const noexcept { + return static_cast(storage); + } + + /** + * @brief Returns a pointer to the underlying concept. + * @return A pointer to the underlying concept. + */ + [[nodiscard]] concept_type *operator->() noexcept { + return this; + } + + /*! @copydoc operator-> */ + [[nodiscard]] const concept_type *operator->() const noexcept { + return this; + } + + /** + * @brief Aliasing constructor. + * @return A poly that shares a reference to an unmanaged object. + */ + [[nodiscard]] basic_poly as_ref() noexcept { + basic_poly ref{}; + ref.storage = storage.as_ref(); + ref.vtable = vtable; + return ref; + } + + /*! @copydoc as_ref */ + [[nodiscard]] basic_poly as_ref() const noexcept { + basic_poly ref{}; + ref.storage = storage.as_ref(); + ref.vtable = vtable; + return ref; + } + +private: + basic_any storage{}; + vtable_type vtable{}; +}; + +} // namespace entt + +#endif diff --git a/include/entt/process/fwd.hpp b/include/entt/process/fwd.hpp new file mode 100644 index 0000000..77bc495 --- /dev/null +++ b/include/entt/process/fwd.hpp @@ -0,0 +1,23 @@ +#ifndef ENTT_PROCESS_FWD_HPP +#define ENTT_PROCESS_FWD_HPP + +#include "../stl/cstdint.hpp" +#include "../stl/memory.hpp" + +namespace entt { + +template> +class basic_process; + +/*! @brief Alias declaration for the most common use case. */ +using process = basic_process; + +template> +class basic_scheduler; + +/*! @brief Alias declaration for the most common use case. */ +using scheduler = basic_scheduler; + +} // namespace entt + +#endif diff --git a/include/entt/process/process.hpp b/include/entt/process/process.hpp new file mode 100644 index 0000000..8e5c950 --- /dev/null +++ b/include/entt/process/process.hpp @@ -0,0 +1,316 @@ +#ifndef ENTT_PROCESS_PROCESS_HPP +#define ENTT_PROCESS_PROCESS_HPP + +#include "../core/compressed_pair.hpp" +#include "../core/type_traits.hpp" +#include "../stl/cstdint.hpp" +#include "../stl/memory.hpp" +#include "../stl/type_traits.hpp" +#include "../stl/utility.hpp" +#include "fwd.hpp" + +namespace entt { + +/*! @cond ENTT_INTERNAL */ +namespace internal { + +template +struct process_adaptor; + +} // namespace internal +/*! @endcond */ + +/** + * @brief Base class for processes. + * + * Derived classes must specify what's the intended type for elapsed times.
+ * A process can implement the following member functions whether required: + * + * * @code{.cpp} + * void update(Delta, void *) override; + * @endcode + * + * It's invoked once per tick until a process is explicitly aborted or it + * terminates either with or without errors. Even though it's not mandatory to + * declare this member function, as a rule of thumb each process should at + * least define it to work properly. The `void *` parameter is an opaque + * pointer to user data (if any) forwarded directly to the process during an + * update. + * + * * @code{.cpp} + * void succeeded() override; + * @endcode + * + * It's invoked in case of success, immediately after an update and during the + * same tick. + * + * * @code{.cpp} + * void failed() override; + * @endcode + * + * It's invoked in case of errors, immediately after an update and during the + * same tick. + * + * * @code{.cpp} + * void aborted() override; + * @endcode + * + * It's invoked only if a process is explicitly aborted. There is no guarantee + * that it executes in the same tick, this depends solely on whether the + * process is aborted immediately or not. + * + * Derived classes can change the internal state of a process by invoking the + * `succeed` and `fail` member functions and even pause or unpause the process + * itself. + * + * @sa scheduler + * + * @tparam Delta Type to use to provide elapsed time. + * @tparam Allocator Type of allocator used to manage memory and elements. + */ +template +class basic_process: public stl::enable_shared_from_this> { + enum class state : stl::uint8_t { + idle = 0, + running, + paused, + succeeded, + failed, + aborted, + finished, + rejected + }; + + virtual void update(const Delta, void *) { + abort(); + } + + virtual void succeeded() {} + virtual void failed() {} + virtual void aborted() {} + +public: + /*! @brief Allocator type. */ + using allocator_type = Allocator; + /*! @brief Type used to provide elapsed time. */ + using delta_type = Delta; + /*! @brief Handle type. */ + using handle_type = stl::shared_ptr; + + /*! @brief Default constructor. */ + basic_process() + : basic_process{allocator_type{}} {} + + /** + * @brief Constructs a scheduler with a given allocator. + * @param allocator The allocator to use. + */ + explicit basic_process(const allocator_type &allocator) + : next{nullptr, allocator}, + current{state::idle} {} + + /*! @brief Default copy constructor, deleted on purpose. */ + basic_process(const basic_process &) = delete; + + /*! @brief Default move constructor, deleted on purpose. */ + basic_process(basic_process &&) = delete; + + /*! @brief Default destructor. */ + virtual ~basic_process() = default; + + /** + * @brief Default copy assignment operator, deleted on purpose. + * @return This process scheduler. + */ + basic_process &operator=(const basic_process &) = delete; + + /** + * @brief Default move assignment operator, deleted on purpose. + * @return This process scheduler. + */ + basic_process &operator=(basic_process &&) = delete; + + /** + * @brief Returns the associated allocator. + * @return The associated allocator. + */ + [[nodiscard]] constexpr allocator_type get_allocator() const noexcept { + return next.second(); + } + + /*! @brief Aborts a process if it's still alive, otherwise does nothing. */ + void abort() { + if(alive()) { + current = state::aborted; + } + } + + /** + * @brief Terminates a process with success if it's still alive, otherwise + * does nothing. + */ + void succeed() noexcept { + if(alive()) { + current = state::succeeded; + } + } + + /** + * @brief Terminates a process with errors if it's still alive, otherwise + * does nothing. + */ + void fail() noexcept { + if(alive()) { + current = state::failed; + } + } + + /*! @brief Stops a process if it's running, otherwise does nothing. */ + void pause() noexcept { + if(alive()) { + current = state::paused; + } + } + + /*! @brief Restarts a process if it's paused, otherwise does nothing. */ + void unpause() noexcept { + if(alive()) { + current = state::running; + } + } + + /** + * @brief Returns true if a process is either running or paused. + * @return True if the process is still alive, false otherwise. + */ + [[nodiscard]] bool alive() const noexcept { + return current == state::running || current == state::paused; + } + + /** + * @brief Returns true if a process is already terminated. + * @return True if the process is terminated, false otherwise. + */ + [[nodiscard]] bool finished() const noexcept { + return current == state::finished; + } + + /** + * @brief Returns true if a process is currently paused. + * @return True if the process is paused, false otherwise. + */ + [[nodiscard]] bool paused() const noexcept { + return current == state::paused; + } + + /** + * @brief Returns true if a process terminated with errors. + * @return True if the process terminated with errors, false otherwise. + */ + [[nodiscard]] bool rejected() const noexcept { + return current == state::rejected; + } + + /** + * @brief Assigns a child process to run in case of success. + * @tparam Type Type of child process to create. + * @tparam Args Types of arguments to use to initialize the child process. + * @param args Parameters to use to initialize the child process. + * @return A reference to the newly created child process. + */ + template + basic_process &then(Args &&...args) { + const auto &allocator = next.second(); + return *(next.first() = stl::allocate_shared(allocator, allocator, stl::forward(args)...)); + } + + /** + * @brief Assigns a child process to run in case of success. + * @tparam Func Type of child process to create. + * @param func Either a lambda or a functor to use as a child process. + * @return A reference to the newly created child process. + */ + template + basic_process &then(Func func) { + const auto &allocator = next.second(); + using process_type = internal::process_adaptor; + return *(next.first() = stl::allocate_shared(allocator, allocator, stl::move(func))); + } + + /** + * @brief Returns the child process without releasing ownership, if any. + * @return The child process attached to the object, if any. + */ + handle_type peek() { + return next.first(); + } + + /** + * @brief Updates a process and its internal state, if required. + * @param delta Elapsed time. + * @param data Optional data. + */ + void tick(const Delta delta, void *data = nullptr) { + switch(current) { + case state::idle: + case state::running: + current = state::running; + update(delta, data); + break; + default: + // suppress warnings + break; + } + + // if it's dead, it must be notified and removed immediately + switch(current) { + case state::succeeded: + succeeded(); + current = state::finished; + break; + case state::failed: + failed(); + current = state::rejected; + break; + case state::aborted: + aborted(); + current = state::rejected; + break; + default: + // suppress warnings + break; + } + } + +private: + compressed_pair next; + state current; +}; + +/*! @cond ENTT_INTERNAL */ +namespace internal { + +template +struct process_adaptor: public basic_process { + using allocator_type = Allocator; + using base_type = basic_process; + using delta_type = base_type::delta_type; + + process_adaptor(const allocator_type &allocator, Func proc) + : base_type{allocator}, + func{stl::move(proc)} {} + + void update(const delta_type delta, void *data) override { + func(*this, delta, data); + } + +private: + Func func; +}; + +} // namespace internal +/*! @endcond */ + +} // namespace entt + +#endif diff --git a/include/entt/process/scheduler.hpp b/include/entt/process/scheduler.hpp new file mode 100644 index 0000000..554f7c2 --- /dev/null +++ b/include/entt/process/scheduler.hpp @@ -0,0 +1,227 @@ +#ifndef ENTT_PROCESS_SCHEDULER_HPP +#define ENTT_PROCESS_SCHEDULER_HPP + +#include "../config/config.h" +#include "../core/compressed_pair.hpp" +#include "../stl/cstddef.hpp" +#include "../stl/memory.hpp" +#include "../stl/type_traits.hpp" +#include "../stl/utility.hpp" +#include "../stl/vector.hpp" +#include "fwd.hpp" +#include "process.hpp" + +namespace entt { + +/** + * @brief Cooperative scheduler for processes. + * + * A cooperative scheduler runs processes and helps managing their life cycles. + * + * Each process is invoked once per tick. If a process terminates, it's + * removed automatically from the scheduler and it's never invoked again.
+ * A process can also have a child. In this case, the process is replaced with + * its child when it terminates if it returns with success. In case of errors, + * both the process and its child are discarded. + * + * In order to invoke all scheduled processes, call the `update` member function + * passing it the elapsed time to forward to the tasks. + * + * @sa process + * + * @tparam Delta Type to use to provide elapsed time. + * @tparam Allocator Type of allocator used to manage memory and elements. + */ +template +class basic_scheduler { + using base_type = basic_process; + using alloc_traits = stl::allocator_traits; + using container_allocator = alloc_traits::template rebind_alloc>; + using container_type = stl::vector, container_allocator>; + +public: + /*! @brief Process type. */ + using type = base_type; + /*! @brief Allocator type. */ + using allocator_type = Allocator; + /*! @brief Unsigned integer type. */ + using size_type = stl::size_t; + /*! @brief Unsigned integer type. */ + using delta_type = Delta; + + /*! @brief Default constructor. */ + basic_scheduler() + : basic_scheduler{allocator_type{}} {} + + /** + * @brief Constructs a scheduler with a given allocator. + * @param allocator The allocator to use. + */ + explicit basic_scheduler(const allocator_type &allocator) + : handlers{allocator, allocator} {} + + /*! @brief Default copy constructor, deleted on purpose. */ + basic_scheduler(const basic_scheduler &) = delete; + + /** + * @brief Move constructor. + * @param other The instance to move from. + */ + basic_scheduler(basic_scheduler &&other) noexcept + : handlers{stl::move(other.handlers)} {} + + /** + * @brief Allocator-extended move constructor. + * @param other The instance to move from. + * @param allocator The allocator to use. + */ + basic_scheduler(basic_scheduler &&other, const allocator_type &allocator) + : handlers{container_type{stl::move(other.handlers.first()), allocator}, allocator} { + ENTT_ASSERT(alloc_traits::is_always_equal::value || get_allocator() == other.get_allocator(), "Copying a scheduler is not allowed"); + } + + /*! @brief Default destructor. */ + ~basic_scheduler() = default; + + /** + * @brief Default copy assignment operator, deleted on purpose. + * @return This process scheduler. + */ + basic_scheduler &operator=(const basic_scheduler &) = delete; + + /** + * @brief Move assignment operator. + * @param other The instance to move from. + * @return This process scheduler. + */ + basic_scheduler &operator=(basic_scheduler &&other) noexcept { + ENTT_ASSERT(alloc_traits::is_always_equal::value || get_allocator() == other.get_allocator(), "Copying a scheduler is not allowed"); + swap(other); + return *this; + } + + /** + * @brief Exchanges the contents with those of a given scheduler. + * @param other Scheduler to exchange the content with. + */ + void swap(basic_scheduler &other) noexcept { + using stl::swap; + swap(handlers, other.handlers); + } + + /** + * @brief Returns the associated allocator. + * @return The associated allocator. + */ + [[nodiscard]] constexpr allocator_type get_allocator() const noexcept { + return handlers.second(); + } + + /** + * @brief Number of processes currently scheduled. + * @return Number of processes currently scheduled. + */ + [[nodiscard]] size_type size() const noexcept { + return handlers.first().size(); + } + + /** + * @brief Returns true if at least a process is currently scheduled. + * @return True if there are scheduled processes, false otherwise. + */ + [[nodiscard]] bool empty() const noexcept { + return handlers.first().empty(); + } + + /** + * @brief Discards all scheduled processes. + * + * Processes aren't aborted. They are discarded along with their children + * and never executed again. + */ + void clear() { + handlers.first().clear(); + } + + /** + * @brief Schedules a process for the next tick. + * @tparam Type Type of process to create. + * @tparam Args Types of arguments to use to initialize the process. + * @param args Parameters to use to initialize the process. + * @return A reference to the newly created process. + */ + template + type &attach(Args &&...args) { + const auto &allocator = handlers.second(); + return *handlers.first().emplace_back(stl::allocate_shared(allocator, allocator, stl::forward(args)...)); + } + + /** + * @brief Schedules a process for the next tick. + * @tparam Func Type of process to create. + * @param func Either a lambda or a functor to use as a process. + * @return A reference to the newly created process. + */ + template + type &attach(Func func) { + const auto &allocator = handlers.second(); + using process_type = internal::process_adaptor; + return *handlers.first().emplace_back(stl::allocate_shared(allocator, allocator, stl::move(func))); + } + + /** + * @brief Updates all scheduled processes. + * + * All scheduled processes are executed in no specific order.
+ * If a process terminates with success, it's replaced with its child, if + * any. Otherwise, if a process terminates with an error, it's removed along + * with its child. + * + * @param delta Elapsed time. + * @param data Optional data. + */ + void update(const delta_type delta, void *data = nullptr) { + for(auto next = handlers.first().size(); next; --next) { + const auto pos = next - 1u; + handlers.first()[pos]->tick(delta, data); + // updating might spawn/reallocate, cannot hold refs until here + auto &elem = handlers.first()[pos]; + + if(elem->finished()) { + elem = elem->peek(); + } + + if(!elem || elem->rejected()) { + elem = stl::move(handlers.first().back()); + handlers.first().pop_back(); + } + } + } + + /** + * @brief Aborts all scheduled processes. + * + * Unless an immediate operation is requested, the abort is scheduled for + * the next tick. Processes won't be executed anymore in any case.
+ * Once a process is fully aborted and thus finished, it's discarded along + * with its child, if any. + * + * @param immediate Requests an immediate operation. + */ + void abort(const bool immediate = false) { + for(auto &&curr: handlers.first()) { + curr->abort(); + + if(immediate) { + curr->tick({}); + } + } + } + +private: + compressed_pair handlers; +}; + +} // namespace entt + +#endif diff --git a/include/entt/resource/cache.hpp b/include/entt/resource/cache.hpp new file mode 100644 index 0000000..99c10e0 --- /dev/null +++ b/include/entt/resource/cache.hpp @@ -0,0 +1,386 @@ +#ifndef ENTT_RESOURCE_RESOURCE_CACHE_HPP +#define ENTT_RESOURCE_RESOURCE_CACHE_HPP + +#include +#include "../container/dense_map.hpp" +#include "../core/compressed_pair.hpp" +#include "../core/fwd.hpp" +#include "../core/iterator.hpp" +#include "../stl/concepts.hpp" +#include "../stl/cstddef.hpp" +#include "../stl/functional.hpp" +#include "../stl/iterator.hpp" +#include "../stl/memory.hpp" +#include "../stl/tuple.hpp" +#include "../stl/type_traits.hpp" +#include "../stl/utility.hpp" +#include "fwd.hpp" +#include "loader.hpp" +#include "resource.hpp" + +namespace entt { + +/*! @cond ENTT_INTERNAL */ +namespace internal { + +template +class resource_cache_iterator final { + template + friend class resource_cache_iterator; + +public: + using value_type = stl::pair>; + using pointer = input_iterator_pointer; + 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 resource_cache_iterator() noexcept = default; + + constexpr resource_cache_iterator(const It iter) noexcept + : it{iter} {} + + template + requires (!stl::same_as && stl::constructible_from) + constexpr resource_cache_iterator(const resource_cache_iterator, Other> &other) noexcept + : it{other.it} {} + + constexpr resource_cache_iterator &operator++() noexcept { + return ++it, *this; + } + + constexpr resource_cache_iterator operator++(int) noexcept { + const resource_cache_iterator orig = *this; + return ++(*this), orig; + } + + constexpr resource_cache_iterator &operator--() noexcept { + return --it, *this; + } + + constexpr resource_cache_iterator operator--(int) noexcept { + const resource_cache_iterator orig = *this; + return operator--(), orig; + } + + constexpr resource_cache_iterator &operator+=(const difference_type value) noexcept { + it += value; + return *this; + } + + constexpr resource_cache_iterator operator+(const difference_type value) const noexcept { + resource_cache_iterator copy = *this; + return (copy += value); + } + + constexpr resource_cache_iterator &operator-=(const difference_type value) noexcept { + return (*this += -value); + } + + constexpr resource_cache_iterator operator-(const difference_type value) const noexcept { + return (*this + -value); + } + + [[nodiscard]] constexpr reference operator[](const difference_type value) const noexcept { + return {it[value].first, resource{it[value].second}}; + } + + [[nodiscard]] constexpr reference operator*() const noexcept { + return operator[](0); + } + + [[nodiscard]] constexpr pointer operator->() const noexcept { + return operator*(); + } + + template + [[nodiscard]] constexpr stl::ptrdiff_t operator-(const resource_cache_iterator &other) const noexcept { + return it - other.it; + } + + template + [[nodiscard]] constexpr bool operator==(const resource_cache_iterator &other) const noexcept { + return it == other.it; + } + + template + [[nodiscard]] constexpr auto operator<=>(const resource_cache_iterator &other) const noexcept { + return it <=> other.it; + } + +private: + It it; +}; + +} // namespace internal +/*! @endcond */ + +/** + * @brief Basic cache for resources of any type. + * @tparam Type Type of resources managed by a cache. + * @tparam Loader Type of loader used to create the resources. + * @tparam Allocator Type of allocator used to manage memory and elements. + */ +template +class resource_cache { + using alloc_traits = stl::allocator_traits; + static_assert(stl::is_same_v, "Invalid value type"); + using container_allocator = alloc_traits::template rebind_alloc>; + using container_type = dense_map, container_allocator>; + +public: + /*! @brief Allocator type. */ + using allocator_type = Allocator; + /*! @brief Resource type. */ + using value_type = Type; + /*! @brief Unsigned integer type. */ + using size_type = stl::size_t; + /*! @brief Loader type. */ + using loader_type = Loader; + /*! @brief Input iterator type. */ + using iterator = internal::resource_cache_iterator; + /*! @brief Constant input iterator type. */ + using const_iterator = internal::resource_cache_iterator; + + /*! @brief Default constructor. */ + resource_cache() + : resource_cache{loader_type{}} {} + + /** + * @brief Constructs an empty cache with a given allocator. + * @param allocator The allocator to use. + */ + explicit resource_cache(const allocator_type &allocator) + : resource_cache{loader_type{}, allocator} {} + + /** + * @brief Constructs an empty cache with a given allocator and loader. + * @param callable The loader to use. + * @param allocator The allocator to use. + */ + explicit resource_cache(const loader_type &callable, const allocator_type &allocator = allocator_type{}) + : pool{container_type{allocator}, callable} {} + + /*! @brief Default copy constructor. */ + resource_cache(const resource_cache &) = default; + + /** + * @brief Allocator-extended copy constructor. + * @param other The instance to copy from. + * @param allocator The allocator to use. + */ + resource_cache(const resource_cache &other, const allocator_type &allocator) + : pool{stl::piecewise_construct, stl::forward_as_tuple(other.pool.first(), allocator), stl::forward_as_tuple(other.pool.second())} {} + + /*! @brief Default move constructor. */ + resource_cache(resource_cache &&) noexcept = default; + + /** + * @brief Allocator-extended move constructor. + * @param other The instance to move from. + * @param allocator The allocator to use. + */ + resource_cache(resource_cache &&other, const allocator_type &allocator) + : pool{stl::piecewise_construct, stl::forward_as_tuple(stl::move(other.pool.first()), allocator), stl::forward_as_tuple(stl::move(other.pool.second()))} {} + + /*! @brief Default destructor. */ + ~resource_cache() = default; + + /** + * @brief Default copy assignment operator. + * @return This cache. + */ + resource_cache &operator=(const resource_cache &) = default; + + /** + * @brief Default move assignment operator. + * @return This cache. + */ + resource_cache &operator=(resource_cache &&) noexcept = default; + + /** + * @brief Returns the associated allocator. + * @return The associated allocator. + */ + [[nodiscard]] constexpr allocator_type get_allocator() const noexcept { + return pool.first().get_allocator(); + } + + /** + * @brief Returns an iterator to the beginning. + * + * If the cache is empty, the returned iterator will be equal to `end()`. + * + * @return An iterator to the first instance of the internal cache. + */ + [[nodiscard]] const_iterator cbegin() const noexcept { + return pool.first().begin(); + } + + /*! @copydoc cbegin */ + [[nodiscard]] const_iterator begin() const noexcept { + return cbegin(); + } + + /*! @copydoc begin */ + [[nodiscard]] iterator begin() noexcept { + return pool.first().begin(); + } + + /** + * @brief Returns an iterator to the end. + * @return An iterator to the element following the last instance of the + * internal cache. + */ + [[nodiscard]] const_iterator cend() const noexcept { + return pool.first().end(); + } + + /*! @copydoc cend */ + [[nodiscard]] const_iterator end() const noexcept { + return cend(); + } + + /*! @copydoc end */ + [[nodiscard]] iterator end() noexcept { + return pool.first().end(); + } + + /** + * @brief Returns true if a cache contains no resources, false otherwise. + * @return True if the cache contains no resources, false otherwise. + */ + [[nodiscard]] bool empty() const noexcept { + return pool.first().empty(); + } + + /** + * @brief Number of resources managed by a cache. + * @return Number of resources currently stored. + */ + [[nodiscard]] size_type size() const noexcept { + return pool.first().size(); + } + + /*! @brief Clears a cache. */ + void clear() noexcept { + pool.first().clear(); + } + + /** + * @brief Loads a resource, if its identifier does not exist. + * + * Arguments are forwarded directly to the loader and _consumed_ only if the + * resource doesn't already exist. + * + * @warning + * If the resource isn't loaded correctly, the returned handle could be + * invalid and any use of it will result in undefined behavior. + * + * @tparam Args Types of arguments to use to load the resource if required. + * @param id Unique resource identifier. + * @param args Arguments to use to load the resource if required. + * @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 + stl::pair load(const id_type id, Args &&...args) { + if(auto it = pool.first().find(id); it != pool.first().end()) { + return {it, false}; + } + + return pool.first().emplace(id, pool.second()(stl::forward(args)...)); + } + + /** + * @brief Force loads a resource, even if its identifier already exists. + * @copydetails load + */ + template + stl::pair force_load(const id_type id, Args &&...args) { + return {pool.first().insert_or_assign(id, pool.second()(stl::forward(args)...)).first, true}; + } + + /** + * @brief Returns a handle for a given resource identifier. + * + * @warning + * There is no guarantee that the returned handle is valid.
+ * If it is not, any use will result in undefined behavior. + * + * @param id Unique resource identifier. + * @return A handle for the given resource. + */ + [[nodiscard]] resource operator[](const id_type id) const { + if(auto it = pool.first().find(id); it != pool.first().cend()) { + return resource{it->second}; + } + + return {}; + } + + /*! @copydoc operator[] */ + [[nodiscard]] resource operator[](const id_type id) { + if(auto it = pool.first().find(id); it != pool.first().end()) { + return resource{it->second}; + } + + return {}; + } + + /** + * @brief Checks if a cache contains a given identifier. + * @param id Unique resource identifier. + * @return True if the cache contains the resource, false otherwise. + */ + [[nodiscard]] bool contains(const id_type id) const { + return pool.first().contains(id); + } + + /** + * @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 it = pool.first().begin(); + return pool.first().erase(it + (pos - const_iterator{it})); + } + + /** + * @brief Removes the given elements from a cache. + * @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 it = pool.first().begin(); + return pool.first().erase(it + (first - const_iterator{it}), it + (last - const_iterator{it})); + } + + /** + * @brief Removes the given elements from a cache. + * @param id Unique resource identifier. + * @return Number of resources erased (either 0 or 1). + */ + size_type erase(const id_type id) { + return pool.first().erase(id); + } + + /** + * @brief Returns the loader used to create resources. + * @return The loader used to create resources. + */ + [[nodiscard]] loader_type loader() const { + return pool.second(); + } + +private: + compressed_pair pool; +}; + +} // namespace entt + +#endif diff --git a/include/entt/resource/fwd.hpp b/include/entt/resource/fwd.hpp new file mode 100644 index 0000000..bb9c542 --- /dev/null +++ b/include/entt/resource/fwd.hpp @@ -0,0 +1,19 @@ +#ifndef ENTT_RESOURCE_FWD_HPP +#define ENTT_RESOURCE_FWD_HPP + +#include "../stl/memory.hpp" + +namespace entt { + +template +struct resource_loader; + +template, typename = stl::allocator> +class resource_cache; + +template +class resource; + +} // namespace entt + +#endif diff --git a/include/entt/resource/loader.hpp b/include/entt/resource/loader.hpp new file mode 100644 index 0000000..2df95d0 --- /dev/null +++ b/include/entt/resource/loader.hpp @@ -0,0 +1,33 @@ +#ifndef ENTT_RESOURCE_LOADER_HPP +#define ENTT_RESOURCE_LOADER_HPP + +#include "../stl/memory.hpp" +#include "../stl/utility.hpp" +#include "fwd.hpp" + +namespace entt { + +/** + * @brief Transparent loader for shared resources. + * @tparam Type Type of resources created by the loader. + */ +template +struct resource_loader { + /*! @brief Result type. */ + using result_type = stl::shared_ptr; + + /** + * @brief Constructs a shared pointer to a resource from its arguments. + * @tparam Args Types of arguments to use to construct the resource. + * @param args Parameters to use to construct the resource. + * @return A shared pointer to a resource of the given type. + */ + template + result_type operator()(Args &&...args) const { + return stl::make_shared(stl::forward(args)...); + } +}; + +} // namespace entt + +#endif diff --git a/include/entt/resource/resource.hpp b/include/entt/resource/resource.hpp new file mode 100644 index 0000000..670569d --- /dev/null +++ b/include/entt/resource/resource.hpp @@ -0,0 +1,212 @@ +#ifndef ENTT_RESOURCE_RESOURCE_HPP +#define ENTT_RESOURCE_RESOURCE_HPP + +#include +#include "../stl/concepts.hpp" +#include "../stl/memory.hpp" +#include "../stl/utility.hpp" +#include "fwd.hpp" + +namespace entt { + +/** + * @brief Basic resource handle. + * + * A handle wraps a resource and extends its lifetime. It also shares the same + * resource with all other handles constructed from the same element.
+ * As a rule of thumb, resources should never be copied nor moved. Handles are + * the way to go to push references around. + * + * @tparam Type Type of resource managed by a handle. + */ +template +class resource { + template + friend class resource; + +public: + /*! @brief Resource type. */ + using element_type = Type; + /*! @brief Handle type. */ + using handle_type = stl::shared_ptr; + + /*! @brief Default constructor. */ + resource() noexcept + : value{} {} + + /** + * @brief Creates a new resource handle. + * @param res A handle to a resource. + */ + explicit resource(handle_type res) noexcept + : value{stl::move(res)} {} + + /*! @brief Default copy constructor. */ + resource(const resource &) noexcept = default; + + /*! @brief Default move constructor. */ + resource(resource &&) noexcept = default; + + /** + * @brief Aliasing constructor. + * @tparam Other Type of resource managed by the received handle. + * @param other The handle with which to share ownership information. + * @param res Unrelated and unmanaged resources. + */ + template + resource(const resource &other, element_type &res) noexcept + : value{other.value, stl::addressof(res)} {} + + /** + * @brief Copy constructs a handle which shares ownership of the resource. + * @tparam Other Type of resource managed by the received handle. + * @param other The handle to copy from. + */ + template + requires (!stl::same_as && stl::constructible_from) + resource(const resource &other) noexcept + : value{other.value} {} + + /** + * @brief Move constructs a handle which takes ownership of the resource. + * @tparam Other Type of resource managed by the received handle. + * @param other The handle to move from. + */ + template + requires (!stl::same_as && stl::constructible_from) + resource(resource &&other) noexcept + : value{stl::move(other.value)} {} + + /*! @brief Default destructor. */ + ~resource() = default; + + /** + * @brief Default copy assignment operator. + * @return This resource handle. + */ + resource &operator=(const resource &) noexcept = default; + + /** + * @brief Default move assignment operator. + * @return This resource handle. + */ + resource &operator=(resource &&) noexcept = default; + + /** + * @brief Copy assignment operator from foreign handle. + * @tparam Other Type of resource managed by the received handle. + * @param other The handle to copy from. + * @return This resource handle. + */ + template + requires (!stl::same_as && stl::constructible_from) + resource &operator=(const resource &other) noexcept { + value = other.value; + return *this; + } + + /** + * @brief Move assignment operator from foreign handle. + * @tparam Other Type of resource managed by the received handle. + * @param other The handle to move from. + * @return This resource handle. + */ + template + requires (!stl::same_as && stl::constructible_from) + resource &operator=(resource &&other) noexcept { + value = stl::move(other.value); + return *this; + } + + /** + * @brief Exchanges the content with that of a given resource. + * @param other Resource to exchange the content with. + */ + void swap(resource &other) noexcept { + using stl::swap; + swap(value, other.value); + } + + /** + * @brief Returns a reference to the managed resource. + * + * @warning + * The behavior is undefined if the handle doesn't contain a resource. + * + * @return A reference to the managed resource. + */ + [[nodiscard]] element_type &operator*() const noexcept { + return *value; + } + + /*! @copydoc operator* */ + [[nodiscard]] operator element_type &() const noexcept { + return *value; + } + + /** + * @brief Returns a pointer to the managed resource. + * @return A pointer to the managed resource. + */ + [[nodiscard]] element_type *operator->() const noexcept { + return value.get(); + } + + /** + * @brief Returns true if a handle contains a resource, false otherwise. + * @return True if the handle contains a resource, false otherwise. + */ + [[nodiscard]] explicit operator bool() const noexcept { + return static_cast(value); + } + + /** + * @brief Compares two handles. + * @tparam Other Type of resource managed by the other handle. + * @param other A valid handle. + * @return True if both handles refer to the same resource, false otherwise. + */ + template + [[nodiscard]] bool operator==(const resource &other) const noexcept { + return (value == other.value); + } + + /** + * @brief Lexicographically compares two handles. + * @tparam Other Type of resource managed by the other handle. + * @param other A valid handle. + * @return The relative order between the two handles. + */ + template + [[nodiscard]] auto operator<=>(const resource &other) const noexcept { + return (value <=> other.value); + } + + /*! @brief Releases the ownership of the managed resource. */ + void reset() { + value.reset(); + } + + /** + * @brief Replaces the managed resource. + * @param other A handle to a resource. + */ + void reset(handle_type other) { + value = stl::move(other); + } + + /** + * @brief Returns the underlying resource handle. + * @return The underlying resource handle. + */ + [[nodiscard]] handle_type handle() const noexcept { + return value; + } + +private: + handle_type value; +}; + +} // namespace entt + +#endif diff --git a/include/entt/signal/delegate.hpp b/include/entt/signal/delegate.hpp new file mode 100644 index 0000000..0ecedcd --- /dev/null +++ b/include/entt/signal/delegate.hpp @@ -0,0 +1,314 @@ +#ifndef ENTT_SIGNAL_DELEGATE_HPP +#define ENTT_SIGNAL_DELEGATE_HPP + +#include "../config/config.h" +#include "../core/type_traits.hpp" +#include "../stl/cstddef.hpp" +#include "../stl/functional.hpp" +#include "../stl/tuple.hpp" +#include "../stl/type_traits.hpp" +#include "../stl/utility.hpp" +#include "fwd.hpp" + +namespace entt { + +/*! @cond ENTT_INTERNAL */ +namespace internal { + +template +auto function_pointer(Ret (*)(Args...)) -> Ret (*)(Args...); + +template +auto function_pointer(Ret (*)(Type, Args...), Other &&) -> Ret (*)(Args...); + +template +auto function_pointer(Ret (Class::*)(Args...), Other &&...) -> Ret (*)(Args...); + +template +auto function_pointer(Ret (Class::*)(Args...) const, Other &&...) -> Ret (*)(Args...); + +template +requires stl::is_member_object_pointer_v +auto function_pointer(Type Class::*, Other &&...) -> Type (*)(); + +template +using function_pointer_t = decltype(function_pointer(stl::declval()...)); + +template +[[nodiscard]] ENTT_CONSTEVAL auto index_sequence_for(Ret (*)(Args...)) { + return stl::index_sequence_for{}; +} + +} // namespace internal +/*! @endcond */ + +/** + * @brief Basic delegate implementation. + * + * Primary template isn't defined on purpose. All the specializations give a + * compile-time error unless the template parameter is a function type. + */ +template +class delegate; + +/** + * @brief Utility class to use to send around functions and members. + * + * Unmanaged delegate for function pointers and members. Users of this class are + * in charge of disconnecting instances before deleting them. + * + * A delegate can be used as a general purpose invoker without memory overhead + * for free functions possibly with payloads and bound or unbound members. + * + * @tparam Ret Return type of a function type. + * @tparam Args Types of arguments of a function type. + */ +template +class delegate { + using return_type = stl::remove_const_t; + using delegate_type = return_type(const void *, Args...); + + template + [[nodiscard]] auto wrap(stl::index_sequence) noexcept { + return [](const void *, Args... args) -> return_type { + [[maybe_unused]] const auto arguments = stl::forward_as_tuple(stl::forward(args)...); + [[maybe_unused]] constexpr auto offset = !stl::is_invocable_r_v>...> * (sizeof...(Args) - sizeof...(Index)); + return static_cast(stl::invoke(Candidate, stl::forward>>(stl::get(arguments))...)); + }; + } + + template + [[nodiscard]] auto wrap(Type &, stl::index_sequence) noexcept { + return [](const void *payload, Args... args) -> return_type { + Type *curr = static_cast(const_cast *>(payload)); + [[maybe_unused]] const auto arguments = stl::forward_as_tuple(stl::forward(args)...); + [[maybe_unused]] constexpr auto offset = !stl::is_invocable_r_v>...> * (sizeof...(Args) - sizeof...(Index)); + return static_cast(stl::invoke(Candidate, *curr, stl::forward>>(stl::get(arguments))...)); + }; + } + + template + [[nodiscard]] auto wrap(Type *, stl::index_sequence) noexcept { + return [](const void *payload, Args... args) -> return_type { + Type *curr = static_cast(const_cast *>(payload)); + [[maybe_unused]] const auto arguments = stl::forward_as_tuple(stl::forward(args)...); + [[maybe_unused]] constexpr auto offset = !stl::is_invocable_r_v>...> * (sizeof...(Args) - sizeof...(Index)); + return static_cast(stl::invoke(Candidate, curr, stl::forward>>(stl::get(arguments))...)); + }; + } + +public: + /*! @brief Function type of the contained target. */ + using function_type = Ret(const void *, Args...); + /*! @brief Function type of the delegate. */ + using type = Ret(Args...); + /*! @brief Return type of the delegate. */ + using result_type = Ret; + + /*! @brief Default constructor. */ + delegate() noexcept = default; + + /** + * @brief Constructs a delegate with a given object or payload, if any. + * @tparam Candidate Function or member to connect to the delegate. + * @tparam Type Type of class or type of payload, if any. + * @param value_or_instance Optional valid object that fits the purpose. + */ + template + delegate(connect_arg_t, Type &&...value_or_instance) noexcept { + connect(stl::forward(value_or_instance)...); + } + + /** + * @brief Constructs a delegate and connects an user defined function with + * optional payload. + * @param function Function to connect to the delegate. + * @param payload User defined arbitrary data. + */ + delegate(function_type *function, const void *payload = nullptr) noexcept { + connect(function, payload); + } + + /** + * @brief Connects a free function or an unbound member to a delegate. + * @tparam Candidate Function or member to connect to the delegate. + */ + template + void connect() noexcept { + instance = nullptr; + + if constexpr(stl::is_invocable_r_v) { + fn = [](const void *, Args... args) -> return_type { + return Ret(stl::invoke(Candidate, stl::forward(args)...)); + }; + } else if constexpr(stl::is_member_pointer_v) { + fn = wrap(internal::index_sequence_for>>(internal::function_pointer_t{})); + } else { + fn = wrap(internal::index_sequence_for(internal::function_pointer_t{})); + } + } + + /** + * @brief Connects a free function with payload or a bound member to a + * delegate. + * + * The delegate isn't responsible for the connected object or the payload. + * Users must always guarantee that the lifetime of the instance overcomes + * the one of the delegate.
+ * When used to connect a free function with payload, its signature must be + * such that the instance is the first argument before the ones used to + * define the delegate itself. + * + * @tparam Candidate Function or member to connect to the delegate. + * @tparam Type Type of class or type of payload. + * @param value_or_instance A valid reference that fits the purpose. + */ + template + void connect(Type &value_or_instance) noexcept { + instance = &value_or_instance; + + if constexpr(stl::is_invocable_r_v) { + fn = [](const void *payload, Args... args) -> return_type { + Type *curr = static_cast(const_cast *>(payload)); + return Ret(stl::invoke(Candidate, *curr, stl::forward(args)...)); + }; + } else { + fn = wrap(value_or_instance, internal::index_sequence_for(internal::function_pointer_t{})); + } + } + + /** + * @brief Connects a free function with payload or a bound member to a + * delegate. + * + * @sa connect(Type &) + * + * @tparam Candidate Function or member to connect to the delegate. + * @tparam Type Type of class or type of payload. + * @param value_or_instance A valid pointer that fits the purpose. + */ + template + void connect(Type *value_or_instance) noexcept { + instance = value_or_instance; + + if constexpr(stl::is_invocable_r_v) { + fn = [](const void *payload, Args... args) -> return_type { + Type *curr = static_cast(const_cast *>(payload)); + return Ret(stl::invoke(Candidate, curr, stl::forward(args)...)); + }; + } else { + fn = wrap(value_or_instance, internal::index_sequence_for(internal::function_pointer_t{})); + } + } + + /** + * @brief Connects an user defined function with optional payload to a + * delegate. + * + * The delegate isn't responsible for the connected object or the payload. + * Users must always guarantee that the lifetime of an instance overcomes + * the one of the delegate.
+ * The payload is returned as the first argument to the target function in + * all cases. + * + * @param function Function to connect to the delegate. + * @param payload User defined arbitrary data. + */ + void connect(function_type *function, const void *payload = nullptr) noexcept { + ENTT_ASSERT(function != nullptr, "Uninitialized function pointer"); + instance = payload; + fn = function; + } + + /** + * @brief Resets a delegate. + * + * After a reset, a delegate cannot be invoked anymore. + */ + void reset() noexcept { + instance = nullptr; + fn = nullptr; + } + + /** + * @brief Returns a pointer to the stored callable function target, if any. + * @return An opaque pointer to the stored callable function target. + */ + [[nodiscard]] function_type *target() const noexcept { + return fn; + } + + /** + * @brief Returns the instance or the payload linked to a delegate, if any. + * @return An opaque pointer to the underlying data. + */ + [[nodiscard]] const void *data() const noexcept { + return instance; + } + + /** + * @brief Triggers a delegate. + * + * The delegate invokes the underlying function and returns the result. + * + * @warning + * Attempting to trigger an invalid delegate results in undefined + * behavior. + * + * @param args Arguments to use to invoke the underlying function. + * @return The value returned by the underlying function. + */ + Ret operator()(Args... args) const { + ENTT_ASSERT(static_cast(*this), "Uninitialized delegate"); + return fn(instance, stl::forward(args)...); + } + + /** + * @brief Checks whether a delegate actually stores a listener. + * @return False if the delegate is empty, true otherwise. + */ + [[nodiscard]] explicit operator bool() const noexcept { + // no need to also test instance + return !(fn == nullptr); + } + + /** + * @brief Compares the contents of two delegates. + * @param other Delegate with which to compare. + * @return False if the two contents differ, true otherwise. + */ + [[nodiscard]] bool operator==(const delegate &other) const noexcept { + return fn == other.fn && instance == other.instance; + } + +private: + const void *instance{}; + delegate_type *fn{}; +}; + +/** + * @brief Deduction guide. + * @tparam Candidate Function or member to connect to the delegate. + */ +template +delegate(connect_arg_t) -> delegate>>; + +/** + * @brief Deduction guide. + * @tparam Candidate Function or member to connect to the delegate. + * @tparam Type Type of class or type of payload. + */ +template +delegate(connect_arg_t, Type &&) -> delegate>>; + +/** + * @brief Deduction guide. + * @tparam Ret Return type of a function type. + * @tparam Args Types of arguments of a function type. + */ +template +delegate(Ret (*)(const void *, Args...), const void * = nullptr) -> delegate; + +} // namespace entt + +#endif diff --git a/include/entt/signal/dispatcher.hpp b/include/entt/signal/dispatcher.hpp new file mode 100644 index 0000000..3a3ccee --- /dev/null +++ b/include/entt/signal/dispatcher.hpp @@ -0,0 +1,391 @@ +#ifndef ENTT_SIGNAL_DISPATCHER_HPP +#define ENTT_SIGNAL_DISPATCHER_HPP + +#include "../container/dense_map.hpp" +#include "../core/compressed_pair.hpp" +#include "../core/concepts.hpp" +#include "../core/fwd.hpp" +#include "../core/type_info.hpp" +#include "../stl/cstddef.hpp" +#include "../stl/functional.hpp" +#include "../stl/memory.hpp" +#include "../stl/type_traits.hpp" +#include "../stl/utility.hpp" +#include "../stl/vector.hpp" +#include "fwd.hpp" +#include "sigh.hpp" + +namespace entt { + +/*! @cond ENTT_INTERNAL */ +namespace internal { + +struct basic_dispatcher_handler { + virtual ~basic_dispatcher_handler() = default; + virtual void publish() = 0; + virtual void disconnect(void *) = 0; + virtual void clear() noexcept = 0; + [[nodiscard]] virtual stl::size_t size() const noexcept = 0; +}; + +template +class dispatcher_handler final: public basic_dispatcher_handler { + using alloc_traits = stl::allocator_traits; + using signal_type = sigh; + using container_type = stl::vector>; + +public: + using allocator_type = Allocator; + + dispatcher_handler(const allocator_type &allocator) + : signal{allocator}, + events{allocator} {} + + void publish() override { + container_type other{}; + other.swap(events); + + for(auto &&elem: other) { + signal.publish(elem); + } + } + + void disconnect(void *instance) override { + bucket().disconnect(instance); + } + + void clear() noexcept override { + events.clear(); + } + + [[nodiscard]] auto bucket() noexcept { + return typename signal_type::sink_type{signal}; + } + + void trigger(Type &event) { + signal.publish(event); + } + + template + void enqueue(Args &&...args) { + if constexpr(stl::is_aggregate_v && (sizeof...(Args) != 0u || !stl::is_default_constructible_v)) { + events.push_back(Type{stl::forward(args)...}); + } else { + events.emplace_back(stl::forward(args)...); + } + } + + [[nodiscard]] stl::size_t size() const noexcept override { + return events.size(); + } + +private: + signal_type signal; + container_type events; +}; + +} // namespace internal +/*! @endcond */ + +/** + * @brief Basic dispatcher implementation. + * + * A dispatcher can be used either to trigger an immediate event or to enqueue + * events to be published all together once per tick.
+ * Listeners are provided in the form of member functions. For each event of + * type `Type`, listeners are such that they can be invoked with an argument of + * type `Type &`, no matter what the return type is. + * + * The dispatcher creates instances of the `sigh` class internally. Refer to the + * documentation of the latter for more details. + * + * @tparam Allocator Type of allocator used to manage memory and elements. + */ +template +class basic_dispatcher { + template + using handler_type = internal::dispatcher_handler; + + using key_type = id_type; + // stl::shared_ptr because of its type erased allocator which is useful here + using mapped_type = stl::shared_ptr; + + using alloc_traits = stl::allocator_traits; + using container_allocator = alloc_traits::template rebind_alloc>; + using container_type = dense_map, container_allocator>; + + template + [[nodiscard]] handler_type &assure(const id_type id) { + auto &&ptr = pools.first()[id]; + + if(!ptr) { + const auto &allocator = get_allocator(); + ptr = stl::allocate_shared>(allocator, allocator); + } + + return static_cast &>(*ptr); + } + + template + [[nodiscard]] const handler_type *assure(const id_type id) const { + if(auto it = pools.first().find(id); it != pools.first().cend()) { + return static_cast *>(it->second.get()); + } + + return nullptr; + } + +public: + /*! @brief Allocator type. */ + using allocator_type = Allocator; + /*! @brief Unsigned integer type. */ + using size_type = stl::size_t; + + /*! @brief Default constructor. */ + basic_dispatcher() + : basic_dispatcher{allocator_type{}} {} + + /** + * @brief Constructs a dispatcher with a given allocator. + * @param allocator The allocator to use. + */ + explicit basic_dispatcher(const allocator_type &allocator) + : pools{allocator, allocator} {} + + /*! @brief Default copy constructor, deleted on purpose. */ + basic_dispatcher(const basic_dispatcher &) = delete; + + /** + * @brief Move constructor. + * @param other The instance to move from. + */ + basic_dispatcher(basic_dispatcher &&other) noexcept + : pools{stl::move(other.pools)} {} + + /** + * @brief Allocator-extended move constructor. + * @param other The instance to move from. + * @param allocator The allocator to use. + */ + basic_dispatcher(basic_dispatcher &&other, const allocator_type &allocator) + : pools{container_type{stl::move(other.pools.first()), allocator}, allocator} { + ENTT_ASSERT(alloc_traits::is_always_equal::value || get_allocator() == other.get_allocator(), "Copying a dispatcher is not allowed"); + } + + /*! @brief Default destructor. */ + ~basic_dispatcher() = default; + + /** + * @brief Default copy assignment operator, deleted on purpose. + * @return This dispatcher. + */ + basic_dispatcher &operator=(const basic_dispatcher &) = delete; + + /** + * @brief Move assignment operator. + * @param other The instance to move from. + * @return This dispatcher. + */ + basic_dispatcher &operator=(basic_dispatcher &&other) noexcept { + ENTT_ASSERT(alloc_traits::is_always_equal::value || get_allocator() == other.get_allocator(), "Copying a dispatcher is not allowed"); + swap(other); + return *this; + } + + /** + * @brief Exchanges the contents with those of a given dispatcher. + * @param other Dispatcher to exchange the content with. + */ + void swap(basic_dispatcher &other) noexcept { + using stl::swap; + swap(pools, other.pools); + } + + /** + * @brief Returns the associated allocator. + * @return The associated allocator. + */ + [[nodiscard]] constexpr allocator_type get_allocator() const noexcept { + return pools.second(); + } + + /** + * @brief Returns the number of pending events for a given type. + * @tparam Type Type of event for which to return the count. + * @param id Name used to map the event queue within the dispatcher. + * @return The number of pending events for the given type. + */ + template + [[nodiscard]] size_type size(const id_type id = type_hash::value()) const noexcept { + const auto *cpool = assure>(id); + return cpool ? cpool->size() : 0u; + } + + /** + * @brief Returns the total number of pending events. + * @return The total number of pending events. + */ + [[nodiscard]] size_type size() const noexcept { + size_type count{}; + + for(auto &&cpool: pools.first()) { + count += cpool.second->size(); + } + + return count; + } + + /** + * @brief Returns a sink object for the given event and queue. + * + * A sink is an opaque object used to connect listeners to events. + * + * The function type for a listener is _compatible_ with: + * + * @code{.cpp} + * void(Type &); + * @endcode + * + * The order of invocation of the listeners isn't guaranteed. + * + * @sa sink + * + * @tparam Type Type of event of which to get the sink. + * @param id Name used to map the event queue within the dispatcher. + * @return A temporary sink object. + */ + template + [[nodiscard]] auto sink(const id_type id = type_hash::value()) { + return assure(id).bucket(); + } + + /** + * @brief Triggers an immediate event of a given type. + * @tparam Type Type of event to trigger. + * @param value An instance of the given type of event. + */ + template + void trigger(Type value) { + trigger(type_hash>::value(), value); + } + + /** + * @brief Triggers an immediate event on a queue of a given type. + * @tparam Type Type of event to trigger. + * @param value An instance of the given type of event. + * @param id Name used to map the event queue within the dispatcher. + */ + template + void trigger(const id_type id, Type value) { + assure>(id).trigger(value); + } + + /** + * @brief Enqueues an event of the given type. + * @tparam Type Type of event to enqueue. + * @tparam Args Types of arguments to use to construct the event. + * @param args Arguments to use to construct the event. + */ + template + void enqueue(Args &&...args) { + enqueue_hint(type_hash::value(), stl::forward(args)...); + } + + /** + * @brief Enqueues an event of the given type. + * @tparam Type Type of event to enqueue. + * @param value An instance of the given type of event. + */ + template + void enqueue(Type &&value) { + enqueue_hint(type_hash>::value(), stl::forward(value)); + } + + /** + * @brief Enqueues an event of the given type. + * @tparam Type Type of event to enqueue. + * @tparam Args Types of arguments to use to construct the event. + * @param id Name used to map the event queue within the dispatcher. + * @param args Arguments to use to construct the event. + */ + template + void enqueue_hint(const id_type id, Args &&...args) { + assure(id).enqueue(stl::forward(args)...); + } + + /** + * @brief Enqueues an event of the given type. + * @tparam Type Type of event to enqueue. + * @param id Name used to map the event queue within the dispatcher. + * @param value An instance of the given type of event. + */ + template + void enqueue_hint(const id_type id, Type &&value) { + assure>(id).enqueue(stl::forward(value)); + } + + /** + * @brief Utility function to disconnect everything related to a given value + * or instance from a dispatcher. + * @tparam Type Type of class or type of payload. + * @param value_or_instance A valid object that fits the purpose. + */ + template + void disconnect(Type &value_or_instance) { + disconnect(&value_or_instance); + } + + /** + * @brief Utility function to disconnect everything related to a given value + * or instance from a dispatcher. + * @tparam Type Type of class or type of payload. + * @param value_or_instance A valid object that fits the purpose. + */ + template + void disconnect(Type *value_or_instance) { + for(auto &&cpool: pools.first()) { + cpool.second->disconnect(value_or_instance); + } + } + + /** + * @brief Discards all the events stored so far in a given queue. + * @tparam Type Type of event to discard. + * @param id Name used to map the event queue within the dispatcher. + */ + template + void clear(const id_type id = type_hash::value()) { + assure(id).clear(); + } + + /*! @brief Discards all the events queued so far. */ + void clear() noexcept { + for(auto &&cpool: pools.first()) { + cpool.second->clear(); + } + } + + /** + * @brief Delivers all the pending events of a given queue. + * @tparam Type Type of event to send. + * @param id Name used to map the event queue within the dispatcher. + */ + template + void update(const id_type id = type_hash::value()) { + assure(id).publish(); + } + + /*! @brief Delivers all the pending events. */ + void update() const { + for(auto &&cpool: pools.first()) { + cpool.second->publish(); + } + } + +private: + compressed_pair pools; +}; + +} // namespace entt + +#endif diff --git a/include/entt/signal/emitter.hpp b/include/entt/signal/emitter.hpp new file mode 100644 index 0000000..1ef4951 --- /dev/null +++ b/include/entt/signal/emitter.hpp @@ -0,0 +1,181 @@ +#ifndef ENTT_SIGNAL_EMITTER_HPP +#define ENTT_SIGNAL_EMITTER_HPP + +#include "../container/dense_map.hpp" +#include "../core/compressed_pair.hpp" +#include "../core/fwd.hpp" +#include "../core/type_info.hpp" +#include "../stl/functional.hpp" +#include "../stl/type_traits.hpp" +#include "../stl/utility.hpp" +#include "fwd.hpp" + +namespace entt { + +/** + * @brief General purpose event emitter. + * + * To create an emitter type, derived classes must inherit from the base as: + * + * @code{.cpp} + * struct my_emitter: emitter { + * // ... + * } + * @endcode + * + * Handlers for the different events are created internally on the fly. It's not + * required to specify in advance the full list of accepted events.
+ * Moreover, whenever an event is published, an emitter also passes a reference + * to itself to its listeners. + * + * @tparam Derived Emitter type. + * @tparam Allocator Type of allocator used to manage memory and elements. + */ +template +class emitter { + using key_type = id_type; + using mapped_type = stl::function; + + using alloc_traits = stl::allocator_traits; + using container_allocator = alloc_traits::template rebind_alloc>; + using container_type = dense_map, container_allocator>; + +public: + /*! @brief Allocator type. */ + using allocator_type = Allocator; + /*! @brief Unsigned integer type. */ + using size_type = stl::size_t; + + /*! @brief Default constructor. */ + emitter() + : emitter{allocator_type{}} {} + + /** + * @brief Constructs an emitter with a given allocator. + * @param allocator The allocator to use. + */ + explicit emitter(const allocator_type &allocator) + : handlers{allocator, allocator} {} + + /*! @brief Default copy constructor, deleted on purpose. */ + emitter(const emitter &) = delete; + + /** + * @brief Move constructor. + * @param other The instance to move from. + */ + emitter(emitter &&other) noexcept + : handlers{stl::move(other.handlers)} {} + + /** + * @brief Allocator-extended move constructor. + * @param other The instance to move from. + * @param allocator The allocator to use. + */ + emitter(emitter &&other, const allocator_type &allocator) + : handlers{container_type{stl::move(other.handlers.first()), allocator}, allocator} { + ENTT_ASSERT(alloc_traits::is_always_equal::value || handlers.second() == other.handlers.second(), "Copying an emitter is not allowed"); + } + + /*! @brief Default destructor. */ + virtual ~emitter() { + static_assert(stl::is_base_of_v, Derived>, "Invalid emitter type"); + } + + /** + * @brief Default copy assignment operator, deleted on purpose. + * @return This emitter. + */ + emitter &operator=(const emitter &) = delete; + + /** + * @brief Move assignment operator. + * @param other The instance to move from. + * @return This emitter. + */ + emitter &operator=(emitter &&other) noexcept { + ENTT_ASSERT(alloc_traits::is_always_equal::value || handlers.second() == other.handlers.second(), "Copying an emitter is not allowed"); + swap(other); + return *this; + } + + /** + * @brief Exchanges the contents with those of a given emitter. + * @param other Emitter to exchange the content with. + */ + void swap(emitter &other) noexcept { + using stl::swap; + swap(handlers, other.handlers); + } + + /** + * @brief Returns the associated allocator. + * @return The associated allocator. + */ + [[nodiscard]] constexpr allocator_type get_allocator() const noexcept { + return handlers.second(); + } + + /** + * @brief Publishes a given event. + * @tparam Type Type of event to trigger. + * @param value An instance of the given type of event. + */ + template + void publish(Type value) { + if(const auto id = type_id().hash(); handlers.first().contains(id)) { + handlers.first()[id](&value); + } + } + + /** + * @brief Registers a listener with the event emitter. + * @tparam Type Type of event to which to connect the listener. + * @param func The listener to register. + */ + template + void on(stl::function func) { + handlers.first().insert_or_assign(type_id().hash(), [func = stl::move(func), this](void *value) { + func(*static_cast(value), static_cast(*this)); + }); + } + + /** + * @brief Disconnects a listener from the event emitter. + * @tparam Type Type of event of the listener. + */ + template + void erase() { + handlers.first().erase(type_hash>::value()); + } + + /*! @brief Disconnects all the listeners. */ + void clear() noexcept { + handlers.first().clear(); + } + + /** + * @brief Checks if there are listeners registered for the specific event. + * @tparam Type Type of event to test. + * @return True if there are no listeners registered, false otherwise. + */ + template + [[nodiscard]] bool contains() const { + return handlers.first().contains(type_hash>::value()); + } + + /** + * @brief Checks if there are listeners registered with the event emitter. + * @return True if there are no listeners registered, false otherwise. + */ + [[nodiscard]] bool empty() const noexcept { + return handlers.first().empty(); + } + +private: + compressed_pair handlers; +}; + +} // namespace entt + +#endif diff --git a/include/entt/signal/fwd.hpp b/include/entt/signal/fwd.hpp new file mode 100644 index 0000000..4543622 --- /dev/null +++ b/include/entt/signal/fwd.hpp @@ -0,0 +1,46 @@ +#ifndef ENTT_SIGNAL_FWD_HPP +#define ENTT_SIGNAL_FWD_HPP + +#include "../stl/memory.hpp" + +namespace entt { + +template +class delegate; + +template> +class basic_dispatcher; + +template> +class emitter; + +class connection; + +struct scoped_connection; + +template +class sink; + +template> +class sigh; + +/*! @brief Alias declaration for the most common use case. */ +using dispatcher = basic_dispatcher<>; + +/*! @brief Disambiguation tag for constructors and the like. */ +template +struct connect_arg_t { + /*! @brief Default constructor. */ + explicit connect_arg_t() = default; +}; + +/** + * @brief Constant of type connect_arg_t used to disambiguate calls. + * @tparam Candidate Element to connect (likely a free or member function). + */ +template +inline constexpr connect_arg_t connect_arg{}; + +} // namespace entt + +#endif diff --git a/include/entt/signal/sigh.hpp b/include/entt/signal/sigh.hpp new file mode 100644 index 0000000..fb05a36 --- /dev/null +++ b/include/entt/signal/sigh.hpp @@ -0,0 +1,573 @@ +#ifndef ENTT_SIGNAL_SIGH_HPP +#define ENTT_SIGNAL_SIGH_HPP + +#include "../stl/cstddef.hpp" +#include "../stl/memory.hpp" +#include "../stl/type_traits.hpp" +#include "../stl/utility.hpp" +#include "../stl/vector.hpp" +#include "delegate.hpp" +#include "fwd.hpp" + +namespace entt { + +/** + * @brief Sink class. + * + * Primary template isn't defined on purpose. All the specializations give a + * compile-time error unless the template parameter is a function type. + * + * @tparam Type A valid signal handler type. + */ +template +class sink; + +/** + * @brief Unmanaged signal handler. + * + * Primary template isn't defined on purpose. All the specializations give a + * compile-time error unless the template parameter is a function type. + * + * @tparam Type A valid function type. + * @tparam Allocator Type of allocator used to manage memory and elements. + */ +template +class sigh; + +/** + * @brief Unmanaged signal handler. + * + * It works directly with references to classes and pointers to member functions + * as well as pointers to free functions. Users of this class are in charge of + * disconnecting instances before deleting them. + * + * This class serves mainly two purposes: + * + * * Creating signals to use later to notify a bunch of listeners. + * * Collecting results from a set of functions like in a voting system. + * + * @tparam Ret Return type of a function type. + * @tparam Args Types of arguments of a function type. + * @tparam Allocator Type of allocator used to manage memory and elements. + */ +template +class sigh { + friend class sink>; + + using alloc_traits = stl::allocator_traits; + using delegate_type = delegate; + using container_type = stl::vector>; + +public: + /*! @brief Allocator type. */ + using allocator_type = Allocator; + /*! @brief Unsigned integer type. */ + using size_type = stl::size_t; + /*! @brief Sink type. */ + using sink_type = sink>; + + /*! @brief Default constructor. */ + sigh() noexcept(noexcept(allocator_type{})) + : sigh{allocator_type{}} {} + + /** + * @brief Constructs a signal handler with a given allocator. + * @param allocator The allocator to use. + */ + explicit sigh(const allocator_type &allocator) noexcept + : calls{allocator} {} + + /** + * @brief Copy constructor. + * @param other The instance to copy from. + */ + sigh(const sigh &other) + : calls{other.calls} {} + + /** + * @brief Allocator-extended copy constructor. + * @param other The instance to copy from. + * @param allocator The allocator to use. + */ + sigh(const sigh &other, const allocator_type &allocator) + : calls{other.calls, allocator} {} + + /** + * @brief Move constructor. + * @param other The instance to move from. + */ + sigh(sigh &&other) noexcept + : calls{stl::move(other.calls)} {} + + /** + * @brief Allocator-extended move constructor. + * @param other The instance to move from. + * @param allocator The allocator to use. + */ + sigh(sigh &&other, const allocator_type &allocator) + : calls{stl::move(other.calls), allocator} {} + + /*! @brief Default destructor. */ + ~sigh() = default; + + /** + * @brief Copy assignment operator. + * @param other The instance to copy from. + * @return This signal handler. + */ + sigh &operator=(const sigh &other) { + calls = other.calls; + return *this; + } + + /** + * @brief Move assignment operator. + * @param other The instance to move from. + * @return This signal handler. + */ + sigh &operator=(sigh &&other) noexcept { + swap(other); + return *this; + } + + /** + * @brief Exchanges the contents with those of a given signal handler. + * @param other Signal handler to exchange the content with. + */ + void swap(sigh &other) noexcept { + using stl::swap; + swap(calls, other.calls); + } + + /** + * @brief Returns the associated allocator. + * @return The associated allocator. + */ + [[nodiscard]] constexpr allocator_type get_allocator() const noexcept { + return calls.get_allocator(); + } + + /** + * @brief Number of listeners connected to the signal. + * @return Number of listeners currently connected. + */ + [[nodiscard]] size_type size() const noexcept { + return calls.size(); + } + + /** + * @brief Returns false if at least a listener is connected to the signal. + * @return True if the signal has no listeners connected, false otherwise. + */ + [[nodiscard]] bool empty() const noexcept { + return calls.empty(); + } + + /** + * @brief Triggers a signal. + * + * All the listeners are notified. Order isn't guaranteed. + * + * @param args Arguments to use to invoke listeners. + */ + void publish(Args... args) const { + for(auto pos = calls.size(); pos; --pos) { + calls[pos - 1u](args...); + } + } + + /** + * @brief Collects return values from the listeners. + * + * The collector must expose a call operator with the following properties: + * + * * The return type is either `void` or such that it's convertible to + * `bool`. In the second case, a true value will stop the iteration. + * * The list of parameters is empty if `Ret` is `void`, otherwise it + * contains a single element such that `Ret` is convertible to it. + * + * @tparam Func Type of collector to use, if any. + * @param func A valid function object. + * @param args Arguments to use to invoke listeners. + */ + template + void collect(Func func, Args... args) const { + for(auto pos = calls.size(); pos; --pos) { + if constexpr(stl::is_void_v || !stl::is_invocable_v) { + calls[pos - 1u](args...); + + if constexpr(stl::is_invocable_r_v) { + if(func()) { + break; + } + } else { + func(); + } + } else if constexpr(stl::is_invocable_r_v) { + if(func(calls[pos - 1u](args...))) { + break; + } + } else { + func(calls[pos - 1u](args...)); + } + } + } + +private: + container_type calls; +}; + +/** + * @brief Connection class. + * + * Opaque object the aim of which is to allow users to release an already + * established connection without having to keep a reference to the signal or + * the sink that generated it. + */ +class connection { + template + friend class sink; + + connection(delegate fn, void *ref) + : disconnect{fn}, signal{ref} {} + +public: + /*! @brief Default constructor. */ + connection() + : signal{} {} + + /** + * @brief Checks whether a connection is properly initialized. + * @return True if the connection is properly initialized, false otherwise. + */ + [[nodiscard]] explicit operator bool() const noexcept { + return static_cast(disconnect); + } + + /*! @brief Breaks the connection. */ + void release() { + if(disconnect) { + disconnect(signal); + disconnect.reset(); + } + } + +private: + delegate disconnect; + void *signal; +}; + +/** + * @brief Scoped connection class. + * + * Opaque object the aim of which is to allow users to release an already + * established connection without having to keep a reference to the signal or + * the sink that generated it.
+ * A scoped connection automatically breaks the link between the two objects + * when it goes out of scope. + */ +struct scoped_connection { + /*! @brief Default constructor. */ + scoped_connection() = default; + + /** + * @brief Constructs a scoped connection from a basic connection. + * @param other A valid connection object. + */ + scoped_connection(const connection &other) + : conn{other} {} + + /*! @brief Default copy constructor, deleted on purpose. */ + scoped_connection(const scoped_connection &) = delete; + + /** + * @brief Move constructor. + * @param other The scoped connection to move from. + */ + scoped_connection(scoped_connection &&other) noexcept + : conn{stl::exchange(other.conn, {})} {} + + /*! @brief Automatically breaks the link on destruction. */ + ~scoped_connection() { + conn.release(); + } + + /** + * @brief Default copy assignment operator, deleted on purpose. + * @return This scoped connection. + */ + scoped_connection &operator=(const scoped_connection &) = delete; + + /** + * @brief Move assignment operator. + * @param other The scoped connection to move from. + * @return This scoped connection. + */ + scoped_connection &operator=(scoped_connection &&other) noexcept { + conn = stl::exchange(other.conn, {}); + return *this; + } + + /** + * @brief Acquires a connection. + * @param other The connection object to acquire. + * @return This scoped connection. + */ + scoped_connection &operator=(connection other) { + conn = other; + return *this; + } + + /** + * @brief Checks whether a scoped connection is properly initialized. + * @return True if the connection is properly initialized, false otherwise. + */ + [[nodiscard]] explicit operator bool() const noexcept { + return static_cast(conn); + } + + /*! @brief Breaks the connection. */ + void release() { + conn.release(); + } + +private: + connection conn; +}; + +/** + * @brief Sink class. + * + * A sink is used to connect listeners to signals and to disconnect them.
+ * The function type for a listener is the one of the signal to which it + * belongs. + * + * The clear separation between a signal and a sink permits to store the former + * as private data member without exposing the publish functionality to the + * users of the class. + * + * @warning + * Lifetime of a sink must not overcome that of the signal to which it refers. + * In any other case, attempting to use a sink results in undefined behavior. + * + * @tparam Ret Return type of a function type. + * @tparam Args Types of arguments of a function type. + * @tparam Allocator Type of allocator used to manage memory and elements. + */ +template +class sink> { + using signal_type = sigh; + using delegate_type = signal_type::delegate_type; + using difference_type = signal_type::container_type::difference_type; + + template + static void release(Type value_or_instance, void *signal) { + sink{*static_cast(signal)}.disconnect(value_or_instance); + } + + template + static void release(void *signal) { + sink{*static_cast(signal)}.disconnect(); + } + + template + void disconnect_if(Func callback) { + auto &ref = signal_or_assert(); + + for(auto pos = ref.calls.size(); pos; --pos) { + if(auto &elem = ref.calls[pos - 1u]; callback(elem)) { + elem = stl::move(ref.calls.back()); + ref.calls.pop_back(); + } + } + } + + [[nodiscard]] auto &signal_or_assert() const noexcept { + ENTT_ASSERT(signal != nullptr, "Invalid pointer to signal"); + return *signal; + } + +public: + /*! @brief Constructs an invalid sink. */ + sink() noexcept + : signal{} {} + + /** + * @brief Constructs a sink that is allowed to modify a given signal. + * @param ref A valid reference to a signal object. + */ + sink(sigh &ref) noexcept + : signal{&ref} {} + + /** + * @brief Returns false if at least a listener is connected to the sink. + * @return True if the sink has no listeners connected, false otherwise. + */ + [[nodiscard]] bool empty() const noexcept { + return signal_or_assert().calls.empty(); + } + + /** + * @brief Connects a free function or an unbound member to a signal. + * @tparam Candidate Function or member to connect to the signal. + * @return A properly initialized connection object. + */ + template + connection connect() { + disconnect(); + + delegate_type call{}; + call.template connect(); + signal_or_assert().calls.push_back(stl::move(call)); + + delegate conn{}; + conn.template connect<&release>(); + return {conn, signal}; + } + + /** + * @brief Connects a free function with payload or a bound member to a + * signal. + * + * The signal isn't responsible for the connected object or the payload. + * Users must always guarantee that the lifetime of the instance overcomes + * the one of the signal.
+ * When used to connect a free function with payload, its signature must be + * such that the instance is the first argument before the ones used to + * define the signal itself. + * + * @tparam Candidate Function or member to connect to the signal. + * @tparam Type Type of class or type of payload. + * @param value_or_instance A valid reference that fits the purpose. + * @return A properly initialized connection object. + */ + template + connection connect(Type &value_or_instance) { + disconnect(value_or_instance); + + delegate_type call{}; + call.template connect(value_or_instance); + signal_or_assert().calls.push_back(stl::move(call)); + + delegate conn{}; + conn.template connect<&release>(value_or_instance); + return {conn, signal}; + } + + /** + * @brief Connects a free function with payload or a bound member to a + * signal. + * + * @sa connect(Type &) + * + * @tparam Candidate Function or member to connect to the signal. + * @tparam Type Type of class or type of payload. + * @param value_or_instance A valid pointer that fits the purpose. + * @return A properly initialized connection object. + */ + template + connection connect(Type *value_or_instance) { + disconnect(value_or_instance); + + delegate_type call{}; + call.template connect(value_or_instance); + signal_or_assert().calls.push_back(stl::move(call)); + + delegate conn{}; + conn.template connect<&release>(value_or_instance); + return {conn, signal}; + } + + /** + * @brief Disconnects a free function or an unbound member from a signal. + * @tparam Candidate Function or member to disconnect from the signal. + */ + template + void disconnect() { + delegate_type call{}; + call.template connect(); + disconnect_if([&call](const auto &elem) { return elem == call; }); + } + + /** + * @brief Disconnects a free function with payload or a bound member from a + * signal. + * + * The signal isn't responsible for the connected object or the payload. + * Users must always guarantee that the lifetime of the instance overcomes + * the one of the signal.
+ * When used to connect a free function with payload, its signature must be + * such that the instance is the first argument before the ones used to + * define the signal itself. + * + * @tparam Candidate Function or member to disconnect from the signal. + * @tparam Type Type of class or type of payload, if any. + * @param value_or_instance A valid reference that fits the purpose. + */ + template + void disconnect(Type &value_or_instance) { + delegate_type call{}; + call.template connect(value_or_instance); + disconnect_if([&call](const auto &elem) { return elem == call; }); + } + + /** + * @brief Disconnects a free function with payload or a bound member from a + * signal. + * + * @sa disconnect(Type &) + * + * @tparam Candidate Function or member to disconnect from the signal. + * @tparam Type Type of class or type of payload, if any. + * @param value_or_instance A valid pointer that fits the purpose. + */ + template + void disconnect(Type *value_or_instance) { + delegate_type call{}; + call.template connect(value_or_instance); + disconnect_if([&call](const auto &elem) { return elem == call; }); + } + + /** + * @brief Disconnects free functions with payload or bound members from a + * signal. + * @param value_or_instance A valid object that fits the purpose. + */ + void disconnect(const void *value_or_instance) { + ENTT_ASSERT(value_or_instance != nullptr, "Invalid value or instance"); + disconnect_if([value_or_instance](const auto &elem) { return elem.data() == value_or_instance; }); + } + + /*! @brief Disconnects all the listeners from a signal. */ + void disconnect() { + signal_or_assert().calls.clear(); + } + + /** + * @brief Returns true if a sink is correctly initialized, false otherwise. + * @return True if a sink is correctly initialized, false otherwise. + */ + [[nodiscard]] explicit operator bool() const noexcept { + return signal != nullptr; + } + +private: + signal_type *signal; +}; + +/** + * @brief Deduction guide. + * + * It allows to deduce the signal handler type of a sink directly from the + * signal it refers to. + * + * @tparam Ret Return type of a function type. + * @tparam Args Types of arguments of a function type. + * @tparam Allocator Type of allocator used to manage memory and elements. + */ +template +sink(sigh &) -> sink>; + +} // namespace entt + +#endif diff --git a/include/entt/stl/algorithm.hpp b/include/entt/stl/algorithm.hpp new file mode 100644 index 0000000..8fc29d9 --- /dev/null +++ b/include/entt/stl/algorithm.hpp @@ -0,0 +1,22 @@ +#ifndef ENTT_STL_ALGORITHM_HPP +#define ENTT_STL_ALGORITHM_HPP + +/*! @cond ENTT_INTERNAL */ +#if __has_include() +# include +#else +# include + +namespace entt::stl { + +using std::all_of; +using std::any_of; +using std::find_if; +using std::none_of; +using std::sort; + +} // namespace entt::stl +#endif +/*! @endcond */ + +#endif diff --git a/include/entt/stl/array.hpp b/include/entt/stl/array.hpp new file mode 100644 index 0000000..27137da --- /dev/null +++ b/include/entt/stl/array.hpp @@ -0,0 +1,19 @@ +#ifndef ENTT_STL_ARRAY_HPP +#define ENTT_STL_ARRAY_HPP + +/*! @cond ENTT_INTERNAL */ +#if __has_include() +# include +#else +# include + +namespace entt::stl { + +using std::array; +using std::get; + +} // namespace entt::stl +#endif +/*! @endcond */ + +#endif diff --git a/include/entt/stl/atomic.hpp b/include/entt/stl/atomic.hpp new file mode 100644 index 0000000..2e5b8d1 --- /dev/null +++ b/include/entt/stl/atomic.hpp @@ -0,0 +1,18 @@ +#ifndef ENTT_STL_ATOMIC_HPP +#define ENTT_STL_ATOMIC_HPP + +/*! @cond ENTT_INTERNAL */ +#if __has_include() +# include +#else +# include + +namespace entt::stl { + +using std::atomic; + +} // namespace entt::stl +#endif +/*! @endcond */ + +#endif diff --git a/include/entt/stl/bit.hpp b/include/entt/stl/bit.hpp new file mode 100644 index 0000000..3dca634 --- /dev/null +++ b/include/entt/stl/bit.hpp @@ -0,0 +1,20 @@ +#ifndef ENTT_STL_BIT_HPP +#define ENTT_STL_BIT_HPP + +/*! @cond ENTT_INTERNAL */ +#if __has_include() +# include +#else +# include + +namespace entt::stl { + +using std::bit_ceil; +using std::has_single_bit; +using std::popcount; + +} // namespace entt::stl +#endif +/*! @endcond */ + +#endif diff --git a/include/entt/stl/cmath.hpp b/include/entt/stl/cmath.hpp new file mode 100644 index 0000000..932ba0f --- /dev/null +++ b/include/entt/stl/cmath.hpp @@ -0,0 +1,18 @@ +#ifndef ENTT_STL_CMATH_HPP +#define ENTT_STL_CMATH_HPP + +/*! @cond ENTT_INTERNAL */ +#if __has_include() +# include +#else +# include + +namespace entt::stl { + +using std::ceil; + +} // namespace entt::stl +#endif +/*! @endcond */ + +#endif diff --git a/include/entt/stl/concepts.hpp b/include/entt/stl/concepts.hpp new file mode 100644 index 0000000..27d78a1 --- /dev/null +++ b/include/entt/stl/concepts.hpp @@ -0,0 +1,24 @@ +#ifndef ENTT_STL_CONCEPTS_HPP +#define ENTT_STL_CONCEPTS_HPP + +/*! @cond ENTT_INTERNAL */ +#if __has_include() +# include +#else +# include + +namespace entt::stl { + +using std::constructible_from; +using std::default_initializable; +using std::derived_from; +using std::integral; +using std::invocable; +using std::same_as; +using std::unsigned_integral; + +} // namespace entt::stl +#endif +/*! @endcond */ + +#endif diff --git a/include/entt/stl/cstddef.hpp b/include/entt/stl/cstddef.hpp new file mode 100644 index 0000000..512d233 --- /dev/null +++ b/include/entt/stl/cstddef.hpp @@ -0,0 +1,21 @@ +#ifndef ENTT_STL_CSTDDEF_HPP +#define ENTT_STL_CSTDDEF_HPP + +/*! @cond ENTT_INTERNAL */ +#if __has_include() +# include +#else +# include + +namespace entt::stl { + +using std::byte; +using std::nullptr_t; +using std::ptrdiff_t; +using std::size_t; + +} // namespace entt::stl +#endif +/*! @endcond */ + +#endif diff --git a/include/entt/stl/cstdint.hpp b/include/entt/stl/cstdint.hpp new file mode 100644 index 0000000..ff6dfcb --- /dev/null +++ b/include/entt/stl/cstdint.hpp @@ -0,0 +1,21 @@ +#ifndef ENTT_STL_CSTDINT_HPP +#define ENTT_STL_CSTDINT_HPP + +/*! @cond ENTT_INTERNAL */ +#if __has_include() +# include +#else +# include + +namespace entt::stl { + +using std::uint16_t; +using std::uint32_t; +using std::uint64_t; +using std::uint8_t; + +} // namespace entt::stl +#endif +/*! @endcond */ + +#endif diff --git a/include/entt/stl/functional.hpp b/include/entt/stl/functional.hpp new file mode 100644 index 0000000..0b6b50b --- /dev/null +++ b/include/entt/stl/functional.hpp @@ -0,0 +1,55 @@ +#ifndef ENTT_STL_FUNCTIONAL_HPP +#define ENTT_STL_FUNCTIONAL_HPP + +/*! @cond ENTT_INTERNAL */ +#if __has_include() +# include +#else +# include +# include +# include "../config/config.h" + +namespace entt::stl { + +using std::equal_to; +using std::function; +using std::hash; +using std::invoke; +using std::less; + +} // namespace entt::stl + +# ifndef ENTT_FORCE_STL +# if defined(__cpp_lib_ranges) +# define ENTT_HAS_IDENTITY +namespace entt::stl { + +using std::identity; + +} // namespace entt::stl +# endif +# endif + +# ifndef ENTT_HAS_IDENTITY +# include + +namespace entt::stl { + +struct identity { + using is_transparent = void; + + template + [[nodiscard]] constexpr Type &&operator()(Type &&value) const noexcept { + return std::forward(value); + } +}; + +} // namespace entt::stl +# endif + +#endif +/*! @endcond */ + +#undef ENTT_HAS_IDENTITY + +#endif diff --git a/include/entt/stl/ios.hpp b/include/entt/stl/ios.hpp new file mode 100644 index 0000000..e1b692e --- /dev/null +++ b/include/entt/stl/ios.hpp @@ -0,0 +1,18 @@ +#ifndef ENTT_STL_IOS_HPP +#define ENTT_STL_IOS_HPP + +/*! @cond ENTT_INTERNAL */ +#if __has_include() +# include +#else +# include + +namespace entt::stl { + +using std::boolalpha; + +} // namespace entt::stl +#endif +/*! @endcond */ + +#endif diff --git a/include/entt/stl/iterator.hpp b/include/entt/stl/iterator.hpp new file mode 100644 index 0000000..1723e03 --- /dev/null +++ b/include/entt/stl/iterator.hpp @@ -0,0 +1,110 @@ +#ifndef ENTT_STL_ITERATOR_HPP +#define ENTT_STL_ITERATOR_HPP + +/*! @cond ENTT_INTERNAL */ +#if __has_include() +# include +#else +# include +# include +# include "../config/config.h" + +namespace entt::stl { + +using std::advance; +using std::bidirectional_iterator_tag; +using std::distance; +using std::forward_iterator_tag; +using std::input_iterator_tag; +using std::iterator_traits; +using std::make_reverse_iterator; +using std::random_access_iterator_tag; +using std::reverse_iterator; + +# ifndef ENTT_FORCE_STL +# if defined(__cpp_lib_ranges) +# define ENTT_HAS_ITERATOR_CONCEPTS + +using std::bidirectional_iterator; +using std::forward_iterator; +using std::input_iterator; +using std::input_or_output_iterator; +using std::output_iterator; +using std::random_access_iterator; +using std::sentinel_for; + +# endif +# endif + +# ifndef ENTT_HAS_ITERATOR_CONCEPTS +# include +# include + +namespace internal { + +template +concept has_iterator_category = requires { + typename std::iterator_traits::iterator_category; +}; + +template +concept has_iterator_concept = has_iterator_category && requires { + typename It::iterator_concept; +}; + +template +struct iterator_tag { + using type = typename std::iterator_traits::iterator_category; +}; + +template +struct iterator_tag { + using type = typename It::iterator_concept; +}; + +template +concept has_iterator_tag = std::derived_from::type, Tag>; + +} // namespace internal + +// Bare minimum definitions to support broken platforms like PS4. +// EnTT does not provide full featured definitions for iterator concepts. + +template +concept input_or_output_iterator = requires(It it) { + *it; + { ++it } -> std::same_as; + it++; +}; + +template +concept input_iterator = input_or_output_iterator && internal::has_iterator_tag; + +template +concept output_iterator = input_or_output_iterator && requires(It it, Type &&value) { + *it++ = std::forward(value); +}; + +template +concept forward_iterator = input_iterator && internal::has_iterator_tag; + +template +concept bidirectional_iterator = forward_iterator && internal::has_iterator_tag; + +template +concept random_access_iterator = bidirectional_iterator && internal::has_iterator_tag; + +template +concept sentinel_for = input_or_output_iterator && requires(Sentinel sentinel, It it) { + { it == sentinel } -> std::same_as; +}; + +# endif + +} // namespace entt::stl +#endif +/*! @endcond */ + +#undef ENTT_HAS_ITERATOR_CONCEPTS + +#endif diff --git a/include/entt/stl/limits.hpp b/include/entt/stl/limits.hpp new file mode 100644 index 0000000..fca169b --- /dev/null +++ b/include/entt/stl/limits.hpp @@ -0,0 +1,18 @@ +#ifndef ENTT_STL_LIMITS_HPP +#define ENTT_STL_LIMITS_HPP + +/*! @cond ENTT_INTERNAL */ +#if __has_include() +# include +#else +# include + +namespace entt::stl { + +using std::numeric_limits; + +} // namespace entt::stl +#endif +/*! @endcond */ + +#endif diff --git a/include/entt/stl/memory.hpp b/include/entt/stl/memory.hpp new file mode 100644 index 0000000..e75fcab --- /dev/null +++ b/include/entt/stl/memory.hpp @@ -0,0 +1,74 @@ +#ifndef ENTT_STL_MEMORY_HPP +#define ENTT_STL_MEMORY_HPP + +/*! @cond ENTT_INTERNAL */ +#if __has_include() +# include +#else +# include +# include +# include "../config/config.h" + +namespace entt::stl { + +using std::addressof; +using std::allocate_shared; +using std::allocator; +using std::allocator_arg; +using std::allocator_arg_t; +using std::allocator_traits; +using std::default_delete; +using std::destroy; +using std::enable_shared_from_this; +using std::make_shared; +using std::make_unique; +using std::pointer_traits; +using std::shared_ptr; +using std::static_pointer_cast; +using std::uninitialized_fill; +using std::unique_ptr; +using std::uses_allocator_v; + +} // namespace entt::stl + +# ifndef ENTT_FORCE_STL +# if defined(__cpp_lib_to_address) +# define ENTT_HAS_TO_ADDRESS + +namespace entt::stl { + +using std::to_address; + +} // namespace entt::stl + +# endif +# endif + +# ifndef ENTT_HAS_TO_ADDRESS +# include + +namespace entt::stl { + +template +constexpr Type *to_address(Type *ptr) noexcept { + static_assert(!std::is_function_v, "Invalid type"); + return ptr; +} + +template +constexpr auto to_address(const Type &ptr) noexcept { + if constexpr(requires { std::pointer_traits::to_address(ptr); }) { + return std::pointer_traits::to_address(ptr); + } else { + return to_address(ptr.operator->()); + } +} + +} // namespace entt::stl +# endif +#endif +/*! @endcond */ + +#undef ENTT_HAS_TO_ADDRESS + +#endif diff --git a/include/entt/stl/ostream.hpp b/include/entt/stl/ostream.hpp new file mode 100644 index 0000000..f185c9e --- /dev/null +++ b/include/entt/stl/ostream.hpp @@ -0,0 +1,18 @@ +#ifndef ENTT_STL_OSTREAM_HPP +#define ENTT_STL_OSTREAM_HPP + +/*! @cond ENTT_INTERNAL */ +#if __has_include() +# include +#else +# include + +namespace entt::stl { + +using std::ostream; + +} // namespace entt::stl +#endif +/*! @endcond */ + +#endif diff --git a/include/entt/stl/sstream.hpp b/include/entt/stl/sstream.hpp new file mode 100644 index 0000000..0c5c4b4 --- /dev/null +++ b/include/entt/stl/sstream.hpp @@ -0,0 +1,18 @@ +#ifndef ENTT_STL_SSTREAM_HPP +#define ENTT_STL_SSTREAM_HPP + +/*! @cond ENTT_INTERNAL */ +#if __has_include() +# include +#else +# include + +namespace entt::stl { + +using std::stringstream; + +} // namespace entt::stl +#endif +/*! @endcond */ + +#endif diff --git a/include/entt/stl/string.hpp b/include/entt/stl/string.hpp new file mode 100644 index 0000000..a0cc88f --- /dev/null +++ b/include/entt/stl/string.hpp @@ -0,0 +1,18 @@ +#ifndef ENTT_STL_STRING_HPP +#define ENTT_STL_STRING_HPP + +/*! @cond ENTT_INTERNAL */ +#if __has_include() +# include +#else +# include + +namespace entt::stl { + +using std::string; + +} // namespace entt::stl +#endif +/*! @endcond */ + +#endif diff --git a/include/entt/stl/string_view.hpp b/include/entt/stl/string_view.hpp new file mode 100644 index 0000000..bfe8956 --- /dev/null +++ b/include/entt/stl/string_view.hpp @@ -0,0 +1,19 @@ +#ifndef ENTT_STL_STRING_VIEW_HPP +#define ENTT_STL_STRING_VIEW_HPP + +/*! @cond ENTT_INTERNAL */ +#if __has_include() +# include +#else +# include + +namespace entt::stl { + +using std::string_view; +using std::swap; + +} // namespace entt::stl +#endif +/*! @endcond */ + +#endif diff --git a/include/entt/stl/tuple.hpp b/include/entt/stl/tuple.hpp new file mode 100644 index 0000000..fa085cd --- /dev/null +++ b/include/entt/stl/tuple.hpp @@ -0,0 +1,28 @@ +#ifndef ENTT_STL_TUPLE_HPP +#define ENTT_STL_TUPLE_HPP + +/*! @cond ENTT_INTERNAL */ +#if __has_include() +# include +#else +# include + +namespace entt::stl { + +using std::apply; +using std::forward_as_tuple; +using std::get; +using std::make_from_tuple; +using std::make_tuple; +using std::tuple; +using std::tuple_cat; +using std::tuple_element; +using std::tuple_element_t; +using std::tuple_size; +using std::tuple_size_v; + +} // namespace entt::stl +#endif +/*! @endcond */ + +#endif diff --git a/include/entt/stl/type_traits.hpp b/include/entt/stl/type_traits.hpp new file mode 100644 index 0000000..882f627 --- /dev/null +++ b/include/entt/stl/type_traits.hpp @@ -0,0 +1,70 @@ +#ifndef ENTT_STL_TYPE_TRAITS_HPP +#define ENTT_STL_TYPE_TRAITS_HPP + +/*! @cond ENTT_INTERNAL */ +#if __has_include() +# include +#else +# include + +namespace entt::stl { + +using std::bool_constant; +using std::common_type_t; +using std::conditional_t; +using std::decay_t; +using std::extent_v; +using std::false_type; +using std::integral_constant; +using std::invoke_result_t; +using std::is_aggregate_v; +using std::is_arithmetic_v; +using std::is_array_v; +using std::is_base_of_v; +using std::is_class_v; +using std::is_const_v; +using std::is_constructible_v; +using std::is_convertible_v; +using std::is_copy_assignable_v; +using std::is_copy_constructible_v; +using std::is_default_constructible_v; +using std::is_empty_v; +using std::is_enum_v; +using std::is_final_v; +using std::is_function_v; +using std::is_integral_v; +using std::is_invocable; +using std::is_invocable_r; +using std::is_invocable_r_v; +using std::is_invocable_v; +using std::is_lvalue_reference_v; +using std::is_member_function_pointer_v; +using std::is_member_object_pointer_v; +using std::is_member_pointer_v; +using std::is_move_assignable_v; +using std::is_move_constructible_v; +using std::is_nothrow_constructible_v; +using std::is_nothrow_copy_constructible_v; +using std::is_nothrow_default_constructible_v; +using std::is_nothrow_destructible_v; +using std::is_nothrow_invocable_v; +using std::is_nothrow_move_constructible_v; +using std::is_pointer_v; +using std::is_reference_v; +using std::is_same_v; +using std::is_signed_v; +using std::is_trivially_destructible_v; +using std::is_void_v; +using std::remove_const_t; +using std::remove_cvref_t; +using std::remove_pointer_t; +using std::remove_reference_t; +using std::true_type; +using std::type_identity; +using std::underlying_type_t; + +} // namespace entt::stl +#endif +/*! @endcond */ + +#endif diff --git a/include/entt/stl/utility.hpp b/include/entt/stl/utility.hpp new file mode 100644 index 0000000..2450d1b --- /dev/null +++ b/include/entt/stl/utility.hpp @@ -0,0 +1,34 @@ +#ifndef ENTT_STL_UTILITY_HPP +#define ENTT_STL_UTILITY_HPP + +/*! @cond ENTT_INTERNAL */ +#if __has_include() +# include +#else +# include + +namespace entt::stl { + +using std::as_const; +using std::declval; +using std::exchange; +using std::forward; +using std::in_place; +using std::in_place_t; +using std::in_place_type; +using std::in_place_type_t; +using std::index_sequence; +using std::index_sequence_for; +using std::make_index_sequence; +using std::make_pair; +using std::move; +using std::pair; +using std::piecewise_construct; +using std::piecewise_construct_t; +using std::swap; + +} // namespace entt::stl +#endif +/*! @endcond */ + +#endif diff --git a/include/entt/stl/vector.hpp b/include/entt/stl/vector.hpp new file mode 100644 index 0000000..f5200d3 --- /dev/null +++ b/include/entt/stl/vector.hpp @@ -0,0 +1,18 @@ +#ifndef ENTT_STL_VECTOR_HPP +#define ENTT_STL_VECTOR_HPP + +/*! @cond ENTT_INTERNAL */ +#if __has_include() +# include +#else +# include + +namespace entt::stl { + +using std::vector; + +} // namespace entt::stl +#endif +/*! @endcond */ + +#endif diff --git a/include/entt/tools.hpp b/include/entt/tools.hpp new file mode 100644 index 0000000..66e9813 --- /dev/null +++ b/include/entt/tools.hpp @@ -0,0 +1,3 @@ +// IWYU pragma: begin_exports +#include "tools/davey.hpp" +// IWYU pragma: end_exports diff --git a/include/entt/tools/davey.hpp b/include/entt/tools/davey.hpp new file mode 100644 index 0000000..c305613 --- /dev/null +++ b/include/entt/tools/davey.hpp @@ -0,0 +1,343 @@ +#ifndef ENTT_TOOLS_DAVEY_HPP +#define ENTT_TOOLS_DAVEY_HPP + +#include "../config/config.h" +#include "../entity/mixin.hpp" +#include "../entity/registry.hpp" +#include "../entity/sparse_set.hpp" +#include "../entity/storage.hpp" +#include "../locator/locator.hpp" +#include "../meta/container.hpp" +#include "../meta/context.hpp" +#include "../meta/meta.hpp" +#include "../meta/pointer.hpp" +#include "../meta/resolve.hpp" +#include "../stl/cstdint.hpp" +#include "../stl/ios.hpp" +#include "../stl/sstream.hpp" +#include "../stl/string.hpp" + +#if __has_include() +# include +#endif + +namespace entt { + +/*! @cond ENTT_INTERNAL */ +namespace internal { + +template +static void present_element(const meta_any &obj, OnEntity on_entity) { + for([[maybe_unused]] const auto [id, data]: obj.type().data()) { + const auto elem = data.get(obj); + const stl::string name = data.name().empty() ? stl::string{data.type().info().name()} : stl::string{data.name()}; + const char *const label = name.c_str(); + + if(auto type = data.type(); type.info() == type_id()) { + ImGui::Text("%s: %s", label, elem.template cast()); + } else if(type.info() == type_id()) { + ImGui::Text("%s: %s", label, elem.template cast().data()); + } else if(type.info() == type_id()) { + if(const auto entt = elem.template cast(); entt == null) { + ImGui::Text("%s: %s", label, "null"); + } else { + on_entity(label, entt); + } + } else if(type.is_enum()) { + const char *as_string = nullptr; + + for(auto [id, curr]: type.data()) { + if(curr.get({}) == elem) { + as_string = curr.name().data(); + break; + } + } + + if(as_string) { + ImGui::Text("%s: %s", label, as_string); + } else { + ImGui::Text("%s: %zu", label, elem.template allow_cast().template cast()); + } + } else if(type.is_arithmetic()) { + if(type.info() == type_id()) { + stl::stringstream buffer{}; + buffer << stl::boolalpha << elem.template cast(); + ImGui::Text("%s: %s", label, buffer.str().data()); + } else if(type.info() == type_id()) { + ImGui::Text("%s: %c", label, elem.template cast()); + } else if(type.is_integral()) { + ImGui::Text("%s: %zu", label, elem.template allow_cast().template cast()); + } else { + ImGui::Text("%s: %f", label, elem.template allow_cast().template cast()); + } + } else if(type.is_pointer_like()) { + if(auto deref = *obj; deref) { + if(ImGui::TreeNode(label)) { + present_element(*obj, on_entity); + ImGui::TreePop(); + } + } else { + ImGui::Text("%s: %s", label, "null"); + } + } else if(type.is_sequence_container()) { + if(ImGui::TreeNode(label)) { + meta_sequence_container view = elem.as_sequence_container(); + + for(stl::size_t pos{}, last = view.size(); pos < last; ++pos) { + ImGui::PushID(static_cast(pos)); + + if(ImGui::TreeNode(label, "%zu", pos)) { + present_element(view[pos], on_entity); + ImGui::TreePop(); + } + + ImGui::PopID(); + } + + ImGui::TreePop(); + } + } else if(type.is_associative_container()) { + if(ImGui::TreeNode(label)) { + meta_associative_container view = elem.as_associative_container(); + auto it = view.begin(); + + for(stl::size_t pos{}, last = view.size(); pos < last; ++pos, ++it) { + ImGui::PushID(static_cast(pos)); + + if(ImGui::TreeNode(label, "%zu", pos)) { + const auto [key, value] = *it; + + if(ImGui::TreeNode("key")) { + present_element(key, on_entity); + ImGui::TreePop(); + } + + if(ImGui::TreeNode("value")) { + present_element(value, on_entity); + ImGui::TreePop(); + } + + ImGui::TreePop(); + } + + ImGui::PopID(); + } + + ImGui::TreePop(); + } + } else if(type.is_class()) { + if(ImGui::TreeNode(label)) { + present_element(elem, on_entity); + ImGui::TreePop(); + } + } else { + const stl::string underlying_type{data.type().info().name()}; + ImGui::Text("%s: %s", label, underlying_type.data()); + } + } + + for([[maybe_unused]] const auto [id, base]: obj.type().base()) { + present_element(obj.allow_cast(base.type()), on_entity); + } +} + +template +static void present_storage(const meta_ctx &ctx, const basic_sparse_set &storage) { + if(auto type = resolve(ctx, storage.info()); type) { + for(auto entt: storage) { + ImGui::PushID(static_cast(to_entity(entt))); + + if(ImGui::TreeNode(&storage.info(), "%d [%d/%d]", to_integral(entt), to_entity(entt), to_version(entt))) { + if(const auto obj = type.from_void(storage.value(entt)); obj) { + present_element::entity_type>(obj, [](const char *name, const Entity entt) { + ImGui::Text("%s: %d [%d/%d]", name, to_integral(entt), to_entity(entt), to_version(entt)); + }); + } + + ImGui::TreePop(); + } + + ImGui::PopID(); + } + } else { + for(auto entt: storage) { + ImGui::Text("%d [%d/%d]", to_integral(entt), to_entity(entt), to_version(entt)); + } + } +} + +template +static void present_entity(const meta_ctx &ctx, const Entity entt, const It from, const It to) { + for(auto it = from; it != to; ++it) { + if(const auto &storage = it->second; storage.contains(entt)) { + if(auto type = resolve(ctx, storage.info()); type) { + const stl::string name = type.name().empty() ? stl::string{storage.info().name()} : stl::string{type.name()}; + const char *const label = name.c_str(); + + if(ImGui::TreeNode(&storage.info(), "%s", label)) { + if(const auto obj = type.from_void(storage.value(entt)); obj) { + present_element(obj, [&ctx, from, to](const char *name, const Entity other) { + if(ImGui::TreeNode(name, "%s: %d [%d/%d]", name, to_integral(other), to_entity(other), to_version(other))) { + present_entity(ctx, other, from, to); + ImGui::TreePop(); + } + }); + } + + ImGui::TreePop(); + } + } else { + const stl::string name{storage.info().name()}; + ImGui::Text("%s", name.data()); + } + } + } +} + +template +static void present_view(const meta_ctx &ctx, const basic_view, exclude_t> &view, stl::index_sequence) { + using view_type = basic_view, exclude_t>; + const stl::array range{view.template storage()...}; + + for(auto tup: view.each()) { + const auto entt = stl::get<0>(tup); + ImGui::PushID(static_cast(to_entity(entt))); + + if(ImGui::TreeNode(&type_id(), "%d [%d/%d]", to_integral(entt), to_entity(entt), to_version(entt))) { + for(const auto *storage: range) { + if(auto type = resolve(ctx, storage->info()); type) { + const stl::string name = type.name().empty() ? stl::string{storage->info().name()} : stl::string{type.name()}; + const char *const label = name.c_str(); + + if(ImGui::TreeNode(&storage->info(), "%s", label)) { + if(const auto obj = type.from_void(storage->value(entt)); obj) { + present_element(obj, [](const char *name, const view_type::entity_type entt) { + ImGui::Text("%s: %d [%d/%d]", name, to_integral(entt), to_entity(entt), to_version(entt)); + }); + } + + ImGui::TreePop(); + } + } else { + const stl::string name{storage->info().name()}; + ImGui::Text("%s", name.data()); + } + } + + ImGui::TreePop(); + } + + ImGui::PopID(); + } +} + +} // namespace internal +/*! @endcond */ + +/** + * @brief ImGui-based introspection tool for storage types. + * @tparam Type Storage element type. + * @tparam Entity Storage entity type. + * @tparam Allocator Storage allocator type. + * @param ctx The context from which to search for meta types. + * @param storage An instance of the storage type. + */ +template +void davey(const meta_ctx &ctx, const basic_storage &storage) { + internal::present_storage(ctx, storage); +} + +/** + * @brief ImGui-based introspection tool for storage types. + * @tparam Type Storage element type. + * @tparam Entity Storage entity type. + * @tparam Allocator Storage allocator type. + * @param storage An instance of the storage type. + */ +template +void davey(const basic_storage &storage) { + davey(locator::value_or(), storage); +} + +/** + * @brief ImGui-based introspection tool for view types. + * @tparam Get Types of storage iterated by the view. + * @tparam Exclude Types of storage used to filter the view. + * @param ctx The context from which to search for meta types. + * @param view An instance of the view type. + */ +template +void davey(const meta_ctx &ctx, const basic_view, exclude_t> &view) { + internal::present_view(ctx, view, stl::index_sequence_for{}); +} + +/** + * @brief ImGui-based introspection tool for view types. + * @tparam Get Types of storage iterated by the view. + * @tparam Exclude Types of storage used to filter the view. + * @param view An instance of the view type. + */ +template +void davey(const basic_view, exclude_t> &view) { + davey(locator::value_or(), view); +} + +/** + * @brief ImGui-based introspection tool for registry types. + * @tparam Entity Registry entity type. + * @tparam Allocator Registry allocator type. + * @param ctx The context from which to search for meta types. + * @param registry An instance of the registry type. + */ +template +void davey(const meta_ctx &ctx, const basic_registry ®istry) { + ImGui::BeginTabBar("#tabs"); + + if(ImGui::BeginTabItem("Entity")) { + for(const auto [entt]: registry.template storage()->each()) { + ImGui::PushID(static_cast(to_entity(entt))); + + if(ImGui::TreeNode(&type_id(), "%d [%d/%d]", to_integral(entt), to_entity(entt), to_version(entt))) { + const auto range = registry.storage(); + internal::present_entity(ctx, entt, range.begin(), range.end()); + ImGui::TreePop(); + } + + ImGui::PopID(); + } + + ImGui::EndTabItem(); + } + + if(ImGui::BeginTabItem("Storage")) { + for([[maybe_unused]] auto [id, storage]: registry.storage()) { + const auto type = resolve(ctx, storage.info()); + const stl::string name = type.name().empty() ? stl::string{storage.info().name()} : stl::string{type.name()}; + const char *const label = name.c_str(); + + if(ImGui::TreeNode(&storage.info(), "%s (%zu)", label, storage.size())) { + internal::present_storage(ctx, storage); + ImGui::TreePop(); + } + } + + ImGui::EndTabItem(); + } + + ImGui::EndTabBar(); +} + +/** + * @brief ImGui-based introspection tool for registry types. + * @tparam Entity Registry entity type. + * @tparam Allocator Registry allocator type. + * @param registry An instance of the registry type. + */ +template +void davey(const basic_registry ®istry) { + davey(locator::value_or(), registry); +} + +} // namespace entt + +#endif diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp deleted file mode 100644 index 82d69f7..0000000 --- a/include/nlohmann/json.hpp +++ /dev/null @@ -1,25526 +0,0 @@ -// __ _____ _____ _____ -// __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.12.0 -// |_____|_____|_____|_|___| https://github.com/nlohmann/json -// -// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann -// SPDX-License-Identifier: MIT - -/****************************************************************************\ - * Note on documentation: The source files contain links to the online * - * documentation of the public API at https://json.nlohmann.me. This URL * - * contains the most recent documentation and should also be applicable to * - * previous versions; documentation for deprecated functions is not * - * removed, but marked deprecated. See "Generate documentation" section in * - * file docs/README.md. * -\****************************************************************************/ - -#ifndef INCLUDE_NLOHMANN_JSON_HPP_ -#define INCLUDE_NLOHMANN_JSON_HPP_ - -#include // all_of, find, for_each -#include // nullptr_t, ptrdiff_t, size_t -#include // hash, less -#include // initializer_list -#ifndef JSON_NO_IO - #include // istream, ostream -#endif // JSON_NO_IO -#include // random_access_iterator_tag -#include // unique_ptr -#include // string, stoi, to_string -#include // declval, forward, move, pair, swap -#include // vector - -// #include -// __ _____ _____ _____ -// __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.12.0 -// |_____|_____|_____|_|___| https://github.com/nlohmann/json -// -// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann -// SPDX-License-Identifier: MIT - - - -#include - -// #include -// __ _____ _____ _____ -// __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.12.0 -// |_____|_____|_____|_|___| https://github.com/nlohmann/json -// -// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann -// SPDX-License-Identifier: MIT - - - -// This file contains all macro definitions affecting or depending on the ABI - -#ifndef JSON_SKIP_LIBRARY_VERSION_CHECK - #if defined(NLOHMANN_JSON_VERSION_MAJOR) && defined(NLOHMANN_JSON_VERSION_MINOR) && defined(NLOHMANN_JSON_VERSION_PATCH) - #if NLOHMANN_JSON_VERSION_MAJOR != 3 || NLOHMANN_JSON_VERSION_MINOR != 12 || NLOHMANN_JSON_VERSION_PATCH != 0 - #warning "Already included a different version of the library!" - #endif - #endif -#endif - -#define NLOHMANN_JSON_VERSION_MAJOR 3 // NOLINT(modernize-macro-to-enum) -#define NLOHMANN_JSON_VERSION_MINOR 12 // NOLINT(modernize-macro-to-enum) -#define NLOHMANN_JSON_VERSION_PATCH 0 // NOLINT(modernize-macro-to-enum) - -#ifndef JSON_DIAGNOSTICS - #define JSON_DIAGNOSTICS 0 -#endif - -#ifndef JSON_DIAGNOSTIC_POSITIONS - #define JSON_DIAGNOSTIC_POSITIONS 0 -#endif - -#ifndef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON - #define JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 0 -#endif - -#if JSON_DIAGNOSTICS - #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag -#else - #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS -#endif - -#if JSON_DIAGNOSTIC_POSITIONS - #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS _dp -#else - #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS -#endif - -#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON - #define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON _ldvcmp -#else - #define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON -#endif - -#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION - #define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0 -#endif - -// Construct the namespace ABI tags component -#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c) json_abi ## a ## b ## c -#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c) \ - NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c) - -#define NLOHMANN_JSON_ABI_TAGS \ - NLOHMANN_JSON_ABI_TAGS_CONCAT( \ - NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS, \ - NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON, \ - NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS) - -// Construct the namespace version component -#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \ - _v ## major ## _ ## minor ## _ ## patch -#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(major, minor, patch) \ - NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) - -#if NLOHMANN_JSON_NAMESPACE_NO_VERSION -#define NLOHMANN_JSON_NAMESPACE_VERSION -#else -#define NLOHMANN_JSON_NAMESPACE_VERSION \ - NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(NLOHMANN_JSON_VERSION_MAJOR, \ - NLOHMANN_JSON_VERSION_MINOR, \ - NLOHMANN_JSON_VERSION_PATCH) -#endif - -// Combine namespace components -#define NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b) a ## b -#define NLOHMANN_JSON_NAMESPACE_CONCAT(a, b) \ - NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b) - -#ifndef NLOHMANN_JSON_NAMESPACE -#define NLOHMANN_JSON_NAMESPACE \ - nlohmann::NLOHMANN_JSON_NAMESPACE_CONCAT( \ - NLOHMANN_JSON_ABI_TAGS, \ - NLOHMANN_JSON_NAMESPACE_VERSION) -#endif - -#ifndef NLOHMANN_JSON_NAMESPACE_BEGIN -#define NLOHMANN_JSON_NAMESPACE_BEGIN \ - namespace nlohmann \ - { \ - inline namespace NLOHMANN_JSON_NAMESPACE_CONCAT( \ - NLOHMANN_JSON_ABI_TAGS, \ - NLOHMANN_JSON_NAMESPACE_VERSION) \ - { -#endif - -#ifndef NLOHMANN_JSON_NAMESPACE_END -#define NLOHMANN_JSON_NAMESPACE_END \ - } /* namespace (inline namespace) NOLINT(readability/namespace) */ \ - } // namespace nlohmann -#endif - -// #include -// __ _____ _____ _____ -// __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.12.0 -// |_____|_____|_____|_|___| https://github.com/nlohmann/json -// -// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann -// SPDX-License-Identifier: MIT - - - -#include // transform -#include // array -#include // forward_list -#include // inserter, front_inserter, end -#include // map -#ifdef JSON_HAS_CPP_17 - #include // optional -#endif -#include // string -#include // tuple, make_tuple -#include // is_arithmetic, is_same, is_enum, underlying_type, is_convertible -#include // unordered_map -#include // pair, declval -#include // valarray - -// #include -// __ _____ _____ _____ -// __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.12.0 -// |_____|_____|_____|_|___| https://github.com/nlohmann/json -// -// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann -// SPDX-License-Identifier: MIT - - - -#include // nullptr_t -#include // exception -#if JSON_DIAGNOSTICS - #include // accumulate -#endif -#include // runtime_error -#include // to_string -#include // vector - -// #include -// __ _____ _____ _____ -// __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.12.0 -// |_____|_____|_____|_|___| https://github.com/nlohmann/json -// -// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann -// SPDX-License-Identifier: MIT - - - -#include // array -#include // size_t -#include // uint8_t -#include // string - -// #include -// __ _____ _____ _____ -// __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.12.0 -// |_____|_____|_____|_|___| https://github.com/nlohmann/json -// -// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann -// SPDX-License-Identifier: MIT - - - -#include // declval, pair -// #include -// __ _____ _____ _____ -// __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.12.0 -// |_____|_____|_____|_|___| https://github.com/nlohmann/json -// -// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann -// SPDX-License-Identifier: MIT - - - -#include - -// #include -// __ _____ _____ _____ -// __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.12.0 -// |_____|_____|_____|_|___| https://github.com/nlohmann/json -// -// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann -// SPDX-License-Identifier: MIT - - - -// #include - - -NLOHMANN_JSON_NAMESPACE_BEGIN -namespace detail -{ - -template struct make_void -{ - using type = void; -}; -template using void_t = typename make_void::type; - -} // namespace detail -NLOHMANN_JSON_NAMESPACE_END - - -NLOHMANN_JSON_NAMESPACE_BEGIN -namespace detail -{ - -// https://en.cppreference.com/w/cpp/experimental/is_detected -struct nonesuch -{ - nonesuch() = delete; - ~nonesuch() = delete; - nonesuch(nonesuch const&) = delete; - nonesuch(nonesuch const&&) = delete; - void operator=(nonesuch const&) = delete; - void operator=(nonesuch&&) = delete; -}; - -template class Op, - class... Args> -struct detector -{ - using value_t = std::false_type; - using type = Default; -}; - -template class Op, class... Args> -struct detector>, Op, Args...> -{ - using value_t = std::true_type; - using type = Op; -}; - -template class Op, class... Args> -using is_detected = typename detector::value_t; - -template class Op, class... Args> -struct is_detected_lazy : is_detected { }; - -template class Op, class... Args> -using detected_t = typename detector::type; - -template class Op, class... Args> -using detected_or = detector; - -template class Op, class... Args> -using detected_or_t = typename detected_or::type; - -template class Op, class... Args> -using is_detected_exact = std::is_same>; - -template class Op, class... Args> -using is_detected_convertible = - std::is_convertible, To>; - -} // namespace detail -NLOHMANN_JSON_NAMESPACE_END - -// #include - - -// __ _____ _____ _____ -// __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.12.0 -// |_____|_____|_____|_|___| https://github.com/nlohmann/json -// -// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann -// SPDX-FileCopyrightText: 2016 - 2021 Evan Nemerson -// SPDX-License-Identifier: MIT - -/* Hedley - https://nemequ.github.io/hedley - * Created by Evan Nemerson - */ - -#if !defined(JSON_HEDLEY_VERSION) || (JSON_HEDLEY_VERSION < 15) -#if defined(JSON_HEDLEY_VERSION) - #undef JSON_HEDLEY_VERSION -#endif -#define JSON_HEDLEY_VERSION 15 - -#if defined(JSON_HEDLEY_STRINGIFY_EX) - #undef JSON_HEDLEY_STRINGIFY_EX -#endif -#define JSON_HEDLEY_STRINGIFY_EX(x) #x - -#if defined(JSON_HEDLEY_STRINGIFY) - #undef JSON_HEDLEY_STRINGIFY -#endif -#define JSON_HEDLEY_STRINGIFY(x) JSON_HEDLEY_STRINGIFY_EX(x) - -#if defined(JSON_HEDLEY_CONCAT_EX) - #undef JSON_HEDLEY_CONCAT_EX -#endif -#define JSON_HEDLEY_CONCAT_EX(a,b) a##b - -#if defined(JSON_HEDLEY_CONCAT) - #undef JSON_HEDLEY_CONCAT -#endif -#define JSON_HEDLEY_CONCAT(a,b) JSON_HEDLEY_CONCAT_EX(a,b) - -#if defined(JSON_HEDLEY_CONCAT3_EX) - #undef JSON_HEDLEY_CONCAT3_EX -#endif -#define JSON_HEDLEY_CONCAT3_EX(a,b,c) a##b##c - -#if defined(JSON_HEDLEY_CONCAT3) - #undef JSON_HEDLEY_CONCAT3 -#endif -#define JSON_HEDLEY_CONCAT3(a,b,c) JSON_HEDLEY_CONCAT3_EX(a,b,c) - -#if defined(JSON_HEDLEY_VERSION_ENCODE) - #undef JSON_HEDLEY_VERSION_ENCODE -#endif -#define JSON_HEDLEY_VERSION_ENCODE(major,minor,revision) (((major) * 1000000) + ((minor) * 1000) + (revision)) - -#if defined(JSON_HEDLEY_VERSION_DECODE_MAJOR) - #undef JSON_HEDLEY_VERSION_DECODE_MAJOR -#endif -#define JSON_HEDLEY_VERSION_DECODE_MAJOR(version) ((version) / 1000000) - -#if defined(JSON_HEDLEY_VERSION_DECODE_MINOR) - #undef JSON_HEDLEY_VERSION_DECODE_MINOR -#endif -#define JSON_HEDLEY_VERSION_DECODE_MINOR(version) (((version) % 1000000) / 1000) - -#if defined(JSON_HEDLEY_VERSION_DECODE_REVISION) - #undef JSON_HEDLEY_VERSION_DECODE_REVISION -#endif -#define JSON_HEDLEY_VERSION_DECODE_REVISION(version) ((version) % 1000) - -#if defined(JSON_HEDLEY_GNUC_VERSION) - #undef JSON_HEDLEY_GNUC_VERSION -#endif -#if defined(__GNUC__) && defined(__GNUC_PATCHLEVEL__) - #define JSON_HEDLEY_GNUC_VERSION JSON_HEDLEY_VERSION_ENCODE(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) -#elif defined(__GNUC__) - #define JSON_HEDLEY_GNUC_VERSION JSON_HEDLEY_VERSION_ENCODE(__GNUC__, __GNUC_MINOR__, 0) -#endif - -#if defined(JSON_HEDLEY_GNUC_VERSION_CHECK) - #undef JSON_HEDLEY_GNUC_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_GNUC_VERSION) - #define JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_GNUC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_MSVC_VERSION) - #undef JSON_HEDLEY_MSVC_VERSION -#endif -#if defined(_MSC_FULL_VER) && (_MSC_FULL_VER >= 140000000) && !defined(__ICL) - #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_FULL_VER / 10000000, (_MSC_FULL_VER % 10000000) / 100000, (_MSC_FULL_VER % 100000) / 100) -#elif defined(_MSC_FULL_VER) && !defined(__ICL) - #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_FULL_VER / 1000000, (_MSC_FULL_VER % 1000000) / 10000, (_MSC_FULL_VER % 10000) / 10) -#elif defined(_MSC_VER) && !defined(__ICL) - #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_VER / 100, _MSC_VER % 100, 0) -#endif - -#if defined(JSON_HEDLEY_MSVC_VERSION_CHECK) - #undef JSON_HEDLEY_MSVC_VERSION_CHECK -#endif -#if !defined(JSON_HEDLEY_MSVC_VERSION) - #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (0) -#elif defined(_MSC_VER) && (_MSC_VER >= 1400) - #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_FULL_VER >= ((major * 10000000) + (minor * 100000) + (patch))) -#elif defined(_MSC_VER) && (_MSC_VER >= 1200) - #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_FULL_VER >= ((major * 1000000) + (minor * 10000) + (patch))) -#else - #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_VER >= ((major * 100) + (minor))) -#endif - -#if defined(JSON_HEDLEY_INTEL_VERSION) - #undef JSON_HEDLEY_INTEL_VERSION -#endif -#if defined(__INTEL_COMPILER) && defined(__INTEL_COMPILER_UPDATE) && !defined(__ICL) - #define JSON_HEDLEY_INTEL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, __INTEL_COMPILER_UPDATE) -#elif defined(__INTEL_COMPILER) && !defined(__ICL) - #define JSON_HEDLEY_INTEL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, 0) -#endif - -#if defined(JSON_HEDLEY_INTEL_VERSION_CHECK) - #undef JSON_HEDLEY_INTEL_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_INTEL_VERSION) - #define JSON_HEDLEY_INTEL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_INTEL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_INTEL_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_INTEL_CL_VERSION) - #undef JSON_HEDLEY_INTEL_CL_VERSION -#endif -#if defined(__INTEL_COMPILER) && defined(__INTEL_COMPILER_UPDATE) && defined(__ICL) - #define JSON_HEDLEY_INTEL_CL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER, __INTEL_COMPILER_UPDATE, 0) -#endif - -#if defined(JSON_HEDLEY_INTEL_CL_VERSION_CHECK) - #undef JSON_HEDLEY_INTEL_CL_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_INTEL_CL_VERSION) - #define JSON_HEDLEY_INTEL_CL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_INTEL_CL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_INTEL_CL_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_PGI_VERSION) - #undef JSON_HEDLEY_PGI_VERSION -#endif -#if defined(__PGI) && defined(__PGIC__) && defined(__PGIC_MINOR__) && defined(__PGIC_PATCHLEVEL__) - #define JSON_HEDLEY_PGI_VERSION JSON_HEDLEY_VERSION_ENCODE(__PGIC__, __PGIC_MINOR__, __PGIC_PATCHLEVEL__) -#endif - -#if defined(JSON_HEDLEY_PGI_VERSION_CHECK) - #undef JSON_HEDLEY_PGI_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_PGI_VERSION) - #define JSON_HEDLEY_PGI_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_PGI_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_PGI_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_SUNPRO_VERSION) - #undef JSON_HEDLEY_SUNPRO_VERSION -#endif -#if defined(__SUNPRO_C) && (__SUNPRO_C > 0x1000) - #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((((__SUNPRO_C >> 16) & 0xf) * 10) + ((__SUNPRO_C >> 12) & 0xf), (((__SUNPRO_C >> 8) & 0xf) * 10) + ((__SUNPRO_C >> 4) & 0xf), (__SUNPRO_C & 0xf) * 10) -#elif defined(__SUNPRO_C) - #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((__SUNPRO_C >> 8) & 0xf, (__SUNPRO_C >> 4) & 0xf, (__SUNPRO_C) & 0xf) -#elif defined(__SUNPRO_CC) && (__SUNPRO_CC > 0x1000) - #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((((__SUNPRO_CC >> 16) & 0xf) * 10) + ((__SUNPRO_CC >> 12) & 0xf), (((__SUNPRO_CC >> 8) & 0xf) * 10) + ((__SUNPRO_CC >> 4) & 0xf), (__SUNPRO_CC & 0xf) * 10) -#elif defined(__SUNPRO_CC) - #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((__SUNPRO_CC >> 8) & 0xf, (__SUNPRO_CC >> 4) & 0xf, (__SUNPRO_CC) & 0xf) -#endif - -#if defined(JSON_HEDLEY_SUNPRO_VERSION_CHECK) - #undef JSON_HEDLEY_SUNPRO_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_SUNPRO_VERSION) - #define JSON_HEDLEY_SUNPRO_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_SUNPRO_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_SUNPRO_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION) - #undef JSON_HEDLEY_EMSCRIPTEN_VERSION -#endif -#if defined(__EMSCRIPTEN__) - #define JSON_HEDLEY_EMSCRIPTEN_VERSION JSON_HEDLEY_VERSION_ENCODE(__EMSCRIPTEN_major__, __EMSCRIPTEN_minor__, __EMSCRIPTEN_tiny__) -#endif - -#if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK) - #undef JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION) - #define JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_EMSCRIPTEN_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_ARM_VERSION) - #undef JSON_HEDLEY_ARM_VERSION -#endif -#if defined(__CC_ARM) && defined(__ARMCOMPILER_VERSION) - #define JSON_HEDLEY_ARM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ARMCOMPILER_VERSION / 1000000, (__ARMCOMPILER_VERSION % 1000000) / 10000, (__ARMCOMPILER_VERSION % 10000) / 100) -#elif defined(__CC_ARM) && defined(__ARMCC_VERSION) - #define JSON_HEDLEY_ARM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ARMCC_VERSION / 1000000, (__ARMCC_VERSION % 1000000) / 10000, (__ARMCC_VERSION % 10000) / 100) -#endif - -#if defined(JSON_HEDLEY_ARM_VERSION_CHECK) - #undef JSON_HEDLEY_ARM_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_ARM_VERSION) - #define JSON_HEDLEY_ARM_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_ARM_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_ARM_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_IBM_VERSION) - #undef JSON_HEDLEY_IBM_VERSION -#endif -#if defined(__ibmxl__) - #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ibmxl_version__, __ibmxl_release__, __ibmxl_modification__) -#elif defined(__xlC__) && defined(__xlC_ver__) - #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__xlC__ >> 8, __xlC__ & 0xff, (__xlC_ver__ >> 8) & 0xff) -#elif defined(__xlC__) - #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__xlC__ >> 8, __xlC__ & 0xff, 0) -#endif - -#if defined(JSON_HEDLEY_IBM_VERSION_CHECK) - #undef JSON_HEDLEY_IBM_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_IBM_VERSION) - #define JSON_HEDLEY_IBM_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_IBM_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_IBM_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_TI_VERSION) - #undef JSON_HEDLEY_TI_VERSION -#endif -#if \ - defined(__TI_COMPILER_VERSION__) && \ - ( \ - defined(__TMS470__) || defined(__TI_ARM__) || \ - defined(__MSP430__) || \ - defined(__TMS320C2000__) \ - ) -#if (__TI_COMPILER_VERSION__ >= 16000000) - #define JSON_HEDLEY_TI_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) -#endif -#endif - -#if defined(JSON_HEDLEY_TI_VERSION_CHECK) - #undef JSON_HEDLEY_TI_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_TI_VERSION) - #define JSON_HEDLEY_TI_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_TI_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_TI_CL2000_VERSION) - #undef JSON_HEDLEY_TI_CL2000_VERSION -#endif -#if defined(__TI_COMPILER_VERSION__) && defined(__TMS320C2000__) - #define JSON_HEDLEY_TI_CL2000_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) -#endif - -#if defined(JSON_HEDLEY_TI_CL2000_VERSION_CHECK) - #undef JSON_HEDLEY_TI_CL2000_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_TI_CL2000_VERSION) - #define JSON_HEDLEY_TI_CL2000_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL2000_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_TI_CL2000_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_TI_CL430_VERSION) - #undef JSON_HEDLEY_TI_CL430_VERSION -#endif -#if defined(__TI_COMPILER_VERSION__) && defined(__MSP430__) - #define JSON_HEDLEY_TI_CL430_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) -#endif - -#if defined(JSON_HEDLEY_TI_CL430_VERSION_CHECK) - #undef JSON_HEDLEY_TI_CL430_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_TI_CL430_VERSION) - #define JSON_HEDLEY_TI_CL430_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL430_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_TI_CL430_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_TI_ARMCL_VERSION) - #undef JSON_HEDLEY_TI_ARMCL_VERSION -#endif -#if defined(__TI_COMPILER_VERSION__) && (defined(__TMS470__) || defined(__TI_ARM__)) - #define JSON_HEDLEY_TI_ARMCL_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) -#endif - -#if defined(JSON_HEDLEY_TI_ARMCL_VERSION_CHECK) - #undef JSON_HEDLEY_TI_ARMCL_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_TI_ARMCL_VERSION) - #define JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_ARMCL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_TI_CL6X_VERSION) - #undef JSON_HEDLEY_TI_CL6X_VERSION -#endif -#if defined(__TI_COMPILER_VERSION__) && defined(__TMS320C6X__) - #define JSON_HEDLEY_TI_CL6X_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) -#endif - -#if defined(JSON_HEDLEY_TI_CL6X_VERSION_CHECK) - #undef JSON_HEDLEY_TI_CL6X_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_TI_CL6X_VERSION) - #define JSON_HEDLEY_TI_CL6X_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL6X_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_TI_CL6X_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_TI_CL7X_VERSION) - #undef JSON_HEDLEY_TI_CL7X_VERSION -#endif -#if defined(__TI_COMPILER_VERSION__) && defined(__C7000__) - #define JSON_HEDLEY_TI_CL7X_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) -#endif - -#if defined(JSON_HEDLEY_TI_CL7X_VERSION_CHECK) - #undef JSON_HEDLEY_TI_CL7X_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_TI_CL7X_VERSION) - #define JSON_HEDLEY_TI_CL7X_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL7X_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_TI_CL7X_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_TI_CLPRU_VERSION) - #undef JSON_HEDLEY_TI_CLPRU_VERSION -#endif -#if defined(__TI_COMPILER_VERSION__) && defined(__PRU__) - #define JSON_HEDLEY_TI_CLPRU_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000)) -#endif - -#if defined(JSON_HEDLEY_TI_CLPRU_VERSION_CHECK) - #undef JSON_HEDLEY_TI_CLPRU_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_TI_CLPRU_VERSION) - #define JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CLPRU_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_CRAY_VERSION) - #undef JSON_HEDLEY_CRAY_VERSION -#endif -#if defined(_CRAYC) - #if defined(_RELEASE_PATCHLEVEL) - #define JSON_HEDLEY_CRAY_VERSION JSON_HEDLEY_VERSION_ENCODE(_RELEASE_MAJOR, _RELEASE_MINOR, _RELEASE_PATCHLEVEL) - #else - #define JSON_HEDLEY_CRAY_VERSION JSON_HEDLEY_VERSION_ENCODE(_RELEASE_MAJOR, _RELEASE_MINOR, 0) - #endif -#endif - -#if defined(JSON_HEDLEY_CRAY_VERSION_CHECK) - #undef JSON_HEDLEY_CRAY_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_CRAY_VERSION) - #define JSON_HEDLEY_CRAY_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_CRAY_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_CRAY_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_IAR_VERSION) - #undef JSON_HEDLEY_IAR_VERSION -#endif -#if defined(__IAR_SYSTEMS_ICC__) - #if __VER__ > 1000 - #define JSON_HEDLEY_IAR_VERSION JSON_HEDLEY_VERSION_ENCODE((__VER__ / 1000000), ((__VER__ / 1000) % 1000), (__VER__ % 1000)) - #else - #define JSON_HEDLEY_IAR_VERSION JSON_HEDLEY_VERSION_ENCODE(__VER__ / 100, __VER__ % 100, 0) - #endif -#endif - -#if defined(JSON_HEDLEY_IAR_VERSION_CHECK) - #undef JSON_HEDLEY_IAR_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_IAR_VERSION) - #define JSON_HEDLEY_IAR_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_IAR_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_IAR_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_TINYC_VERSION) - #undef JSON_HEDLEY_TINYC_VERSION -#endif -#if defined(__TINYC__) - #define JSON_HEDLEY_TINYC_VERSION JSON_HEDLEY_VERSION_ENCODE(__TINYC__ / 1000, (__TINYC__ / 100) % 10, __TINYC__ % 100) -#endif - -#if defined(JSON_HEDLEY_TINYC_VERSION_CHECK) - #undef JSON_HEDLEY_TINYC_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_TINYC_VERSION) - #define JSON_HEDLEY_TINYC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TINYC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_TINYC_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_DMC_VERSION) - #undef JSON_HEDLEY_DMC_VERSION -#endif -#if defined(__DMC__) - #define JSON_HEDLEY_DMC_VERSION JSON_HEDLEY_VERSION_ENCODE(__DMC__ >> 8, (__DMC__ >> 4) & 0xf, __DMC__ & 0xf) -#endif - -#if defined(JSON_HEDLEY_DMC_VERSION_CHECK) - #undef JSON_HEDLEY_DMC_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_DMC_VERSION) - #define JSON_HEDLEY_DMC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_DMC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_DMC_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_COMPCERT_VERSION) - #undef JSON_HEDLEY_COMPCERT_VERSION -#endif -#if defined(__COMPCERT_VERSION__) - #define JSON_HEDLEY_COMPCERT_VERSION JSON_HEDLEY_VERSION_ENCODE(__COMPCERT_VERSION__ / 10000, (__COMPCERT_VERSION__ / 100) % 100, __COMPCERT_VERSION__ % 100) -#endif - -#if defined(JSON_HEDLEY_COMPCERT_VERSION_CHECK) - #undef JSON_HEDLEY_COMPCERT_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_COMPCERT_VERSION) - #define JSON_HEDLEY_COMPCERT_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_COMPCERT_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_COMPCERT_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_PELLES_VERSION) - #undef JSON_HEDLEY_PELLES_VERSION -#endif -#if defined(__POCC__) - #define JSON_HEDLEY_PELLES_VERSION JSON_HEDLEY_VERSION_ENCODE(__POCC__ / 100, __POCC__ % 100, 0) -#endif - -#if defined(JSON_HEDLEY_PELLES_VERSION_CHECK) - #undef JSON_HEDLEY_PELLES_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_PELLES_VERSION) - #define JSON_HEDLEY_PELLES_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_PELLES_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_PELLES_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_MCST_LCC_VERSION) - #undef JSON_HEDLEY_MCST_LCC_VERSION -#endif -#if defined(__LCC__) && defined(__LCC_MINOR__) - #define JSON_HEDLEY_MCST_LCC_VERSION JSON_HEDLEY_VERSION_ENCODE(__LCC__ / 100, __LCC__ % 100, __LCC_MINOR__) -#endif - -#if defined(JSON_HEDLEY_MCST_LCC_VERSION_CHECK) - #undef JSON_HEDLEY_MCST_LCC_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_MCST_LCC_VERSION) - #define JSON_HEDLEY_MCST_LCC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_MCST_LCC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_MCST_LCC_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_GCC_VERSION) - #undef JSON_HEDLEY_GCC_VERSION -#endif -#if \ - defined(JSON_HEDLEY_GNUC_VERSION) && \ - !defined(__clang__) && \ - !defined(JSON_HEDLEY_INTEL_VERSION) && \ - !defined(JSON_HEDLEY_PGI_VERSION) && \ - !defined(JSON_HEDLEY_ARM_VERSION) && \ - !defined(JSON_HEDLEY_CRAY_VERSION) && \ - !defined(JSON_HEDLEY_TI_VERSION) && \ - !defined(JSON_HEDLEY_TI_ARMCL_VERSION) && \ - !defined(JSON_HEDLEY_TI_CL430_VERSION) && \ - !defined(JSON_HEDLEY_TI_CL2000_VERSION) && \ - !defined(JSON_HEDLEY_TI_CL6X_VERSION) && \ - !defined(JSON_HEDLEY_TI_CL7X_VERSION) && \ - !defined(JSON_HEDLEY_TI_CLPRU_VERSION) && \ - !defined(__COMPCERT__) && \ - !defined(JSON_HEDLEY_MCST_LCC_VERSION) - #define JSON_HEDLEY_GCC_VERSION JSON_HEDLEY_GNUC_VERSION -#endif - -#if defined(JSON_HEDLEY_GCC_VERSION_CHECK) - #undef JSON_HEDLEY_GCC_VERSION_CHECK -#endif -#if defined(JSON_HEDLEY_GCC_VERSION) - #define JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_GCC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) -#else - #define JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) (0) -#endif - -#if defined(JSON_HEDLEY_HAS_ATTRIBUTE) - #undef JSON_HEDLEY_HAS_ATTRIBUTE -#endif -#if \ - defined(__has_attribute) && \ - ( \ - (!defined(JSON_HEDLEY_IAR_VERSION) || JSON_HEDLEY_IAR_VERSION_CHECK(8,5,9)) \ - ) -# define JSON_HEDLEY_HAS_ATTRIBUTE(attribute) __has_attribute(attribute) -#else -# define JSON_HEDLEY_HAS_ATTRIBUTE(attribute) (0) -#endif - -#if defined(JSON_HEDLEY_GNUC_HAS_ATTRIBUTE) - #undef JSON_HEDLEY_GNUC_HAS_ATTRIBUTE -#endif -#if defined(__has_attribute) - #define JSON_HEDLEY_GNUC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_HAS_ATTRIBUTE(attribute) -#else - #define JSON_HEDLEY_GNUC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) -#endif - -#if defined(JSON_HEDLEY_GCC_HAS_ATTRIBUTE) - #undef JSON_HEDLEY_GCC_HAS_ATTRIBUTE -#endif -#if defined(__has_attribute) - #define JSON_HEDLEY_GCC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_HAS_ATTRIBUTE(attribute) -#else - #define JSON_HEDLEY_GCC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) -#endif - -#if defined(JSON_HEDLEY_HAS_CPP_ATTRIBUTE) - #undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE -#endif -#if \ - defined(__has_cpp_attribute) && \ - defined(__cplusplus) && \ - (!defined(JSON_HEDLEY_SUNPRO_VERSION) || JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0)) - #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) __has_cpp_attribute(attribute) -#else - #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) (0) -#endif - -#if defined(JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS) - #undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS -#endif -#if !defined(__cplusplus) || !defined(__has_cpp_attribute) - #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) (0) -#elif \ - !defined(JSON_HEDLEY_PGI_VERSION) && \ - !defined(JSON_HEDLEY_IAR_VERSION) && \ - (!defined(JSON_HEDLEY_SUNPRO_VERSION) || JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0)) && \ - (!defined(JSON_HEDLEY_MSVC_VERSION) || JSON_HEDLEY_MSVC_VERSION_CHECK(19,20,0)) - #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) JSON_HEDLEY_HAS_CPP_ATTRIBUTE(ns::attribute) -#else - #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) (0) -#endif - -#if defined(JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE) - #undef JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE -#endif -#if defined(__has_cpp_attribute) && defined(__cplusplus) - #define JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) __has_cpp_attribute(attribute) -#else - #define JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) -#endif - -#if defined(JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE) - #undef JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE -#endif -#if defined(__has_cpp_attribute) && defined(__cplusplus) - #define JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) __has_cpp_attribute(attribute) -#else - #define JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) -#endif - -#if defined(JSON_HEDLEY_HAS_BUILTIN) - #undef JSON_HEDLEY_HAS_BUILTIN -#endif -#if defined(__has_builtin) - #define JSON_HEDLEY_HAS_BUILTIN(builtin) __has_builtin(builtin) -#else - #define JSON_HEDLEY_HAS_BUILTIN(builtin) (0) -#endif - -#if defined(JSON_HEDLEY_GNUC_HAS_BUILTIN) - #undef JSON_HEDLEY_GNUC_HAS_BUILTIN -#endif -#if defined(__has_builtin) - #define JSON_HEDLEY_GNUC_HAS_BUILTIN(builtin,major,minor,patch) __has_builtin(builtin) -#else - #define JSON_HEDLEY_GNUC_HAS_BUILTIN(builtin,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) -#endif - -#if defined(JSON_HEDLEY_GCC_HAS_BUILTIN) - #undef JSON_HEDLEY_GCC_HAS_BUILTIN -#endif -#if defined(__has_builtin) - #define JSON_HEDLEY_GCC_HAS_BUILTIN(builtin,major,minor,patch) __has_builtin(builtin) -#else - #define JSON_HEDLEY_GCC_HAS_BUILTIN(builtin,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) -#endif - -#if defined(JSON_HEDLEY_HAS_FEATURE) - #undef JSON_HEDLEY_HAS_FEATURE -#endif -#if defined(__has_feature) - #define JSON_HEDLEY_HAS_FEATURE(feature) __has_feature(feature) -#else - #define JSON_HEDLEY_HAS_FEATURE(feature) (0) -#endif - -#if defined(JSON_HEDLEY_GNUC_HAS_FEATURE) - #undef JSON_HEDLEY_GNUC_HAS_FEATURE -#endif -#if defined(__has_feature) - #define JSON_HEDLEY_GNUC_HAS_FEATURE(feature,major,minor,patch) __has_feature(feature) -#else - #define JSON_HEDLEY_GNUC_HAS_FEATURE(feature,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) -#endif - -#if defined(JSON_HEDLEY_GCC_HAS_FEATURE) - #undef JSON_HEDLEY_GCC_HAS_FEATURE -#endif -#if defined(__has_feature) - #define JSON_HEDLEY_GCC_HAS_FEATURE(feature,major,minor,patch) __has_feature(feature) -#else - #define JSON_HEDLEY_GCC_HAS_FEATURE(feature,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) -#endif - -#if defined(JSON_HEDLEY_HAS_EXTENSION) - #undef JSON_HEDLEY_HAS_EXTENSION -#endif -#if defined(__has_extension) - #define JSON_HEDLEY_HAS_EXTENSION(extension) __has_extension(extension) -#else - #define JSON_HEDLEY_HAS_EXTENSION(extension) (0) -#endif - -#if defined(JSON_HEDLEY_GNUC_HAS_EXTENSION) - #undef JSON_HEDLEY_GNUC_HAS_EXTENSION -#endif -#if defined(__has_extension) - #define JSON_HEDLEY_GNUC_HAS_EXTENSION(extension,major,minor,patch) __has_extension(extension) -#else - #define JSON_HEDLEY_GNUC_HAS_EXTENSION(extension,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) -#endif - -#if defined(JSON_HEDLEY_GCC_HAS_EXTENSION) - #undef JSON_HEDLEY_GCC_HAS_EXTENSION -#endif -#if defined(__has_extension) - #define JSON_HEDLEY_GCC_HAS_EXTENSION(extension,major,minor,patch) __has_extension(extension) -#else - #define JSON_HEDLEY_GCC_HAS_EXTENSION(extension,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) -#endif - -#if defined(JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE) - #undef JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE -#endif -#if defined(__has_declspec_attribute) - #define JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) __has_declspec_attribute(attribute) -#else - #define JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) (0) -#endif - -#if defined(JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE) - #undef JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE -#endif -#if defined(__has_declspec_attribute) - #define JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) __has_declspec_attribute(attribute) -#else - #define JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) -#endif - -#if defined(JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE) - #undef JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE -#endif -#if defined(__has_declspec_attribute) - #define JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) __has_declspec_attribute(attribute) -#else - #define JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) -#endif - -#if defined(JSON_HEDLEY_HAS_WARNING) - #undef JSON_HEDLEY_HAS_WARNING -#endif -#if defined(__has_warning) - #define JSON_HEDLEY_HAS_WARNING(warning) __has_warning(warning) -#else - #define JSON_HEDLEY_HAS_WARNING(warning) (0) -#endif - -#if defined(JSON_HEDLEY_GNUC_HAS_WARNING) - #undef JSON_HEDLEY_GNUC_HAS_WARNING -#endif -#if defined(__has_warning) - #define JSON_HEDLEY_GNUC_HAS_WARNING(warning,major,minor,patch) __has_warning(warning) -#else - #define JSON_HEDLEY_GNUC_HAS_WARNING(warning,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) -#endif - -#if defined(JSON_HEDLEY_GCC_HAS_WARNING) - #undef JSON_HEDLEY_GCC_HAS_WARNING -#endif -#if defined(__has_warning) - #define JSON_HEDLEY_GCC_HAS_WARNING(warning,major,minor,patch) __has_warning(warning) -#else - #define JSON_HEDLEY_GCC_HAS_WARNING(warning,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) -#endif - -#if \ - (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || \ - defined(__clang__) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) || \ - JSON_HEDLEY_PGI_VERSION_CHECK(18,4,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ - JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ - JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,7,0) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(2,0,1) || \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,1,0) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,0,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ - JSON_HEDLEY_CRAY_VERSION_CHECK(5,0,0) || \ - JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,17) || \ - JSON_HEDLEY_SUNPRO_VERSION_CHECK(8,0,0) || \ - (JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) && defined(__C99_PRAGMA_OPERATOR)) - #define JSON_HEDLEY_PRAGMA(value) _Pragma(#value) -#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) - #define JSON_HEDLEY_PRAGMA(value) __pragma(value) -#else - #define JSON_HEDLEY_PRAGMA(value) -#endif - -#if defined(JSON_HEDLEY_DIAGNOSTIC_PUSH) - #undef JSON_HEDLEY_DIAGNOSTIC_PUSH -#endif -#if defined(JSON_HEDLEY_DIAGNOSTIC_POP) - #undef JSON_HEDLEY_DIAGNOSTIC_POP -#endif -#if defined(__clang__) - #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("clang diagnostic push") - #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("clang diagnostic pop") -#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) - #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("warning(push)") - #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("warning(pop)") -#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) - #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("GCC diagnostic push") - #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("GCC diagnostic pop") -#elif \ - JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) || \ - JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) - #define JSON_HEDLEY_DIAGNOSTIC_PUSH __pragma(warning(push)) - #define JSON_HEDLEY_DIAGNOSTIC_POP __pragma(warning(pop)) -#elif JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) - #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("push") - #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("pop") -#elif \ - JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ - JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,4,0) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,1,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) - #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("diag_push") - #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("diag_pop") -#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,90,0) - #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("warning(push)") - #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("warning(pop)") -#else - #define JSON_HEDLEY_DIAGNOSTIC_PUSH - #define JSON_HEDLEY_DIAGNOSTIC_POP -#endif - -/* JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_ is for - HEDLEY INTERNAL USE ONLY. API subject to change without notice. */ -#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_) - #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_ -#endif -#if defined(__cplusplus) -# if JSON_HEDLEY_HAS_WARNING("-Wc++98-compat") -# if JSON_HEDLEY_HAS_WARNING("-Wc++17-extensions") -# if JSON_HEDLEY_HAS_WARNING("-Wc++1z-extensions") -# define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \ - JSON_HEDLEY_DIAGNOSTIC_PUSH \ - _Pragma("clang diagnostic ignored \"-Wc++98-compat\"") \ - _Pragma("clang diagnostic ignored \"-Wc++17-extensions\"") \ - _Pragma("clang diagnostic ignored \"-Wc++1z-extensions\"") \ - xpr \ - JSON_HEDLEY_DIAGNOSTIC_POP -# else -# define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \ - JSON_HEDLEY_DIAGNOSTIC_PUSH \ - _Pragma("clang diagnostic ignored \"-Wc++98-compat\"") \ - _Pragma("clang diagnostic ignored \"-Wc++17-extensions\"") \ - xpr \ - JSON_HEDLEY_DIAGNOSTIC_POP -# endif -# else -# define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \ - JSON_HEDLEY_DIAGNOSTIC_PUSH \ - _Pragma("clang diagnostic ignored \"-Wc++98-compat\"") \ - xpr \ - JSON_HEDLEY_DIAGNOSTIC_POP -# endif -# endif -#endif -#if !defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(x) x -#endif - -#if defined(JSON_HEDLEY_CONST_CAST) - #undef JSON_HEDLEY_CONST_CAST -#endif -#if defined(__cplusplus) -# define JSON_HEDLEY_CONST_CAST(T, expr) (const_cast(expr)) -#elif \ - JSON_HEDLEY_HAS_WARNING("-Wcast-qual") || \ - JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) -# define JSON_HEDLEY_CONST_CAST(T, expr) (__extension__ ({ \ - JSON_HEDLEY_DIAGNOSTIC_PUSH \ - JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL \ - ((T) (expr)); \ - JSON_HEDLEY_DIAGNOSTIC_POP \ - })) -#else -# define JSON_HEDLEY_CONST_CAST(T, expr) ((T) (expr)) -#endif - -#if defined(JSON_HEDLEY_REINTERPRET_CAST) - #undef JSON_HEDLEY_REINTERPRET_CAST -#endif -#if defined(__cplusplus) - #define JSON_HEDLEY_REINTERPRET_CAST(T, expr) (reinterpret_cast(expr)) -#else - #define JSON_HEDLEY_REINTERPRET_CAST(T, expr) ((T) (expr)) -#endif - -#if defined(JSON_HEDLEY_STATIC_CAST) - #undef JSON_HEDLEY_STATIC_CAST -#endif -#if defined(__cplusplus) - #define JSON_HEDLEY_STATIC_CAST(T, expr) (static_cast(expr)) -#else - #define JSON_HEDLEY_STATIC_CAST(T, expr) ((T) (expr)) -#endif - -#if defined(JSON_HEDLEY_CPP_CAST) - #undef JSON_HEDLEY_CPP_CAST -#endif -#if defined(__cplusplus) -# if JSON_HEDLEY_HAS_WARNING("-Wold-style-cast") -# define JSON_HEDLEY_CPP_CAST(T, expr) \ - JSON_HEDLEY_DIAGNOSTIC_PUSH \ - _Pragma("clang diagnostic ignored \"-Wold-style-cast\"") \ - ((T) (expr)) \ - JSON_HEDLEY_DIAGNOSTIC_POP -# elif JSON_HEDLEY_IAR_VERSION_CHECK(8,3,0) -# define JSON_HEDLEY_CPP_CAST(T, expr) \ - JSON_HEDLEY_DIAGNOSTIC_PUSH \ - _Pragma("diag_suppress=Pe137") \ - JSON_HEDLEY_DIAGNOSTIC_POP -# else -# define JSON_HEDLEY_CPP_CAST(T, expr) ((T) (expr)) -# endif -#else -# define JSON_HEDLEY_CPP_CAST(T, expr) (expr) -#endif - -#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED) - #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED -#endif -#if JSON_HEDLEY_HAS_WARNING("-Wdeprecated-declarations") - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"") -#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("warning(disable:1478 1786)") -#elif JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED __pragma(warning(disable:1478 1786)) -#elif JSON_HEDLEY_PGI_VERSION_CHECK(20,7,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1215,1216,1444,1445") -#elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1215,1444") -#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") -#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED __pragma(warning(disable:4996)) -#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1215,1444") -#elif \ - JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ - (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ - (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ - (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ - (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1291,1718") -#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) && !defined(__cplusplus) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("error_messages(off,E_DEPRECATED_ATT,E_DEPRECATED_ATT_MESS)") -#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) && defined(__cplusplus) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("error_messages(off,symdeprecated,symdeprecated2)") -#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress=Pe1444,Pe1215") -#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,90,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("warn(disable:2241)") -#else - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED -#endif - -#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS) - #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS -#endif -#if JSON_HEDLEY_HAS_WARNING("-Wunknown-pragmas") - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("clang diagnostic ignored \"-Wunknown-pragmas\"") -#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("warning(disable:161)") -#elif JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS __pragma(warning(disable:161)) -#elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 1675") -#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("GCC diagnostic ignored \"-Wunknown-pragmas\"") -#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS __pragma(warning(disable:4068)) -#elif \ - JSON_HEDLEY_TI_VERSION_CHECK(16,9,0) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,3,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 163") -#elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 163") -#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress=Pe161") -#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 161") -#else - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS -#endif - -#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES) - #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES -#endif -#if JSON_HEDLEY_HAS_WARNING("-Wunknown-attributes") - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("clang diagnostic ignored \"-Wunknown-attributes\"") -#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") -#elif JSON_HEDLEY_INTEL_VERSION_CHECK(17,0,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("warning(disable:1292)") -#elif JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES __pragma(warning(disable:1292)) -#elif JSON_HEDLEY_MSVC_VERSION_CHECK(19,0,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES __pragma(warning(disable:5030)) -#elif JSON_HEDLEY_PGI_VERSION_CHECK(20,7,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1097,1098") -#elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1097") -#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,14,0) && defined(__cplusplus) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("error_messages(off,attrskipunsup)") -#elif \ - JSON_HEDLEY_TI_VERSION_CHECK(18,1,0) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,3,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1173") -#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress=Pe1097") -#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1097") -#else - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES -#endif - -#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL) - #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL -#endif -#if JSON_HEDLEY_HAS_WARNING("-Wcast-qual") - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("clang diagnostic ignored \"-Wcast-qual\"") -#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("warning(disable:2203 2331)") -#elif JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma("GCC diagnostic ignored \"-Wcast-qual\"") -#else - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL -#endif - -#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION) - #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION -#endif -#if JSON_HEDLEY_HAS_WARNING("-Wunused-function") - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION _Pragma("clang diagnostic ignored \"-Wunused-function\"") -#elif JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION _Pragma("GCC diagnostic ignored \"-Wunused-function\"") -#elif JSON_HEDLEY_MSVC_VERSION_CHECK(1,0,0) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION __pragma(warning(disable:4505)) -#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION _Pragma("diag_suppress 3142") -#else - #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION -#endif - -#if defined(JSON_HEDLEY_DEPRECATED) - #undef JSON_HEDLEY_DEPRECATED -#endif -#if defined(JSON_HEDLEY_DEPRECATED_FOR) - #undef JSON_HEDLEY_DEPRECATED_FOR -#endif -#if \ - JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \ - JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) - #define JSON_HEDLEY_DEPRECATED(since) __declspec(deprecated("Since " # since)) - #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __declspec(deprecated("Since " #since "; use " #replacement)) -#elif \ - (JSON_HEDLEY_HAS_EXTENSION(attribute_deprecated_with_message) && !defined(JSON_HEDLEY_IAR_VERSION)) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(4,5,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) || \ - JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) || \ - JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ - JSON_HEDLEY_TI_VERSION_CHECK(18,1,0) || \ - JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(18,1,0) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,3,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,3,0) || \ - JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) - #define JSON_HEDLEY_DEPRECATED(since) __attribute__((__deprecated__("Since " #since))) - #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __attribute__((__deprecated__("Since " #since "; use " #replacement))) -#elif defined(__cplusplus) && (__cplusplus >= 201402L) - #define JSON_HEDLEY_DEPRECATED(since) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[deprecated("Since " #since)]]) - #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[deprecated("Since " #since "; use " #replacement)]]) -#elif \ - JSON_HEDLEY_HAS_ATTRIBUTE(deprecated) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ - JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ - (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ - (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ - (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ - (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ - JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) || \ - JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0) - #define JSON_HEDLEY_DEPRECATED(since) __attribute__((__deprecated__)) - #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __attribute__((__deprecated__)) -#elif \ - JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ - JSON_HEDLEY_PELLES_VERSION_CHECK(6,50,0) || \ - JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) - #define JSON_HEDLEY_DEPRECATED(since) __declspec(deprecated) - #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __declspec(deprecated) -#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) - #define JSON_HEDLEY_DEPRECATED(since) _Pragma("deprecated") - #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) _Pragma("deprecated") -#else - #define JSON_HEDLEY_DEPRECATED(since) - #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) -#endif - -#if defined(JSON_HEDLEY_UNAVAILABLE) - #undef JSON_HEDLEY_UNAVAILABLE -#endif -#if \ - JSON_HEDLEY_HAS_ATTRIBUTE(warning) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) - #define JSON_HEDLEY_UNAVAILABLE(available_since) __attribute__((__warning__("Not available until " #available_since))) -#else - #define JSON_HEDLEY_UNAVAILABLE(available_since) -#endif - -#if defined(JSON_HEDLEY_WARN_UNUSED_RESULT) - #undef JSON_HEDLEY_WARN_UNUSED_RESULT -#endif -#if defined(JSON_HEDLEY_WARN_UNUSED_RESULT_MSG) - #undef JSON_HEDLEY_WARN_UNUSED_RESULT_MSG -#endif -#if \ - JSON_HEDLEY_HAS_ATTRIBUTE(warn_unused_result) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ - (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ - (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ - (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ - (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ - (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0) && defined(__cplusplus)) || \ - JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ - JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) - #define JSON_HEDLEY_WARN_UNUSED_RESULT __attribute__((__warn_unused_result__)) - #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) __attribute__((__warn_unused_result__)) -#elif (JSON_HEDLEY_HAS_CPP_ATTRIBUTE(nodiscard) >= 201907L) - #define JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) - #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard(msg)]]) -#elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE(nodiscard) - #define JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) - #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) -#elif defined(_Check_return_) /* SAL */ - #define JSON_HEDLEY_WARN_UNUSED_RESULT _Check_return_ - #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) _Check_return_ -#else - #define JSON_HEDLEY_WARN_UNUSED_RESULT - #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) -#endif - -#if defined(JSON_HEDLEY_SENTINEL) - #undef JSON_HEDLEY_SENTINEL -#endif -#if \ - JSON_HEDLEY_HAS_ATTRIBUTE(sentinel) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(5,4,0) || \ - JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) - #define JSON_HEDLEY_SENTINEL(position) __attribute__((__sentinel__(position))) -#else - #define JSON_HEDLEY_SENTINEL(position) -#endif - -#if defined(JSON_HEDLEY_NO_RETURN) - #undef JSON_HEDLEY_NO_RETURN -#endif -#if JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) - #define JSON_HEDLEY_NO_RETURN __noreturn -#elif \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) - #define JSON_HEDLEY_NO_RETURN __attribute__((__noreturn__)) -#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L - #define JSON_HEDLEY_NO_RETURN _Noreturn -#elif defined(__cplusplus) && (__cplusplus >= 201103L) - #define JSON_HEDLEY_NO_RETURN JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[noreturn]]) -#elif \ - JSON_HEDLEY_HAS_ATTRIBUTE(noreturn) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(3,2,0) || \ - JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ - JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ - JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ - (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ - (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ - (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ - (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ - JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0) - #define JSON_HEDLEY_NO_RETURN __attribute__((__noreturn__)) -#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) - #define JSON_HEDLEY_NO_RETURN _Pragma("does_not_return") -#elif \ - JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ - JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) - #define JSON_HEDLEY_NO_RETURN __declspec(noreturn) -#elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,0,0) && defined(__cplusplus) - #define JSON_HEDLEY_NO_RETURN _Pragma("FUNC_NEVER_RETURNS;") -#elif JSON_HEDLEY_COMPCERT_VERSION_CHECK(3,2,0) - #define JSON_HEDLEY_NO_RETURN __attribute((noreturn)) -#elif JSON_HEDLEY_PELLES_VERSION_CHECK(9,0,0) - #define JSON_HEDLEY_NO_RETURN __declspec(noreturn) -#else - #define JSON_HEDLEY_NO_RETURN -#endif - -#if defined(JSON_HEDLEY_NO_ESCAPE) - #undef JSON_HEDLEY_NO_ESCAPE -#endif -#if JSON_HEDLEY_HAS_ATTRIBUTE(noescape) - #define JSON_HEDLEY_NO_ESCAPE __attribute__((__noescape__)) -#else - #define JSON_HEDLEY_NO_ESCAPE -#endif - -#if defined(JSON_HEDLEY_UNREACHABLE) - #undef JSON_HEDLEY_UNREACHABLE -#endif -#if defined(JSON_HEDLEY_UNREACHABLE_RETURN) - #undef JSON_HEDLEY_UNREACHABLE_RETURN -#endif -#if defined(JSON_HEDLEY_ASSUME) - #undef JSON_HEDLEY_ASSUME -#endif -#if \ - JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) - #define JSON_HEDLEY_ASSUME(expr) __assume(expr) -#elif JSON_HEDLEY_HAS_BUILTIN(__builtin_assume) - #define JSON_HEDLEY_ASSUME(expr) __builtin_assume(expr) -#elif \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0) - #if defined(__cplusplus) - #define JSON_HEDLEY_ASSUME(expr) std::_nassert(expr) - #else - #define JSON_HEDLEY_ASSUME(expr) _nassert(expr) - #endif -#endif -#if \ - (JSON_HEDLEY_HAS_BUILTIN(__builtin_unreachable) && (!defined(JSON_HEDLEY_ARM_VERSION))) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(4,5,0) || \ - JSON_HEDLEY_PGI_VERSION_CHECK(18,10,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_IBM_VERSION_CHECK(13,1,5) || \ - JSON_HEDLEY_CRAY_VERSION_CHECK(10,0,0) || \ - JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) - #define JSON_HEDLEY_UNREACHABLE() __builtin_unreachable() -#elif defined(JSON_HEDLEY_ASSUME) - #define JSON_HEDLEY_UNREACHABLE() JSON_HEDLEY_ASSUME(0) -#endif -#if !defined(JSON_HEDLEY_ASSUME) - #if defined(JSON_HEDLEY_UNREACHABLE) - #define JSON_HEDLEY_ASSUME(expr) JSON_HEDLEY_STATIC_CAST(void, ((expr) ? 1 : (JSON_HEDLEY_UNREACHABLE(), 1))) - #else - #define JSON_HEDLEY_ASSUME(expr) JSON_HEDLEY_STATIC_CAST(void, expr) - #endif -#endif -#if defined(JSON_HEDLEY_UNREACHABLE) - #if \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0) - #define JSON_HEDLEY_UNREACHABLE_RETURN(value) return (JSON_HEDLEY_STATIC_CAST(void, JSON_HEDLEY_ASSUME(0)), (value)) - #else - #define JSON_HEDLEY_UNREACHABLE_RETURN(value) JSON_HEDLEY_UNREACHABLE() - #endif -#else - #define JSON_HEDLEY_UNREACHABLE_RETURN(value) return (value) -#endif -#if !defined(JSON_HEDLEY_UNREACHABLE) - #define JSON_HEDLEY_UNREACHABLE() JSON_HEDLEY_ASSUME(0) -#endif - -JSON_HEDLEY_DIAGNOSTIC_PUSH -#if JSON_HEDLEY_HAS_WARNING("-Wpedantic") - #pragma clang diagnostic ignored "-Wpedantic" -#endif -#if JSON_HEDLEY_HAS_WARNING("-Wc++98-compat-pedantic") && defined(__cplusplus) - #pragma clang diagnostic ignored "-Wc++98-compat-pedantic" -#endif -#if JSON_HEDLEY_GCC_HAS_WARNING("-Wvariadic-macros",4,0,0) - #if defined(__clang__) - #pragma clang diagnostic ignored "-Wvariadic-macros" - #elif defined(JSON_HEDLEY_GCC_VERSION) - #pragma GCC diagnostic ignored "-Wvariadic-macros" - #endif -#endif -#if defined(JSON_HEDLEY_NON_NULL) - #undef JSON_HEDLEY_NON_NULL -#endif -#if \ - JSON_HEDLEY_HAS_ATTRIBUTE(nonnull) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) - #define JSON_HEDLEY_NON_NULL(...) __attribute__((__nonnull__(__VA_ARGS__))) -#else - #define JSON_HEDLEY_NON_NULL(...) -#endif -JSON_HEDLEY_DIAGNOSTIC_POP - -#if defined(JSON_HEDLEY_PRINTF_FORMAT) - #undef JSON_HEDLEY_PRINTF_FORMAT -#endif -#if defined(__MINGW32__) && JSON_HEDLEY_GCC_HAS_ATTRIBUTE(format,4,4,0) && !defined(__USE_MINGW_ANSI_STDIO) - #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(ms_printf, string_idx, first_to_check))) -#elif defined(__MINGW32__) && JSON_HEDLEY_GCC_HAS_ATTRIBUTE(format,4,4,0) && defined(__USE_MINGW_ANSI_STDIO) - #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(gnu_printf, string_idx, first_to_check))) -#elif \ - JSON_HEDLEY_HAS_ATTRIBUTE(format) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) || \ - JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ - JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ - (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ - (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ - (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ - (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ - JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) - #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(__printf__, string_idx, first_to_check))) -#elif JSON_HEDLEY_PELLES_VERSION_CHECK(6,0,0) - #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __declspec(vaformat(printf,string_idx,first_to_check)) -#else - #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) -#endif - -#if defined(JSON_HEDLEY_CONSTEXPR) - #undef JSON_HEDLEY_CONSTEXPR -#endif -#if defined(__cplusplus) - #if __cplusplus >= 201103L - #define JSON_HEDLEY_CONSTEXPR JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(constexpr) - #endif -#endif -#if !defined(JSON_HEDLEY_CONSTEXPR) - #define JSON_HEDLEY_CONSTEXPR -#endif - -#if defined(JSON_HEDLEY_PREDICT) - #undef JSON_HEDLEY_PREDICT -#endif -#if defined(JSON_HEDLEY_LIKELY) - #undef JSON_HEDLEY_LIKELY -#endif -#if defined(JSON_HEDLEY_UNLIKELY) - #undef JSON_HEDLEY_UNLIKELY -#endif -#if defined(JSON_HEDLEY_UNPREDICTABLE) - #undef JSON_HEDLEY_UNPREDICTABLE -#endif -#if JSON_HEDLEY_HAS_BUILTIN(__builtin_unpredictable) - #define JSON_HEDLEY_UNPREDICTABLE(expr) __builtin_unpredictable((expr)) -#endif -#if \ - (JSON_HEDLEY_HAS_BUILTIN(__builtin_expect_with_probability) && !defined(JSON_HEDLEY_PGI_VERSION)) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(9,0,0) || \ - JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) -# define JSON_HEDLEY_PREDICT(expr, value, probability) __builtin_expect_with_probability( (expr), (value), (probability)) -# define JSON_HEDLEY_PREDICT_TRUE(expr, probability) __builtin_expect_with_probability(!!(expr), 1 , (probability)) -# define JSON_HEDLEY_PREDICT_FALSE(expr, probability) __builtin_expect_with_probability(!!(expr), 0 , (probability)) -# define JSON_HEDLEY_LIKELY(expr) __builtin_expect (!!(expr), 1 ) -# define JSON_HEDLEY_UNLIKELY(expr) __builtin_expect (!!(expr), 0 ) -#elif \ - (JSON_HEDLEY_HAS_BUILTIN(__builtin_expect) && !defined(JSON_HEDLEY_INTEL_CL_VERSION)) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0) && defined(__cplusplus)) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ - JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ - JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ - JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,7,0) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(3,1,0) || \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,1,0) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ - JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,27) || \ - JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \ - JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) -# define JSON_HEDLEY_PREDICT(expr, expected, probability) \ - (((probability) >= 0.9) ? __builtin_expect((expr), (expected)) : (JSON_HEDLEY_STATIC_CAST(void, expected), (expr))) -# define JSON_HEDLEY_PREDICT_TRUE(expr, probability) \ - (__extension__ ({ \ - double hedley_probability_ = (probability); \ - ((hedley_probability_ >= 0.9) ? __builtin_expect(!!(expr), 1) : ((hedley_probability_ <= 0.1) ? __builtin_expect(!!(expr), 0) : !!(expr))); \ - })) -# define JSON_HEDLEY_PREDICT_FALSE(expr, probability) \ - (__extension__ ({ \ - double hedley_probability_ = (probability); \ - ((hedley_probability_ >= 0.9) ? __builtin_expect(!!(expr), 0) : ((hedley_probability_ <= 0.1) ? __builtin_expect(!!(expr), 1) : !!(expr))); \ - })) -# define JSON_HEDLEY_LIKELY(expr) __builtin_expect(!!(expr), 1) -# define JSON_HEDLEY_UNLIKELY(expr) __builtin_expect(!!(expr), 0) -#else -# define JSON_HEDLEY_PREDICT(expr, expected, probability) (JSON_HEDLEY_STATIC_CAST(void, expected), (expr)) -# define JSON_HEDLEY_PREDICT_TRUE(expr, probability) (!!(expr)) -# define JSON_HEDLEY_PREDICT_FALSE(expr, probability) (!!(expr)) -# define JSON_HEDLEY_LIKELY(expr) (!!(expr)) -# define JSON_HEDLEY_UNLIKELY(expr) (!!(expr)) -#endif -#if !defined(JSON_HEDLEY_UNPREDICTABLE) - #define JSON_HEDLEY_UNPREDICTABLE(expr) JSON_HEDLEY_PREDICT(expr, 1, 0.5) -#endif - -#if defined(JSON_HEDLEY_MALLOC) - #undef JSON_HEDLEY_MALLOC -#endif -#if \ - JSON_HEDLEY_HAS_ATTRIBUTE(malloc) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ - JSON_HEDLEY_IBM_VERSION_CHECK(12,1,0) || \ - JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ - (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ - (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ - (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ - (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ - JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) - #define JSON_HEDLEY_MALLOC __attribute__((__malloc__)) -#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) - #define JSON_HEDLEY_MALLOC _Pragma("returns_new_memory") -#elif \ - JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \ - JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) - #define JSON_HEDLEY_MALLOC __declspec(restrict) -#else - #define JSON_HEDLEY_MALLOC -#endif - -#if defined(JSON_HEDLEY_PURE) - #undef JSON_HEDLEY_PURE -#endif -#if \ - JSON_HEDLEY_HAS_ATTRIBUTE(pure) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(2,96,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ - JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ - JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ - (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ - (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ - (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ - (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ - JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ - JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) -# define JSON_HEDLEY_PURE __attribute__((__pure__)) -#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) -# define JSON_HEDLEY_PURE _Pragma("does_not_write_global_data") -#elif defined(__cplusplus) && \ - ( \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(2,0,1) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) \ - ) -# define JSON_HEDLEY_PURE _Pragma("FUNC_IS_PURE;") -#else -# define JSON_HEDLEY_PURE -#endif - -#if defined(JSON_HEDLEY_CONST) - #undef JSON_HEDLEY_CONST -#endif -#if \ - JSON_HEDLEY_HAS_ATTRIBUTE(const) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(2,5,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ - JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ - JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ - (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ - (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ - (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ - (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ - JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ - JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) - #define JSON_HEDLEY_CONST __attribute__((__const__)) -#elif \ - JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) - #define JSON_HEDLEY_CONST _Pragma("no_side_effect") -#else - #define JSON_HEDLEY_CONST JSON_HEDLEY_PURE -#endif - -#if defined(JSON_HEDLEY_RESTRICT) - #undef JSON_HEDLEY_RESTRICT -#endif -#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && !defined(__cplusplus) - #define JSON_HEDLEY_RESTRICT restrict -#elif \ - JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ - JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ - JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ - JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,4) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,1,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,14,0) && defined(__cplusplus)) || \ - JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) || \ - defined(__clang__) || \ - JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) - #define JSON_HEDLEY_RESTRICT __restrict -#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,3,0) && !defined(__cplusplus) - #define JSON_HEDLEY_RESTRICT _Restrict -#else - #define JSON_HEDLEY_RESTRICT -#endif - -#if defined(JSON_HEDLEY_INLINE) - #undef JSON_HEDLEY_INLINE -#endif -#if \ - (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || \ - (defined(__cplusplus) && (__cplusplus >= 199711L)) - #define JSON_HEDLEY_INLINE inline -#elif \ - defined(JSON_HEDLEY_GCC_VERSION) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(6,2,0) - #define JSON_HEDLEY_INLINE __inline__ -#elif \ - JSON_HEDLEY_MSVC_VERSION_CHECK(12,0,0) || \ - JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ - JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,1,0) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(3,1,0) || \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ - JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) - #define JSON_HEDLEY_INLINE __inline -#else - #define JSON_HEDLEY_INLINE -#endif - -#if defined(JSON_HEDLEY_ALWAYS_INLINE) - #undef JSON_HEDLEY_ALWAYS_INLINE -#endif -#if \ - JSON_HEDLEY_HAS_ATTRIBUTE(always_inline) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ - JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ - JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ - (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ - (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ - (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ - (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ - JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) || \ - JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0) -# define JSON_HEDLEY_ALWAYS_INLINE __attribute__((__always_inline__)) JSON_HEDLEY_INLINE -#elif \ - JSON_HEDLEY_MSVC_VERSION_CHECK(12,0,0) || \ - JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) -# define JSON_HEDLEY_ALWAYS_INLINE __forceinline -#elif defined(__cplusplus) && \ - ( \ - JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) \ - ) -# define JSON_HEDLEY_ALWAYS_INLINE _Pragma("FUNC_ALWAYS_INLINE;") -#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) -# define JSON_HEDLEY_ALWAYS_INLINE _Pragma("inline=forced") -#else -# define JSON_HEDLEY_ALWAYS_INLINE JSON_HEDLEY_INLINE -#endif - -#if defined(JSON_HEDLEY_NEVER_INLINE) - #undef JSON_HEDLEY_NEVER_INLINE -#endif -#if \ - JSON_HEDLEY_HAS_ATTRIBUTE(noinline) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ - JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ - JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ - (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ - (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \ - (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \ - (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ - JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) || \ - JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0) - #define JSON_HEDLEY_NEVER_INLINE __attribute__((__noinline__)) -#elif \ - JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ - JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) - #define JSON_HEDLEY_NEVER_INLINE __declspec(noinline) -#elif JSON_HEDLEY_PGI_VERSION_CHECK(10,2,0) - #define JSON_HEDLEY_NEVER_INLINE _Pragma("noinline") -#elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,0,0) && defined(__cplusplus) - #define JSON_HEDLEY_NEVER_INLINE _Pragma("FUNC_CANNOT_INLINE;") -#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) - #define JSON_HEDLEY_NEVER_INLINE _Pragma("inline=never") -#elif JSON_HEDLEY_COMPCERT_VERSION_CHECK(3,2,0) - #define JSON_HEDLEY_NEVER_INLINE __attribute((noinline)) -#elif JSON_HEDLEY_PELLES_VERSION_CHECK(9,0,0) - #define JSON_HEDLEY_NEVER_INLINE __declspec(noinline) -#else - #define JSON_HEDLEY_NEVER_INLINE -#endif - -#if defined(JSON_HEDLEY_PRIVATE) - #undef JSON_HEDLEY_PRIVATE -#endif -#if defined(JSON_HEDLEY_PUBLIC) - #undef JSON_HEDLEY_PUBLIC -#endif -#if defined(JSON_HEDLEY_IMPORT) - #undef JSON_HEDLEY_IMPORT -#endif -#if defined(_WIN32) || defined(__CYGWIN__) -# define JSON_HEDLEY_PRIVATE -# define JSON_HEDLEY_PUBLIC __declspec(dllexport) -# define JSON_HEDLEY_IMPORT __declspec(dllimport) -#else -# if \ - JSON_HEDLEY_HAS_ATTRIBUTE(visibility) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ - JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ - JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ - ( \ - defined(__TI_EABI__) && \ - ( \ - (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) \ - ) \ - ) || \ - JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) -# define JSON_HEDLEY_PRIVATE __attribute__((__visibility__("hidden"))) -# define JSON_HEDLEY_PUBLIC __attribute__((__visibility__("default"))) -# else -# define JSON_HEDLEY_PRIVATE -# define JSON_HEDLEY_PUBLIC -# endif -# define JSON_HEDLEY_IMPORT extern -#endif - -#if defined(JSON_HEDLEY_NO_THROW) - #undef JSON_HEDLEY_NO_THROW -#endif -#if \ - JSON_HEDLEY_HAS_ATTRIBUTE(nothrow) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) - #define JSON_HEDLEY_NO_THROW __attribute__((__nothrow__)) -#elif \ - JSON_HEDLEY_MSVC_VERSION_CHECK(13,1,0) || \ - JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) - #define JSON_HEDLEY_NO_THROW __declspec(nothrow) -#else - #define JSON_HEDLEY_NO_THROW -#endif - -#if defined(JSON_HEDLEY_FALL_THROUGH) - #undef JSON_HEDLEY_FALL_THROUGH -#endif -#if \ - JSON_HEDLEY_HAS_ATTRIBUTE(fallthrough) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(7,0,0) || \ - JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) - #define JSON_HEDLEY_FALL_THROUGH __attribute__((__fallthrough__)) -#elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(clang,fallthrough) - #define JSON_HEDLEY_FALL_THROUGH JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[clang::fallthrough]]) -#elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE(fallthrough) - #define JSON_HEDLEY_FALL_THROUGH JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[fallthrough]]) -#elif defined(__fallthrough) /* SAL */ - #define JSON_HEDLEY_FALL_THROUGH __fallthrough -#else - #define JSON_HEDLEY_FALL_THROUGH -#endif - -#if defined(JSON_HEDLEY_RETURNS_NON_NULL) - #undef JSON_HEDLEY_RETURNS_NON_NULL -#endif -#if \ - JSON_HEDLEY_HAS_ATTRIBUTE(returns_nonnull) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0) || \ - JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) - #define JSON_HEDLEY_RETURNS_NON_NULL __attribute__((__returns_nonnull__)) -#elif defined(_Ret_notnull_) /* SAL */ - #define JSON_HEDLEY_RETURNS_NON_NULL _Ret_notnull_ -#else - #define JSON_HEDLEY_RETURNS_NON_NULL -#endif - -#if defined(JSON_HEDLEY_ARRAY_PARAM) - #undef JSON_HEDLEY_ARRAY_PARAM -#endif -#if \ - defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && \ - !defined(__STDC_NO_VLA__) && \ - !defined(__cplusplus) && \ - !defined(JSON_HEDLEY_PGI_VERSION) && \ - !defined(JSON_HEDLEY_TINYC_VERSION) - #define JSON_HEDLEY_ARRAY_PARAM(name) (name) -#else - #define JSON_HEDLEY_ARRAY_PARAM(name) -#endif - -#if defined(JSON_HEDLEY_IS_CONSTANT) - #undef JSON_HEDLEY_IS_CONSTANT -#endif -#if defined(JSON_HEDLEY_REQUIRE_CONSTEXPR) - #undef JSON_HEDLEY_REQUIRE_CONSTEXPR -#endif -/* JSON_HEDLEY_IS_CONSTEXPR_ is for - HEDLEY INTERNAL USE ONLY. API subject to change without notice. */ -#if defined(JSON_HEDLEY_IS_CONSTEXPR_) - #undef JSON_HEDLEY_IS_CONSTEXPR_ -#endif -#if \ - JSON_HEDLEY_HAS_BUILTIN(__builtin_constant_p) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,19) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ - JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \ - (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) && !defined(__cplusplus)) || \ - JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \ - JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) - #define JSON_HEDLEY_IS_CONSTANT(expr) __builtin_constant_p(expr) -#endif -#if !defined(__cplusplus) -# if \ - JSON_HEDLEY_HAS_BUILTIN(__builtin_types_compatible_p) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ - JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(5,4,0) || \ - JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,24) -#if defined(__INTPTR_TYPE__) - #define JSON_HEDLEY_IS_CONSTEXPR_(expr) __builtin_types_compatible_p(__typeof__((1 ? (void*) ((__INTPTR_TYPE__) ((expr) * 0)) : (int*) 0)), int*) -#else - #include - #define JSON_HEDLEY_IS_CONSTEXPR_(expr) __builtin_types_compatible_p(__typeof__((1 ? (void*) ((intptr_t) ((expr) * 0)) : (int*) 0)), int*) -#endif -# elif \ - ( \ - defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) && \ - !defined(JSON_HEDLEY_SUNPRO_VERSION) && \ - !defined(JSON_HEDLEY_PGI_VERSION) && \ - !defined(JSON_HEDLEY_IAR_VERSION)) || \ - (JSON_HEDLEY_HAS_EXTENSION(c_generic_selections) && !defined(JSON_HEDLEY_IAR_VERSION)) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(17,0,0) || \ - JSON_HEDLEY_IBM_VERSION_CHECK(12,1,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(5,3,0) -#if defined(__INTPTR_TYPE__) - #define JSON_HEDLEY_IS_CONSTEXPR_(expr) _Generic((1 ? (void*) ((__INTPTR_TYPE__) ((expr) * 0)) : (int*) 0), int*: 1, void*: 0) -#else - #include - #define JSON_HEDLEY_IS_CONSTEXPR_(expr) _Generic((1 ? (void*) ((intptr_t) * 0) : (int*) 0), int*: 1, void*: 0) -#endif -# elif \ - defined(JSON_HEDLEY_GCC_VERSION) || \ - defined(JSON_HEDLEY_INTEL_VERSION) || \ - defined(JSON_HEDLEY_TINYC_VERSION) || \ - defined(JSON_HEDLEY_TI_ARMCL_VERSION) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(18,12,0) || \ - defined(JSON_HEDLEY_TI_CL2000_VERSION) || \ - defined(JSON_HEDLEY_TI_CL6X_VERSION) || \ - defined(JSON_HEDLEY_TI_CL7X_VERSION) || \ - defined(JSON_HEDLEY_TI_CLPRU_VERSION) || \ - defined(__clang__) -# define JSON_HEDLEY_IS_CONSTEXPR_(expr) ( \ - sizeof(void) != \ - sizeof(*( \ - 1 ? \ - ((void*) ((expr) * 0L) ) : \ -((struct { char v[sizeof(void) * 2]; } *) 1) \ - ) \ - ) \ - ) -# endif -#endif -#if defined(JSON_HEDLEY_IS_CONSTEXPR_) - #if !defined(JSON_HEDLEY_IS_CONSTANT) - #define JSON_HEDLEY_IS_CONSTANT(expr) JSON_HEDLEY_IS_CONSTEXPR_(expr) - #endif - #define JSON_HEDLEY_REQUIRE_CONSTEXPR(expr) (JSON_HEDLEY_IS_CONSTEXPR_(expr) ? (expr) : (-1)) -#else - #if !defined(JSON_HEDLEY_IS_CONSTANT) - #define JSON_HEDLEY_IS_CONSTANT(expr) (0) - #endif - #define JSON_HEDLEY_REQUIRE_CONSTEXPR(expr) (expr) -#endif - -#if defined(JSON_HEDLEY_BEGIN_C_DECLS) - #undef JSON_HEDLEY_BEGIN_C_DECLS -#endif -#if defined(JSON_HEDLEY_END_C_DECLS) - #undef JSON_HEDLEY_END_C_DECLS -#endif -#if defined(JSON_HEDLEY_C_DECL) - #undef JSON_HEDLEY_C_DECL -#endif -#if defined(__cplusplus) - #define JSON_HEDLEY_BEGIN_C_DECLS extern "C" { - #define JSON_HEDLEY_END_C_DECLS } - #define JSON_HEDLEY_C_DECL extern "C" -#else - #define JSON_HEDLEY_BEGIN_C_DECLS - #define JSON_HEDLEY_END_C_DECLS - #define JSON_HEDLEY_C_DECL -#endif - -#if defined(JSON_HEDLEY_STATIC_ASSERT) - #undef JSON_HEDLEY_STATIC_ASSERT -#endif -#if \ - !defined(__cplusplus) && ( \ - (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)) || \ - (JSON_HEDLEY_HAS_FEATURE(c_static_assert) && !defined(JSON_HEDLEY_INTEL_CL_VERSION)) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(6,0,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - defined(_Static_assert) \ - ) -# define JSON_HEDLEY_STATIC_ASSERT(expr, message) _Static_assert(expr, message) -#elif \ - (defined(__cplusplus) && (__cplusplus >= 201103L)) || \ - JSON_HEDLEY_MSVC_VERSION_CHECK(16,0,0) || \ - JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) -# define JSON_HEDLEY_STATIC_ASSERT(expr, message) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(static_assert(expr, message)) -#else -# define JSON_HEDLEY_STATIC_ASSERT(expr, message) -#endif - -#if defined(JSON_HEDLEY_NULL) - #undef JSON_HEDLEY_NULL -#endif -#if defined(__cplusplus) - #if __cplusplus >= 201103L - #define JSON_HEDLEY_NULL JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(nullptr) - #elif defined(NULL) - #define JSON_HEDLEY_NULL NULL - #else - #define JSON_HEDLEY_NULL JSON_HEDLEY_STATIC_CAST(void*, 0) - #endif -#elif defined(NULL) - #define JSON_HEDLEY_NULL NULL -#else - #define JSON_HEDLEY_NULL ((void*) 0) -#endif - -#if defined(JSON_HEDLEY_MESSAGE) - #undef JSON_HEDLEY_MESSAGE -#endif -#if JSON_HEDLEY_HAS_WARNING("-Wunknown-pragmas") -# define JSON_HEDLEY_MESSAGE(msg) \ - JSON_HEDLEY_DIAGNOSTIC_PUSH \ - JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS \ - JSON_HEDLEY_PRAGMA(message msg) \ - JSON_HEDLEY_DIAGNOSTIC_POP -#elif \ - JSON_HEDLEY_GCC_VERSION_CHECK(4,4,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) -# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message msg) -#elif JSON_HEDLEY_CRAY_VERSION_CHECK(5,0,0) -# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(_CRI message msg) -#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) -# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message(msg)) -#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,0,0) -# define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message(msg)) -#else -# define JSON_HEDLEY_MESSAGE(msg) -#endif - -#if defined(JSON_HEDLEY_WARNING) - #undef JSON_HEDLEY_WARNING -#endif -#if JSON_HEDLEY_HAS_WARNING("-Wunknown-pragmas") -# define JSON_HEDLEY_WARNING(msg) \ - JSON_HEDLEY_DIAGNOSTIC_PUSH \ - JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS \ - JSON_HEDLEY_PRAGMA(clang warning msg) \ - JSON_HEDLEY_DIAGNOSTIC_POP -#elif \ - JSON_HEDLEY_GCC_VERSION_CHECK(4,8,0) || \ - JSON_HEDLEY_PGI_VERSION_CHECK(18,4,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) -# define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_PRAGMA(GCC warning msg) -#elif \ - JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) || \ - JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) -# define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_PRAGMA(message(msg)) -#else -# define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_MESSAGE(msg) -#endif - -#if defined(JSON_HEDLEY_REQUIRE) - #undef JSON_HEDLEY_REQUIRE -#endif -#if defined(JSON_HEDLEY_REQUIRE_MSG) - #undef JSON_HEDLEY_REQUIRE_MSG -#endif -#if JSON_HEDLEY_HAS_ATTRIBUTE(diagnose_if) -# if JSON_HEDLEY_HAS_WARNING("-Wgcc-compat") -# define JSON_HEDLEY_REQUIRE(expr) \ - JSON_HEDLEY_DIAGNOSTIC_PUSH \ - _Pragma("clang diagnostic ignored \"-Wgcc-compat\"") \ - __attribute__((diagnose_if(!(expr), #expr, "error"))) \ - JSON_HEDLEY_DIAGNOSTIC_POP -# define JSON_HEDLEY_REQUIRE_MSG(expr,msg) \ - JSON_HEDLEY_DIAGNOSTIC_PUSH \ - _Pragma("clang diagnostic ignored \"-Wgcc-compat\"") \ - __attribute__((diagnose_if(!(expr), msg, "error"))) \ - JSON_HEDLEY_DIAGNOSTIC_POP -# else -# define JSON_HEDLEY_REQUIRE(expr) __attribute__((diagnose_if(!(expr), #expr, "error"))) -# define JSON_HEDLEY_REQUIRE_MSG(expr,msg) __attribute__((diagnose_if(!(expr), msg, "error"))) -# endif -#else -# define JSON_HEDLEY_REQUIRE(expr) -# define JSON_HEDLEY_REQUIRE_MSG(expr,msg) -#endif - -#if defined(JSON_HEDLEY_FLAGS) - #undef JSON_HEDLEY_FLAGS -#endif -#if JSON_HEDLEY_HAS_ATTRIBUTE(flag_enum) && (!defined(__cplusplus) || JSON_HEDLEY_HAS_WARNING("-Wbitfield-enum-conversion")) - #define JSON_HEDLEY_FLAGS __attribute__((__flag_enum__)) -#else - #define JSON_HEDLEY_FLAGS -#endif - -#if defined(JSON_HEDLEY_FLAGS_CAST) - #undef JSON_HEDLEY_FLAGS_CAST -#endif -#if JSON_HEDLEY_INTEL_VERSION_CHECK(19,0,0) -# define JSON_HEDLEY_FLAGS_CAST(T, expr) (__extension__ ({ \ - JSON_HEDLEY_DIAGNOSTIC_PUSH \ - _Pragma("warning(disable:188)") \ - ((T) (expr)); \ - JSON_HEDLEY_DIAGNOSTIC_POP \ - })) -#else -# define JSON_HEDLEY_FLAGS_CAST(T, expr) JSON_HEDLEY_STATIC_CAST(T, expr) -#endif - -#if defined(JSON_HEDLEY_EMPTY_BASES) - #undef JSON_HEDLEY_EMPTY_BASES -#endif -#if \ - (JSON_HEDLEY_MSVC_VERSION_CHECK(19,0,23918) && !JSON_HEDLEY_MSVC_VERSION_CHECK(20,0,0)) || \ - JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) - #define JSON_HEDLEY_EMPTY_BASES __declspec(empty_bases) -#else - #define JSON_HEDLEY_EMPTY_BASES -#endif - -/* Remaining macros are deprecated. */ - -#if defined(JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK) - #undef JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK -#endif -#if defined(__clang__) - #define JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK(major,minor,patch) (0) -#else - #define JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK(major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) -#endif - -#if defined(JSON_HEDLEY_CLANG_HAS_ATTRIBUTE) - #undef JSON_HEDLEY_CLANG_HAS_ATTRIBUTE -#endif -#define JSON_HEDLEY_CLANG_HAS_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_ATTRIBUTE(attribute) - -#if defined(JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE) - #undef JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE -#endif -#define JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) - -#if defined(JSON_HEDLEY_CLANG_HAS_BUILTIN) - #undef JSON_HEDLEY_CLANG_HAS_BUILTIN -#endif -#define JSON_HEDLEY_CLANG_HAS_BUILTIN(builtin) JSON_HEDLEY_HAS_BUILTIN(builtin) - -#if defined(JSON_HEDLEY_CLANG_HAS_FEATURE) - #undef JSON_HEDLEY_CLANG_HAS_FEATURE -#endif -#define JSON_HEDLEY_CLANG_HAS_FEATURE(feature) JSON_HEDLEY_HAS_FEATURE(feature) - -#if defined(JSON_HEDLEY_CLANG_HAS_EXTENSION) - #undef JSON_HEDLEY_CLANG_HAS_EXTENSION -#endif -#define JSON_HEDLEY_CLANG_HAS_EXTENSION(extension) JSON_HEDLEY_HAS_EXTENSION(extension) - -#if defined(JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE) - #undef JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE -#endif -#define JSON_HEDLEY_CLANG_HAS_DECLSPEC_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) - -#if defined(JSON_HEDLEY_CLANG_HAS_WARNING) - #undef JSON_HEDLEY_CLANG_HAS_WARNING -#endif -#define JSON_HEDLEY_CLANG_HAS_WARNING(warning) JSON_HEDLEY_HAS_WARNING(warning) - -#endif /* !defined(JSON_HEDLEY_VERSION) || (JSON_HEDLEY_VERSION < X) */ - - -// This file contains all internal macro definitions (except those affecting ABI) -// You MUST include macro_unscope.hpp at the end of json.hpp to undef all of them - -// #include - - -// exclude unsupported compilers -#if !defined(JSON_SKIP_UNSUPPORTED_COMPILER_CHECK) - #if defined(__clang__) - #if (__clang_major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__) < 30400 - #error "unsupported Clang version - see https://github.com/nlohmann/json#supported-compilers" - #endif - #elif defined(__GNUC__) && !(defined(__ICC) || defined(__INTEL_COMPILER)) - #if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) < 40800 - #error "unsupported GCC version - see https://github.com/nlohmann/json#supported-compilers" - #endif - #endif -#endif - -// C++ language standard detection -// if the user manually specified the used c++ version this is skipped -#if !defined(JSON_HAS_CPP_23) && !defined(JSON_HAS_CPP_20) && !defined(JSON_HAS_CPP_17) && !defined(JSON_HAS_CPP_14) && !defined(JSON_HAS_CPP_11) - #if (defined(__cplusplus) && __cplusplus > 202002L) || (defined(_MSVC_LANG) && _MSVC_LANG > 202002L) - #define JSON_HAS_CPP_23 - #define JSON_HAS_CPP_20 - #define JSON_HAS_CPP_17 - #define JSON_HAS_CPP_14 - #elif (defined(__cplusplus) && __cplusplus > 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG > 201703L) - #define JSON_HAS_CPP_20 - #define JSON_HAS_CPP_17 - #define JSON_HAS_CPP_14 - #elif (defined(__cplusplus) && __cplusplus > 201402L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464 - #define JSON_HAS_CPP_17 - #define JSON_HAS_CPP_14 - #elif (defined(__cplusplus) && __cplusplus > 201103L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1) - #define JSON_HAS_CPP_14 - #endif - // the cpp 11 flag is always specified because it is the minimal required version - #define JSON_HAS_CPP_11 -#endif - -#ifdef __has_include - #if __has_include() - #include - #endif -#endif - -#if !defined(JSON_HAS_FILESYSTEM) && !defined(JSON_HAS_EXPERIMENTAL_FILESYSTEM) - #ifdef JSON_HAS_CPP_17 - #if defined(__cpp_lib_filesystem) - #define JSON_HAS_FILESYSTEM 1 - #elif defined(__cpp_lib_experimental_filesystem) - #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 1 - #elif !defined(__has_include) - #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 1 - #elif __has_include() - #define JSON_HAS_FILESYSTEM 1 - #elif __has_include() - #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 1 - #endif - - // std::filesystem does not work on MinGW GCC 8: https://sourceforge.net/p/mingw-w64/bugs/737/ - #if defined(__MINGW32__) && defined(__GNUC__) && __GNUC__ == 8 - #undef JSON_HAS_FILESYSTEM - #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM - #endif - - // no filesystem support before GCC 8: https://en.cppreference.com/w/cpp/compiler_support - #if defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 8 - #undef JSON_HAS_FILESYSTEM - #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM - #endif - - // no filesystem support before Clang 7: https://en.cppreference.com/w/cpp/compiler_support - #if defined(__clang_major__) && __clang_major__ < 7 - #undef JSON_HAS_FILESYSTEM - #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM - #endif - - // no filesystem support before MSVC 19.14: https://en.cppreference.com/w/cpp/compiler_support - #if defined(_MSC_VER) && _MSC_VER < 1914 - #undef JSON_HAS_FILESYSTEM - #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM - #endif - - // no filesystem support before iOS 13 - #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED < 130000 - #undef JSON_HAS_FILESYSTEM - #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM - #endif - - // no filesystem support before macOS Catalina - #if defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED < 101500 - #undef JSON_HAS_FILESYSTEM - #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM - #endif - #endif -#endif - -#ifndef JSON_HAS_EXPERIMENTAL_FILESYSTEM - #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 0 -#endif - -#ifndef JSON_HAS_FILESYSTEM - #define JSON_HAS_FILESYSTEM 0 -#endif - -#ifndef JSON_HAS_THREE_WAY_COMPARISON - #if defined(__cpp_impl_three_way_comparison) && __cpp_impl_three_way_comparison >= 201907L \ - && defined(__cpp_lib_three_way_comparison) && __cpp_lib_three_way_comparison >= 201907L - #define JSON_HAS_THREE_WAY_COMPARISON 1 - #else - #define JSON_HAS_THREE_WAY_COMPARISON 0 - #endif -#endif - -#ifndef JSON_HAS_RANGES - // ranges header shipping in GCC 11.1.0 (released 2021-04-27) has syntax error - #if defined(__GLIBCXX__) && __GLIBCXX__ == 20210427 - #define JSON_HAS_RANGES 0 - #elif defined(__cpp_lib_ranges) - #define JSON_HAS_RANGES 1 - #else - #define JSON_HAS_RANGES 0 - #endif -#endif - -#ifndef JSON_HAS_STATIC_RTTI - #if !defined(_HAS_STATIC_RTTI) || _HAS_STATIC_RTTI != 0 - #define JSON_HAS_STATIC_RTTI 1 - #else - #define JSON_HAS_STATIC_RTTI 0 - #endif -#endif - -#ifdef JSON_HAS_CPP_17 - #define JSON_INLINE_VARIABLE inline -#else - #define JSON_INLINE_VARIABLE -#endif - -#if JSON_HEDLEY_HAS_ATTRIBUTE(no_unique_address) - #define JSON_NO_UNIQUE_ADDRESS [[no_unique_address]] -#else - #define JSON_NO_UNIQUE_ADDRESS -#endif - -// disable documentation warnings on clang -#if defined(__clang__) - #pragma clang diagnostic push - #pragma clang diagnostic ignored "-Wdocumentation" - #pragma clang diagnostic ignored "-Wdocumentation-unknown-command" -#endif - -// allow disabling exceptions -#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION) - #define JSON_THROW(exception) throw exception - #define JSON_TRY try - #define JSON_CATCH(exception) catch(exception) - #define JSON_INTERNAL_CATCH(exception) catch(exception) -#else - #include - #define JSON_THROW(exception) std::abort() - #define JSON_TRY if(true) - #define JSON_CATCH(exception) if(false) - #define JSON_INTERNAL_CATCH(exception) if(false) -#endif - -// override exception macros -#if defined(JSON_THROW_USER) - #undef JSON_THROW - #define JSON_THROW JSON_THROW_USER -#endif -#if defined(JSON_TRY_USER) - #undef JSON_TRY - #define JSON_TRY JSON_TRY_USER -#endif -#if defined(JSON_CATCH_USER) - #undef JSON_CATCH - #define JSON_CATCH JSON_CATCH_USER - #undef JSON_INTERNAL_CATCH - #define JSON_INTERNAL_CATCH JSON_CATCH_USER -#endif -#if defined(JSON_INTERNAL_CATCH_USER) - #undef JSON_INTERNAL_CATCH - #define JSON_INTERNAL_CATCH JSON_INTERNAL_CATCH_USER -#endif - -// allow overriding assert -#if !defined(JSON_ASSERT) - #include // assert - #define JSON_ASSERT(x) assert(x) -#endif - -// allow to access some private functions (needed by the test suite) -#if defined(JSON_TESTS_PRIVATE) - #define JSON_PRIVATE_UNLESS_TESTED public -#else - #define JSON_PRIVATE_UNLESS_TESTED private -#endif - -/*! -@brief macro to briefly define a mapping between an enum and JSON -@def NLOHMANN_JSON_SERIALIZE_ENUM -@since version 3.4.0 -*/ -#define NLOHMANN_JSON_SERIALIZE_ENUM(ENUM_TYPE, ...) \ - template \ - inline void to_json(BasicJsonType& j, const ENUM_TYPE& e) \ - { \ - /* NOLINTNEXTLINE(modernize-type-traits) we use C++11 */ \ - static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ - /* NOLINTNEXTLINE(modernize-avoid-c-arrays) we don't want to depend on */ \ - static const std::pair m[] = __VA_ARGS__; \ - auto it = std::find_if(std::begin(m), std::end(m), \ - [e](const std::pair& ej_pair) -> bool \ - { \ - return ej_pair.first == e; \ - }); \ - j = ((it != std::end(m)) ? it : std::begin(m))->second; \ - } \ - template \ - inline void from_json(const BasicJsonType& j, ENUM_TYPE& e) \ - { \ - /* NOLINTNEXTLINE(modernize-type-traits) we use C++11 */ \ - static_assert(std::is_enum::value, #ENUM_TYPE " must be an enum!"); \ - /* NOLINTNEXTLINE(modernize-avoid-c-arrays) we don't want to depend on */ \ - static const std::pair m[] = __VA_ARGS__; \ - auto it = std::find_if(std::begin(m), std::end(m), \ - [&j](const std::pair& ej_pair) -> bool \ - { \ - return ej_pair.second == j; \ - }); \ - e = ((it != std::end(m)) ? it : std::begin(m))->first; \ - } - -// Ugly macros to avoid uglier copy-paste when specializing basic_json. They -// may be removed in the future once the class is split. - -#define NLOHMANN_BASIC_JSON_TPL_DECLARATION \ - template class ObjectType, \ - template class ArrayType, \ - class StringType, class BooleanType, class NumberIntegerType, \ - class NumberUnsignedType, class NumberFloatType, \ - template class AllocatorType, \ - template class JSONSerializer, \ - class BinaryType, \ - class CustomBaseClass> - -#define NLOHMANN_BASIC_JSON_TPL \ - basic_json - -// Macros to simplify conversion from/to types - -#define NLOHMANN_JSON_EXPAND( x ) x -#define NLOHMANN_JSON_GET_MACRO(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, _57, _58, _59, _60, _61, _62, _63, _64, NAME,...) NAME -#define NLOHMANN_JSON_PASTE(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_GET_MACRO(__VA_ARGS__, \ - NLOHMANN_JSON_PASTE64, \ - NLOHMANN_JSON_PASTE63, \ - NLOHMANN_JSON_PASTE62, \ - NLOHMANN_JSON_PASTE61, \ - NLOHMANN_JSON_PASTE60, \ - NLOHMANN_JSON_PASTE59, \ - NLOHMANN_JSON_PASTE58, \ - NLOHMANN_JSON_PASTE57, \ - NLOHMANN_JSON_PASTE56, \ - NLOHMANN_JSON_PASTE55, \ - NLOHMANN_JSON_PASTE54, \ - NLOHMANN_JSON_PASTE53, \ - NLOHMANN_JSON_PASTE52, \ - NLOHMANN_JSON_PASTE51, \ - NLOHMANN_JSON_PASTE50, \ - NLOHMANN_JSON_PASTE49, \ - NLOHMANN_JSON_PASTE48, \ - NLOHMANN_JSON_PASTE47, \ - NLOHMANN_JSON_PASTE46, \ - NLOHMANN_JSON_PASTE45, \ - NLOHMANN_JSON_PASTE44, \ - NLOHMANN_JSON_PASTE43, \ - NLOHMANN_JSON_PASTE42, \ - NLOHMANN_JSON_PASTE41, \ - NLOHMANN_JSON_PASTE40, \ - NLOHMANN_JSON_PASTE39, \ - NLOHMANN_JSON_PASTE38, \ - NLOHMANN_JSON_PASTE37, \ - NLOHMANN_JSON_PASTE36, \ - NLOHMANN_JSON_PASTE35, \ - NLOHMANN_JSON_PASTE34, \ - NLOHMANN_JSON_PASTE33, \ - NLOHMANN_JSON_PASTE32, \ - NLOHMANN_JSON_PASTE31, \ - NLOHMANN_JSON_PASTE30, \ - NLOHMANN_JSON_PASTE29, \ - NLOHMANN_JSON_PASTE28, \ - NLOHMANN_JSON_PASTE27, \ - NLOHMANN_JSON_PASTE26, \ - NLOHMANN_JSON_PASTE25, \ - NLOHMANN_JSON_PASTE24, \ - NLOHMANN_JSON_PASTE23, \ - NLOHMANN_JSON_PASTE22, \ - NLOHMANN_JSON_PASTE21, \ - NLOHMANN_JSON_PASTE20, \ - NLOHMANN_JSON_PASTE19, \ - NLOHMANN_JSON_PASTE18, \ - NLOHMANN_JSON_PASTE17, \ - NLOHMANN_JSON_PASTE16, \ - NLOHMANN_JSON_PASTE15, \ - NLOHMANN_JSON_PASTE14, \ - NLOHMANN_JSON_PASTE13, \ - NLOHMANN_JSON_PASTE12, \ - NLOHMANN_JSON_PASTE11, \ - NLOHMANN_JSON_PASTE10, \ - NLOHMANN_JSON_PASTE9, \ - NLOHMANN_JSON_PASTE8, \ - NLOHMANN_JSON_PASTE7, \ - NLOHMANN_JSON_PASTE6, \ - NLOHMANN_JSON_PASTE5, \ - NLOHMANN_JSON_PASTE4, \ - NLOHMANN_JSON_PASTE3, \ - NLOHMANN_JSON_PASTE2, \ - NLOHMANN_JSON_PASTE1)(__VA_ARGS__)) -#define NLOHMANN_JSON_PASTE2(func, v1) func(v1) -#define NLOHMANN_JSON_PASTE3(func, v1, v2) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE2(func, v2) -#define NLOHMANN_JSON_PASTE4(func, v1, v2, v3) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE3(func, v2, v3) -#define NLOHMANN_JSON_PASTE5(func, v1, v2, v3, v4) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE4(func, v2, v3, v4) -#define NLOHMANN_JSON_PASTE6(func, v1, v2, v3, v4, v5) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE5(func, v2, v3, v4, v5) -#define NLOHMANN_JSON_PASTE7(func, v1, v2, v3, v4, v5, v6) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE6(func, v2, v3, v4, v5, v6) -#define NLOHMANN_JSON_PASTE8(func, v1, v2, v3, v4, v5, v6, v7) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE7(func, v2, v3, v4, v5, v6, v7) -#define NLOHMANN_JSON_PASTE9(func, v1, v2, v3, v4, v5, v6, v7, v8) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE8(func, v2, v3, v4, v5, v6, v7, v8) -#define NLOHMANN_JSON_PASTE10(func, v1, v2, v3, v4, v5, v6, v7, v8, v9) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE9(func, v2, v3, v4, v5, v6, v7, v8, v9) -#define NLOHMANN_JSON_PASTE11(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE10(func, v2, v3, v4, v5, v6, v7, v8, v9, v10) -#define NLOHMANN_JSON_PASTE12(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE11(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) -#define NLOHMANN_JSON_PASTE13(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE12(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) -#define NLOHMANN_JSON_PASTE14(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE13(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13) -#define NLOHMANN_JSON_PASTE15(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE14(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14) -#define NLOHMANN_JSON_PASTE16(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE15(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) -#define NLOHMANN_JSON_PASTE17(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE16(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16) -#define NLOHMANN_JSON_PASTE18(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE17(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17) -#define NLOHMANN_JSON_PASTE19(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE18(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18) -#define NLOHMANN_JSON_PASTE20(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE19(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19) -#define NLOHMANN_JSON_PASTE21(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE20(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20) -#define NLOHMANN_JSON_PASTE22(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE21(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21) -#define NLOHMANN_JSON_PASTE23(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE22(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22) -#define NLOHMANN_JSON_PASTE24(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE23(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23) -#define NLOHMANN_JSON_PASTE25(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE24(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24) -#define NLOHMANN_JSON_PASTE26(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE25(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25) -#define NLOHMANN_JSON_PASTE27(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE26(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26) -#define NLOHMANN_JSON_PASTE28(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE27(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27) -#define NLOHMANN_JSON_PASTE29(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE28(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28) -#define NLOHMANN_JSON_PASTE30(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE29(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29) -#define NLOHMANN_JSON_PASTE31(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE30(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30) -#define NLOHMANN_JSON_PASTE32(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE31(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31) -#define NLOHMANN_JSON_PASTE33(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE32(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32) -#define NLOHMANN_JSON_PASTE34(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE33(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33) -#define NLOHMANN_JSON_PASTE35(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE34(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34) -#define NLOHMANN_JSON_PASTE36(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE35(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35) -#define NLOHMANN_JSON_PASTE37(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE36(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36) -#define NLOHMANN_JSON_PASTE38(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE37(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37) -#define NLOHMANN_JSON_PASTE39(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE38(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38) -#define NLOHMANN_JSON_PASTE40(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE39(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39) -#define NLOHMANN_JSON_PASTE41(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE40(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40) -#define NLOHMANN_JSON_PASTE42(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE41(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41) -#define NLOHMANN_JSON_PASTE43(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE42(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42) -#define NLOHMANN_JSON_PASTE44(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE43(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43) -#define NLOHMANN_JSON_PASTE45(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE44(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44) -#define NLOHMANN_JSON_PASTE46(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE45(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45) -#define NLOHMANN_JSON_PASTE47(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE46(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46) -#define NLOHMANN_JSON_PASTE48(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE47(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47) -#define NLOHMANN_JSON_PASTE49(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE48(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48) -#define NLOHMANN_JSON_PASTE50(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE49(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49) -#define NLOHMANN_JSON_PASTE51(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE50(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50) -#define NLOHMANN_JSON_PASTE52(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE51(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51) -#define NLOHMANN_JSON_PASTE53(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE52(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52) -#define NLOHMANN_JSON_PASTE54(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE53(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53) -#define NLOHMANN_JSON_PASTE55(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE54(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54) -#define NLOHMANN_JSON_PASTE56(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE55(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55) -#define NLOHMANN_JSON_PASTE57(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE56(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56) -#define NLOHMANN_JSON_PASTE58(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE57(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57) -#define NLOHMANN_JSON_PASTE59(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE58(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58) -#define NLOHMANN_JSON_PASTE60(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE59(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59) -#define NLOHMANN_JSON_PASTE61(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE60(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60) -#define NLOHMANN_JSON_PASTE62(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE61(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61) -#define NLOHMANN_JSON_PASTE63(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE62(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62) -#define NLOHMANN_JSON_PASTE64(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62, v63) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE63(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62, v63) - -#define NLOHMANN_JSON_TO(v1) nlohmann_json_j[#v1] = nlohmann_json_t.v1; -#define NLOHMANN_JSON_FROM(v1) nlohmann_json_j.at(#v1).get_to(nlohmann_json_t.v1); -#define NLOHMANN_JSON_FROM_WITH_DEFAULT(v1) nlohmann_json_t.v1 = !nlohmann_json_j.is_null() ? nlohmann_json_j.value(#v1, nlohmann_json_default_obj.v1) : nlohmann_json_default_obj.v1; - -/*! -@brief macro -@def NLOHMANN_DEFINE_TYPE_INTRUSIVE -@since version 3.9.0 -@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_intrusive/ -*/ -#define NLOHMANN_DEFINE_TYPE_INTRUSIVE(Type, ...) \ - template::value, int> = 0> \ - friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ - template::value, int> = 0> \ - friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } - -/*! -@brief macro -@def NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT -@since version 3.11.0 -@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_intrusive/ -*/ -#define NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Type, ...) \ - template::value, int> = 0> \ - friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ - template::value, int> = 0> \ - friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } - -/*! -@brief macro -@def NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE -@since version 3.11.3 -@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_intrusive/ -*/ -#define NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(Type, ...) \ - template::value, int> = 0> \ - friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } - -/*! -@brief macro -@def NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE -@since version 3.9.0 -@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_non_intrusive/ -*/ -#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Type, ...) \ - template::value, int> = 0> \ - void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ - template::value, int> = 0> \ - void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } - -/*! -@brief macro -@def NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT -@since version 3.11.0 -@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_non_intrusive/ -*/ -#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(Type, ...) \ - template::value, int> = 0> \ - void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ - template::value, int> = 0> \ - void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } - -/*! -@brief macro -@def NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE -@since version 3.11.3 -@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_non_intrusive/ -*/ -#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE(Type, ...) \ - template::value, int> = 0> \ - void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } - -/*! -@brief macro -@def NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE -@since version 3.12.0 -@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/ -*/ -#define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE(Type, BaseType, ...) \ - template::value, int> = 0> \ - friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ - template::value, int> = 0> \ - friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } - -/*! -@brief macro -@def NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT -@since version 3.12.0 -@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/ -*/ -#define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT(Type, BaseType, ...) \ - template::value, int> = 0> \ - friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ - template::value, int> = 0> \ - friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } - -/*! -@brief macro -@def NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE -@since version 3.12.0 -@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/ -*/ -#define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE(Type, BaseType, ...) \ - template::value, int> = 0> \ - friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } - -/*! -@brief macro -@def NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE -@since version 3.12.0 -@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/ -*/ -#define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE(Type, BaseType, ...) \ - template::value, int> = 0> \ - void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ - template::value, int> = 0> \ - void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } - -/*! -@brief macro -@def NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT -@since version 3.12.0 -@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/ -*/ -#define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT(Type, BaseType, ...) \ - template::value, int> = 0> \ - void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ - template::value, int> = 0> \ - void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast(nlohmann_json_t)); const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } - -/*! -@brief macro -@def NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE -@since version 3.12.0 -@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/ -*/ -#define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE(Type, BaseType, ...) \ - template::value, int> = 0> \ - void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } - -// inspired from https://stackoverflow.com/a/26745591 -// allows calling any std function as if (e.g., with begin): -// using std::begin; begin(x); -// -// it allows using the detected idiom to retrieve the return type -// of such an expression -#define NLOHMANN_CAN_CALL_STD_FUNC_IMPL(std_name) \ - namespace detail { \ - using std::std_name; \ - \ - template \ - using result_of_##std_name = decltype(std_name(std::declval()...)); \ - } \ - \ - namespace detail2 { \ - struct std_name##_tag \ - { \ - }; \ - \ - template \ - std_name##_tag std_name(T&&...); \ - \ - template \ - using result_of_##std_name = decltype(std_name(std::declval()...)); \ - \ - template \ - struct would_call_std_##std_name \ - { \ - static constexpr auto const value = ::nlohmann::detail:: \ - is_detected_exact::value; \ - }; \ - } /* namespace detail2 */ \ - \ - template \ - struct would_call_std_##std_name : detail2::would_call_std_##std_name \ - { \ - } - -#ifndef JSON_USE_IMPLICIT_CONVERSIONS - #define JSON_USE_IMPLICIT_CONVERSIONS 1 -#endif - -#if JSON_USE_IMPLICIT_CONVERSIONS - #define JSON_EXPLICIT -#else - #define JSON_EXPLICIT explicit -#endif - -#ifndef JSON_DISABLE_ENUM_SERIALIZATION - #define JSON_DISABLE_ENUM_SERIALIZATION 0 -#endif - -#ifndef JSON_USE_GLOBAL_UDLS - #define JSON_USE_GLOBAL_UDLS 1 -#endif - -#if JSON_HAS_THREE_WAY_COMPARISON - #include // partial_ordering -#endif - -NLOHMANN_JSON_NAMESPACE_BEGIN -namespace detail -{ - -/////////////////////////// -// JSON type enumeration // -/////////////////////////// - -/*! -@brief the JSON type enumeration - -This enumeration collects the different JSON types. It is internally used to -distinguish the stored values, and the functions @ref basic_json::is_null(), -@ref basic_json::is_object(), @ref basic_json::is_array(), -@ref basic_json::is_string(), @ref basic_json::is_boolean(), -@ref basic_json::is_number() (with @ref basic_json::is_number_integer(), -@ref basic_json::is_number_unsigned(), and @ref basic_json::is_number_float()), -@ref basic_json::is_discarded(), @ref basic_json::is_primitive(), and -@ref basic_json::is_structured() rely on it. - -@note There are three enumeration entries (number_integer, number_unsigned, and -number_float), because the library distinguishes these three types for numbers: -@ref basic_json::number_unsigned_t is used for unsigned integers, -@ref basic_json::number_integer_t is used for signed integers, and -@ref basic_json::number_float_t is used for floating-point numbers or to -approximate integers which do not fit in the limits of their respective type. - -@sa see @ref basic_json::basic_json(const value_t value_type) -- create a JSON -value with the default value for a given type - -@since version 1.0.0 -*/ -enum class value_t : std::uint8_t -{ - null, ///< null value - object, ///< object (unordered set of name/value pairs) - array, ///< array (ordered collection of values) - string, ///< string value - boolean, ///< boolean value - number_integer, ///< number value (signed integer) - number_unsigned, ///< number value (unsigned integer) - number_float, ///< number value (floating-point) - binary, ///< binary array (ordered collection of bytes) - discarded ///< discarded by the parser callback function -}; - -/*! -@brief comparison operator for JSON types - -Returns an ordering that is similar to Python: -- order: null < boolean < number < object < array < string < binary -- furthermore, each type is not smaller than itself -- discarded values are not comparable -- binary is represented as a b"" string in python and directly comparable to a - string; however, making a binary array directly comparable with a string would - be surprising behavior in a JSON file. - -@since version 1.0.0 -*/ -#if JSON_HAS_THREE_WAY_COMPARISON - inline std::partial_ordering operator<=>(const value_t lhs, const value_t rhs) noexcept // *NOPAD* -#else - inline bool operator<(const value_t lhs, const value_t rhs) noexcept -#endif -{ - static constexpr std::array order = {{ - 0 /* null */, 3 /* object */, 4 /* array */, 5 /* string */, - 1 /* boolean */, 2 /* integer */, 2 /* unsigned */, 2 /* float */, - 6 /* binary */ - } - }; - - const auto l_index = static_cast(lhs); - const auto r_index = static_cast(rhs); -#if JSON_HAS_THREE_WAY_COMPARISON - if (l_index < order.size() && r_index < order.size()) - { - return order[l_index] <=> order[r_index]; // *NOPAD* - } - return std::partial_ordering::unordered; -#else - return l_index < order.size() && r_index < order.size() && order[l_index] < order[r_index]; -#endif -} - -// GCC selects the built-in operator< over an operator rewritten from -// a user-defined spaceship operator -// Clang, MSVC, and ICC select the rewritten candidate -// (see GCC bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105200) -#if JSON_HAS_THREE_WAY_COMPARISON && defined(__GNUC__) -inline bool operator<(const value_t lhs, const value_t rhs) noexcept -{ - return std::is_lt(lhs <=> rhs); // *NOPAD* -} -#endif - -} // namespace detail -NLOHMANN_JSON_NAMESPACE_END - -// #include -// __ _____ _____ _____ -// __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.12.0 -// |_____|_____|_____|_|___| https://github.com/nlohmann/json -// -// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann -// SPDX-License-Identifier: MIT - - - -// #include - - -NLOHMANN_JSON_NAMESPACE_BEGIN -namespace detail -{ - -/*! -@brief replace all occurrences of a substring by another string - -@param[in,out] s the string to manipulate; changed so that all - occurrences of @a f are replaced with @a t -@param[in] f the substring to replace with @a t -@param[in] t the string to replace @a f - -@pre The search string @a f must not be empty. **This precondition is -enforced with an assertion.** - -@since version 2.0.0 -*/ -template -inline void replace_substring(StringType& s, const StringType& f, - const StringType& t) -{ - JSON_ASSERT(!f.empty()); - for (auto pos = s.find(f); // find first occurrence of f - pos != StringType::npos; // make sure f was found - s.replace(pos, f.size(), t), // replace with t, and - pos = s.find(f, pos + t.size())) // find next occurrence of f - {} -} - -/*! - * @brief string escaping as described in RFC 6901 (Sect. 4) - * @param[in] s string to escape - * @return escaped string - * - * Note the order of escaping "~" to "~0" and "/" to "~1" is important. - */ -template -inline StringType escape(StringType s) -{ - replace_substring(s, StringType{"~"}, StringType{"~0"}); - replace_substring(s, StringType{"/"}, StringType{"~1"}); - return s; -} - -/*! - * @brief string unescaping as described in RFC 6901 (Sect. 4) - * @param[in] s string to unescape - * @return unescaped string - * - * Note the order of escaping "~1" to "/" and "~0" to "~" is important. - */ -template -static void unescape(StringType& s) -{ - replace_substring(s, StringType{"~1"}, StringType{"/"}); - replace_substring(s, StringType{"~0"}, StringType{"~"}); -} - -} // namespace detail -NLOHMANN_JSON_NAMESPACE_END - -// #include -// __ _____ _____ _____ -// __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.12.0 -// |_____|_____|_____|_|___| https://github.com/nlohmann/json -// -// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann -// SPDX-License-Identifier: MIT - - - -#include // size_t - -// #include - - -NLOHMANN_JSON_NAMESPACE_BEGIN -namespace detail -{ - -/// struct to capture the start position of the current token -struct position_t -{ - /// the total number of characters read - std::size_t chars_read_total = 0; - /// the number of characters read in the current line - std::size_t chars_read_current_line = 0; - /// the number of lines read - std::size_t lines_read = 0; - - /// conversion to size_t to preserve SAX interface - constexpr operator size_t() const - { - return chars_read_total; - } -}; - -} // namespace detail -NLOHMANN_JSON_NAMESPACE_END - -// #include - -// #include -// __ _____ _____ _____ -// __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.12.0 -// |_____|_____|_____|_|___| https://github.com/nlohmann/json -// -// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann -// SPDX-FileCopyrightText: 2018 The Abseil Authors -// SPDX-License-Identifier: MIT - - - -#include // array -#include // size_t -#include // conditional, enable_if, false_type, integral_constant, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type -#include // index_sequence, make_index_sequence, index_sequence_for - -// #include - - -NLOHMANN_JSON_NAMESPACE_BEGIN -namespace detail -{ - -template -using uncvref_t = typename std::remove_cv::type>::type; - -#ifdef JSON_HAS_CPP_14 - -// the following utilities are natively available in C++14 -using std::enable_if_t; -using std::index_sequence; -using std::make_index_sequence; -using std::index_sequence_for; - -#else - -// alias templates to reduce boilerplate -template -using enable_if_t = typename std::enable_if::type; - -// The following code is taken from https://github.com/abseil/abseil-cpp/blob/10cb35e459f5ecca5b2ff107635da0bfa41011b4/absl/utility/utility.h -// which is part of Google Abseil (https://github.com/abseil/abseil-cpp), licensed under the Apache License 2.0. - -//// START OF CODE FROM GOOGLE ABSEIL - -// integer_sequence -// -// Class template representing a compile-time integer sequence. An instantiation -// of `integer_sequence` has a sequence of integers encoded in its -// type through its template arguments (which is a common need when -// working with C++11 variadic templates). `absl::integer_sequence` is designed -// to be a drop-in replacement for C++14's `std::integer_sequence`. -// -// Example: -// -// template< class T, T... Ints > -// void user_function(integer_sequence); -// -// int main() -// { -// // user_function's `T` will be deduced to `int` and `Ints...` -// // will be deduced to `0, 1, 2, 3, 4`. -// user_function(make_integer_sequence()); -// } -template -struct integer_sequence -{ - using value_type = T; - static constexpr std::size_t size() noexcept - { - return sizeof...(Ints); - } -}; - -// index_sequence -// -// A helper template for an `integer_sequence` of `size_t`, -// `absl::index_sequence` is designed to be a drop-in replacement for C++14's -// `std::index_sequence`. -template -using index_sequence = integer_sequence; - -namespace utility_internal -{ - -template -struct Extend; - -// Note that SeqSize == sizeof...(Ints). It's passed explicitly for efficiency. -template -struct Extend, SeqSize, 0> -{ - using type = integer_sequence < T, Ints..., (Ints + SeqSize)... >; -}; - -template -struct Extend, SeqSize, 1> -{ - using type = integer_sequence < T, Ints..., (Ints + SeqSize)..., 2 * SeqSize >; -}; - -// Recursion helper for 'make_integer_sequence'. -// 'Gen::type' is an alias for 'integer_sequence'. -template -struct Gen -{ - using type = - typename Extend < typename Gen < T, N / 2 >::type, N / 2, N % 2 >::type; -}; - -template -struct Gen -{ - using type = integer_sequence; -}; - -} // namespace utility_internal - -// Compile-time sequences of integers - -// make_integer_sequence -// -// This template alias is equivalent to -// `integer_sequence`, and is designed to be a drop-in -// replacement for C++14's `std::make_integer_sequence`. -template -using make_integer_sequence = typename utility_internal::Gen::type; - -// make_index_sequence -// -// This template alias is equivalent to `index_sequence<0, 1, ..., N-1>`, -// and is designed to be a drop-in replacement for C++14's -// `std::make_index_sequence`. -template -using make_index_sequence = make_integer_sequence; - -// index_sequence_for -// -// Converts a typename pack into an index sequence of the same length, and -// is designed to be a drop-in replacement for C++14's -// `std::index_sequence_for()` -template -using index_sequence_for = make_index_sequence; - -//// END OF CODE FROM GOOGLE ABSEIL - -#endif - -// dispatch utility (taken from ranges-v3) -template struct priority_tag : priority_tag < N - 1 > {}; -template<> struct priority_tag<0> {}; - -// taken from ranges-v3 -template -struct static_const -{ - static JSON_INLINE_VARIABLE constexpr T value{}; -}; - -#ifndef JSON_HAS_CPP_17 - template - constexpr T static_const::value; -#endif - -template -constexpr std::array make_array(Args&& ... args) -{ - return std::array {{static_cast(std::forward(args))...}}; -} - -} // namespace detail -NLOHMANN_JSON_NAMESPACE_END - -// #include -// __ _____ _____ _____ -// __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.12.0 -// |_____|_____|_____|_|___| https://github.com/nlohmann/json -// -// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann -// SPDX-License-Identifier: MIT - - - -#include // numeric_limits -#include // char_traits -#include // tuple -#include // false_type, is_constructible, is_integral, is_same, true_type -#include // declval - -// #include -// __ _____ _____ _____ -// __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.12.0 -// |_____|_____|_____|_|___| https://github.com/nlohmann/json -// -// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann -// SPDX-License-Identifier: MIT - - - -#include // random_access_iterator_tag - -// #include - -// #include - -// #include - - -NLOHMANN_JSON_NAMESPACE_BEGIN -namespace detail -{ - -template -struct iterator_types {}; - -template -struct iterator_types < - It, - void_t> -{ - using difference_type = typename It::difference_type; - using value_type = typename It::value_type; - using pointer = typename It::pointer; - using reference = typename It::reference; - using iterator_category = typename It::iterator_category; -}; - -// This is required as some compilers implement std::iterator_traits in a way that -// doesn't work with SFINAE. See https://github.com/nlohmann/json/issues/1341. -template -struct iterator_traits -{ -}; - -template -struct iterator_traits < T, enable_if_t < !std::is_pointer::value >> - : iterator_types -{ -}; - -template -struct iterator_traits::value>> -{ - using iterator_category = std::random_access_iterator_tag; - using value_type = T; - using difference_type = ptrdiff_t; - using pointer = T*; - using reference = T&; -}; - -} // namespace detail -NLOHMANN_JSON_NAMESPACE_END - -// #include - -// #include -// __ _____ _____ _____ -// __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.12.0 -// |_____|_____|_____|_|___| https://github.com/nlohmann/json -// -// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann -// SPDX-License-Identifier: MIT - - - -// #include - - -NLOHMANN_JSON_NAMESPACE_BEGIN - -NLOHMANN_CAN_CALL_STD_FUNC_IMPL(begin); - -NLOHMANN_JSON_NAMESPACE_END - -// #include -// __ _____ _____ _____ -// __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.12.0 -// |_____|_____|_____|_|___| https://github.com/nlohmann/json -// -// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann -// SPDX-License-Identifier: MIT - - - -// #include - - -NLOHMANN_JSON_NAMESPACE_BEGIN - -NLOHMANN_CAN_CALL_STD_FUNC_IMPL(end); - -NLOHMANN_JSON_NAMESPACE_END - -// #include - -// #include - -// #include -// __ _____ _____ _____ -// __| | __| | | | JSON for Modern C++ -// | | |__ | | | | | | version 3.12.0 -// |_____|_____|_____|_|___| https://github.com/nlohmann/json -// -// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann -// SPDX-License-Identifier: MIT - -#ifndef INCLUDE_NLOHMANN_JSON_FWD_HPP_ - #define INCLUDE_NLOHMANN_JSON_FWD_HPP_ - - #include // int64_t, uint64_t - #include // map - #include // allocator - #include // string - #include // vector - - // #include - - - /*! - @brief namespace for Niels Lohmann - @see https://github.com/nlohmann - @since version 1.0.0 - */ - NLOHMANN_JSON_NAMESPACE_BEGIN - - /*! - @brief default JSONSerializer template argument - - This serializer ignores the template arguments and uses ADL - ([argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl)) - for serialization. - */ - template - struct adl_serializer; - - /// a class to store JSON values - /// @sa https://json.nlohmann.me/api/basic_json/ - template class ObjectType = - std::map, - template class ArrayType = std::vector, - class StringType = std::string, class BooleanType = bool, - class NumberIntegerType = std::int64_t, - class NumberUnsignedType = std::uint64_t, - class NumberFloatType = double, - template class AllocatorType = std::allocator, - template class JSONSerializer = - adl_serializer, - class BinaryType = std::vector, // cppcheck-suppress syntaxError - class CustomBaseClass = void> - class basic_json; - - /// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document - /// @sa https://json.nlohmann.me/api/json_pointer/ - template - class json_pointer; - - /*! - @brief default specialization - @sa https://json.nlohmann.me/api/json/ - */ - using json = basic_json<>; - - /// @brief a minimal map-like container that preserves insertion order - /// @sa https://json.nlohmann.me/api/ordered_map/ - template - struct ordered_map; - - /// @brief specialization that maintains the insertion order of object keys - /// @sa https://json.nlohmann.me/api/ordered_json/ - using ordered_json = basic_json; - - NLOHMANN_JSON_NAMESPACE_END - -#endif // INCLUDE_NLOHMANN_JSON_FWD_HPP_ - - -NLOHMANN_JSON_NAMESPACE_BEGIN -/*! -@brief detail namespace with internal helper functions - -This namespace collects functions that should not be exposed, -implementations of some @ref basic_json methods, and meta-programming helpers. - -@since version 2.1.0 -*/ -namespace detail -{ - -///////////// -// helpers // -///////////// - -// Note to maintainers: -// -// Every trait in this file expects a non CV-qualified type. -// The only exceptions are in the 'aliases for detected' section -// (i.e. those of the form: decltype(T::member_function(std::declval()))) -// -// In this case, T has to be properly CV-qualified to constraint the function arguments -// (e.g. to_json(BasicJsonType&, const T&)) - -template struct is_basic_json : std::false_type {}; - -NLOHMANN_BASIC_JSON_TPL_DECLARATION -struct is_basic_json : std::true_type {}; - -// used by exceptions create() member functions -// true_type for pointer to possibly cv-qualified basic_json or std::nullptr_t -// false_type otherwise -template -struct is_basic_json_context : - std::integral_constant < bool, - is_basic_json::type>::type>::value - || std::is_same::value > -{}; - -////////////////////// -// json_ref helpers // -////////////////////// - -template -class json_ref; - -template -struct is_json_ref : std::false_type {}; - -template -struct is_json_ref> : std::true_type {}; - -////////////////////////// -// aliases for detected // -////////////////////////// - -template -using mapped_type_t = typename T::mapped_type; - -template -using key_type_t = typename T::key_type; - -template -using value_type_t = typename T::value_type; - -template -using difference_type_t = typename T::difference_type; - -template -using pointer_t = typename T::pointer; - -template -using reference_t = typename T::reference; - -template -using iterator_category_t = typename T::iterator_category; - -template -using to_json_function = decltype(T::to_json(std::declval()...)); - -template -using from_json_function = decltype(T::from_json(std::declval()...)); - -template -using get_template_function = decltype(std::declval().template get()); - -// trait checking if JSONSerializer::from_json(json const&, udt&) exists -template -struct has_from_json : std::false_type {}; - -// trait checking if j.get is valid -// use this trait instead of std::is_constructible or std::is_convertible, -// both rely on, or make use of implicit conversions, and thus fail when T -// has several constructors/operator= (see https://github.com/nlohmann/json/issues/958) -template -struct is_getable -{ - static constexpr bool value = is_detected::value; -}; - -template -struct has_from_json < BasicJsonType, T, enable_if_t < !is_basic_json::value >> -{ - using serializer = typename BasicJsonType::template json_serializer; - - static constexpr bool value = - is_detected_exact::value; -}; - -// This trait checks if JSONSerializer::from_json(json const&) exists -// this overload is used for non-default-constructible user-defined-types -template -struct has_non_default_from_json : std::false_type {}; - -template -struct has_non_default_from_json < BasicJsonType, T, enable_if_t < !is_basic_json::value >> -{ - using serializer = typename BasicJsonType::template json_serializer; - - static constexpr bool value = - is_detected_exact::value; -}; - -// This trait checks if BasicJsonType::json_serializer::to_json exists -// Do not evaluate the trait when T is a basic_json type, to avoid template instantiation infinite recursion. -template -struct has_to_json : std::false_type {}; - -template -struct has_to_json < BasicJsonType, T, enable_if_t < !is_basic_json::value >> -{ - using serializer = typename BasicJsonType::template json_serializer; - - static constexpr bool value = - is_detected_exact::value; -}; - -template -using detect_key_compare = typename T::key_compare; - -template -struct has_key_compare : std::integral_constant::value> {}; - -// obtains the actual object key comparator -template -struct actual_object_comparator -{ - using object_t = typename BasicJsonType::object_t; - using object_comparator_t = typename BasicJsonType::default_object_comparator_t; - using type = typename std::conditional < has_key_compare::value, - typename object_t::key_compare, object_comparator_t>::type; -}; - -template -using actual_object_comparator_t = typename actual_object_comparator::type; - -///////////////// -// char_traits // -///////////////// - -// Primary template of char_traits calls std char_traits -template -struct char_traits : std::char_traits -{}; - -// Explicitly define char traits for unsigned char since it is not standard -template<> -struct char_traits : std::char_traits -{ - using char_type = unsigned char; - using int_type = uint64_t; - - // Redefine to_int_type function - static int_type to_int_type(char_type c) noexcept - { - return static_cast(c); - } - - static char_type to_char_type(int_type i) noexcept - { - return static_cast(i); - } - - static constexpr int_type eof() noexcept - { - return static_cast(std::char_traits::eof()); - } -}; - -// Explicitly define char traits for signed char since it is not standard -template<> -struct char_traits : std::char_traits -{ - using char_type = signed char; - using int_type = uint64_t; - - // Redefine to_int_type function - static int_type to_int_type(char_type c) noexcept - { - return static_cast(c); - } - - static char_type to_char_type(int_type i) noexcept - { - return static_cast(i); - } - - static constexpr int_type eof() noexcept - { - return static_cast(std::char_traits::eof()); - } -}; - -/////////////////// -// is_ functions // -/////////////////// - -// https://en.cppreference.com/w/cpp/types/conjunction -template struct conjunction : std::true_type { }; -template struct conjunction : B { }; -template -struct conjunction -: std::conditional(B::value), conjunction, B>::type {}; - -// https://en.cppreference.com/w/cpp/types/negation -template struct negation : std::integral_constant < bool, !B::value > { }; - -// Reimplementation of is_constructible and is_default_constructible, due to them being broken for -// std::pair and std::tuple until LWG 2367 fix (see https://cplusplus.github.io/LWG/lwg-defects.html#2367). -// This causes compile errors in e.g. clang 3.5 or gcc 4.9. -template -struct is_default_constructible : std::is_default_constructible {}; - -template -struct is_default_constructible> - : conjunction, is_default_constructible> {}; - -template -struct is_default_constructible> - : conjunction, is_default_constructible> {}; - -template -struct is_default_constructible> - : conjunction...> {}; - -template -struct is_default_constructible> - : conjunction...> {}; - -template -struct is_constructible : std::is_constructible {}; - -template -struct is_constructible> : is_default_constructible> {}; - -template -struct is_constructible> : is_default_constructible> {}; - -template -struct is_constructible> : is_default_constructible> {}; - -template -struct is_constructible> : is_default_constructible> {}; - -template -struct is_iterator_traits : std::false_type {}; - -template -struct is_iterator_traits> -{ - private: - using traits = iterator_traits; - - public: - static constexpr auto value = - is_detected::value && - is_detected::value && - is_detected::value && - is_detected::value && - is_detected::value; -}; - -template -struct is_range -{ - private: - using t_ref = typename std::add_lvalue_reference::type; - - using iterator = detected_t; - using sentinel = detected_t; - - // to be 100% correct, it should use https://en.cppreference.com/w/cpp/iterator/input_or_output_iterator - // and https://en.cppreference.com/w/cpp/iterator/sentinel_for - // but reimplementing these would be too much work, as a lot of other concepts are used underneath - static constexpr auto is_iterator_begin = - is_iterator_traits>::value; - - public: - static constexpr bool value = !std::is_same::value && !std::is_same::value && is_iterator_begin; -}; - -template -using iterator_t = enable_if_t::value, result_of_begin())>>; - -template -using range_value_t = value_type_t>>; - -// The following implementation of is_complete_type is taken from -// https://blogs.msdn.microsoft.com/vcblog/2015/12/02/partial-support-for-expression-sfinae-in-vs-2015-update-1/ -// and is written by Xiang Fan who agreed to using it in this library. - -template -struct is_complete_type : std::false_type {}; - -template -struct is_complete_type : std::true_type {}; - -template -struct is_compatible_object_type_impl : std::false_type {}; - -template -struct is_compatible_object_type_impl < - BasicJsonType, CompatibleObjectType, - enable_if_t < is_detected::value&& - is_detected::value >> -{ - using object_t = typename BasicJsonType::object_t; - - // macOS's is_constructible does not play well with nonesuch... - static constexpr bool value = - is_constructible::value && - is_constructible::value; -}; - -template -struct is_compatible_object_type - : is_compatible_object_type_impl {}; - -template -struct is_constructible_object_type_impl : std::false_type {}; - -template -struct is_constructible_object_type_impl < - BasicJsonType, ConstructibleObjectType, - enable_if_t < is_detected::value&& - is_detected::value >> -{ - using object_t = typename BasicJsonType::object_t; - - static constexpr bool value = - (is_default_constructible::value && - (std::is_move_assignable::value || - std::is_copy_assignable::value) && - (is_constructible::value && - std::is_same < - typename object_t::mapped_type, - typename ConstructibleObjectType::mapped_type >::value)) || - (has_from_json::value || - has_non_default_from_json < - BasicJsonType, - typename ConstructibleObjectType::mapped_type >::value); -}; - -template -struct is_constructible_object_type - : is_constructible_object_type_impl {}; - -template -struct is_compatible_string_type -{ - static constexpr auto value = - is_constructible::value; -}; - -template -struct is_constructible_string_type -{ - // launder type through decltype() to fix compilation failure on ICPC -#ifdef __INTEL_COMPILER - using laundered_type = decltype(std::declval()); -#else - using laundered_type = ConstructibleStringType; -#endif - - static constexpr auto value = - conjunction < - is_constructible, - is_detected_exact>::value; -}; - -template -struct is_compatible_array_type_impl : std::false_type {}; - -template -struct is_compatible_array_type_impl < - BasicJsonType, CompatibleArrayType, - enable_if_t < - is_detected::value&& - is_iterator_traits>>::value&& -// special case for types like std::filesystem::path whose iterator's value_type are themselves -// c.f. https://github.com/nlohmann/json/pull/3073 - !std::is_same>::value >> -{ - static constexpr bool value = - is_constructible>::value; -}; - -template -struct is_compatible_array_type - : is_compatible_array_type_impl {}; - -template -struct is_constructible_array_type_impl : std::false_type {}; - -template -struct is_constructible_array_type_impl < - BasicJsonType, ConstructibleArrayType, - enable_if_t::value >> - : std::true_type {}; - -template -struct is_constructible_array_type_impl < - BasicJsonType, ConstructibleArrayType, - enable_if_t < !std::is_same::value&& - !is_compatible_string_type::value&& - is_default_constructible::value&& -(std::is_move_assignable::value || - std::is_copy_assignable::value)&& -is_detected::value&& -is_iterator_traits>>::value&& -is_detected::value&& -// special case for types like std::filesystem::path whose iterator's value_type are themselves -// c.f. https://github.com/nlohmann/json/pull/3073 -!std::is_same>::value&& -is_complete_type < -detected_t>::value >> -{ - using value_type = range_value_t; - - static constexpr bool value = - std::is_same::value || - has_from_json::value || - has_non_default_from_json < - BasicJsonType, - value_type >::value; -}; - -template -struct is_constructible_array_type - : is_constructible_array_type_impl {}; - -template -struct is_compatible_integer_type_impl : std::false_type {}; - -template -struct is_compatible_integer_type_impl < - RealIntegerType, CompatibleNumberIntegerType, - enable_if_t < std::is_integral::value&& - std::is_integral::value&& - !std::is_same::value >> -{ - // is there an assert somewhere on overflows? - using RealLimits = std::numeric_limits; - using CompatibleLimits = std::numeric_limits; - - static constexpr auto value = - is_constructible::value && - CompatibleLimits::is_integer && - RealLimits::is_signed == CompatibleLimits::is_signed; -}; - -template -struct is_compatible_integer_type - : is_compatible_integer_type_impl {}; - -template -struct is_compatible_type_impl: std::false_type {}; - -template -struct is_compatible_type_impl < - BasicJsonType, CompatibleType, - enable_if_t::value >> -{ - static constexpr bool value = - has_to_json::value; -}; - -template -struct is_compatible_type - : is_compatible_type_impl {}; - -template -struct is_constructible_tuple : std::false_type {}; - -template -struct is_constructible_tuple> : conjunction...> {}; - -template -struct is_json_iterator_of : std::false_type {}; - -template -struct is_json_iterator_of : std::true_type {}; - -template -struct is_json_iterator_of : std::true_type -{}; - -// checks if a given type T is a template specialization of Primary -template